VC++串口读写编程方法
Visual C++为我们提供了一种好用的ActiveX控件Microsoft Communications Control(即MSComm)来支持应用程序对串口的访问,在应用程序中插入MSComm控件后就可以较为方便地实现对通过计算机串口收发数据。
要使用ActiveX控件MSComm,程序员必须将其添加入工程,其方法是:
  (1)单击主菜单project的子菜单Add To project的Components and Controls选项;
(2)在弹出的"Components and Controls Gallery"对话框中选择Registered ActiveX Controls文件夹中的"Microsoft Communications Control,version 6.0"选项,如下图:
单击其中的"Insert"按钮,MSComm控件就被增加到工程中了。与此同时,类CMSComm的相关文件mscomm.h和mscomm.cpp也 一并被加入Project的Header Files和Source Files中。当然,程序员可以自己修改文件名。
直接分析mscomm.h头文件就可以完备地获取这个控件的使用方法(主要是public类型的接口函数),下面我们摘取了头文件的主要代码并对其关键部分给出了注释:
#if !defined(AFX_MSCOMM_H__)
#define AFX_MSCOMM_H__
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// Machine generated IDispatch wrapper class(es) created by Microsoft Visual C++

// NOTE: Do not modify the contents of this file. If this class is regenerated by
// Microsoft Visual C++, your modifications will be overwritten.

/////////////////////////////////////////////////////////////////////////////
// CMSComm wrapper class

class CMSComm : public CWnd
{
protected:
 DECLARE_DYNCREATE(CMSComm)
public:
 CLSID const& GetClsid()
 {
  static CLSID const clsid = { 0x648a5600, 0x2c6e, 0x101b, { 0x82, 0xb6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14 } };
  return clsid;
 }
 virtual BOOL Create(LPCTSTR lpszClassName,
   LPCTSTR lpszWindowName, DWORD dwStyle,
   const RECT& rect,
   CWnd* pParentWnd, UINT nID,
   CCreateContext* pContext = NULL)
 { return CreateControl(GetClsid(), lpszWindowName, dwStyle, rect, pParentWnd, nID); }

 BOOL Create(LPCTSTR lpszWindowName, DWORD dwStyle,
activex 控件   const RECT& rect, CWnd* pParentWnd, UINT nID,
   CFile* pPersist = NULL, BOOL bStorage = FALSE,
   BSTR bstrLicKey = NULL)
 { return CreateControl(GetClsid(), lpszWindowName, dwStyle, rect, pParentWnd, nID,
  pPersist, bStorage, bstrLicKey); }

  // Attributes
 public:

  // Operations
 public:
  void SetCDHolding(BOOL bNewValue);
  BOOL GetCDHolding();
  void SetCommID(long nNewValue);
  long GetCommID();
  void SetCommPort(short nNewValue);
  //设置端口号,如nNewValue =1表示COM1
  short GetCommPort();
  void SetCTSHolding(BOOL bNewValue);
  BOOL GetCTSHolding();
  void SetDSRHolding(BOOL bNewValue);
  BOOL GetDSRHolding();
  void SetDTREnable(BOOL bNewValue);
  BOOL GetDTREnable();
  void SetHandshaking(long nNewValue);
  long GetHandshaking();
  void SetInBufferSize(short nNewValue);
  short GetInBufferSize();
  void SetInBufferCount(short nNewValue);
  short GetInBufferCount();
  void SetBreak(BOOL bNewValue);
  BOOL GetBreak();
  void SetInputLen(short nNewValue);
  short GetInputLen();
  void SetNullDiscard(BOOL bNewValue);
  BOOL GetNullDiscard();
  void SetOutBufferSize(short nNewValue);
  short GetOutBufferSize();
  void SetOutBufferCount(short nNewValue);
  short GetOutBufferCount();
  void SetParityReplace(LPCTSTR lpszNewValue);
  CString GetParityReplace();
  void SetPortOpen(BOOL bNewValue);
  //打开或关闭串口,TRUE:打开,FALSE:关闭
  BOOL GetPortOpen();
  //串口是否已打开,TRUE:打开,FALSE:关闭
  void SetRThreshold(short nNewValue);
  //如果设置为1,表示一接收到字符就发送2号事件
  short GetRThreshold();
  void SetRTSEnable(BOOL bNewValue);
  //硬件握手使能?
  BOOL GetRTSEnable();
  void SetSettings(LPCTSTR lpszNewValue);
  //Settings由4部分组成,其格式为:"BBBB,P,D,S",即"波特率,是否奇偶校验,数据位 //个数,停止位",如设置为:"9600,n,8,1"
  CString GetSettings();
  void SetSThreshold(short nNewValue);
  //如果保持缺省值0不变,则表示发送数据的过程中串口上不发生事件
  short GetSThreshold();
  void SetOutput(const VARIANT& newValue);
  //一个非常重要的函数,用于写串口,注意其接收的输入参数为VARIANT类型对象,
  //我们需要将字符串转化为VARIANT类型对象
  VARIANT GetOutput();
  void SetInput(const VARIANT& newValue);
  VARIANT GetInput();
  //一个非常重要的函数,用于读串口,注意其返回的是VARIANT类型对象,我们需要
  //将其转化为字符串
  void SetCommEvent(short nNewValue);
  short GetCommEvent();
  //一个非常重要的函数,获得串口上刚发生的事件("事件"可以理解为软件意义上的
  //"消息"或硬件意义上的"中断"),事件的发送会导致OnComm消息的诞生!
  void SetEOFEnable(BOOL bNewValue);
  BOOL GetEOFEnable();
  void SetInputMode(long nNewValue);
  long GetInputMode();
 };

 //{{AFX_INSERT_LOCATION}}
 // Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif
分析上述源代码可知,基本上,MSComm的诸多接口可以分为如下几类:
  (1)打开与设置串口接口函数;
  (2)获得串口设置和串口状态接口函数;
  (3)设置串口发送数据方式、缓冲区接口及发送数据接口函数;
  (4)设置串口接收数据方式、缓冲区接口及接收数据接口函数;
  (5)设置与获取串口上发生的事件接口函数
为了使用控件,我们将控件拖入对话框内任意一个位置(运行时"电话"图标会隐藏),其操作如下图:
我们可以直接右键单击这个"电话",来设置串口的属性,如下图:
接着,我们需要为控件添加一个对应的成员变量m_mscom,其对应的变量类型为CMSComm,如下图:
这样就建立了m_mscom和IDC_MSCOMM1控件的相互映射:
void CSerialPortActivexDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);
 //{{AFX_DATA_MAP(CSerialPortActivexDlg)
  DDX_Text(pDX, IDC_RECV_EDIT, m_recv);
  DDX_Text(pDX, IDC_SEND_EDIT, m_send);
  DDX_Control(pDX, IDC_MSCOMM1, m_mscom);
 //}}AFX_DATA_MAP
}
同时,在对话框的头文件也会由"MFC类向导"自动定义CSerialPortActivexDlg类的CMSComm型成员变量m_mscom:
CMSComm m_mscom;
在对话框初始化时(即在CSerialPortActivexDlg::OnInitDialog函数中)打开串口1:
BOOL CSerialPortActivexDlg::OnInitDialog()
{
 CDialog::OnInitDialog();


 // Add "" menu item to system menu.

 // IDM_ABOUTBOX must be in the system command range.
 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 ASSERT(IDM_ABOUTBOX < 0xF000);

 CMenu* pSysMenu = GetSystemMenu(FALSE);
 if (pSysMenu != NULL)
 {
  CString strAboutMenu;
  strAboutMenu.LoadString(IDS_ABOUTBOX);
  if (!strAboutMenu.IsEmpty())
  {
   pSysMenu->AppendMenu(MF_SEPARATOR);
   pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX,
   strAboutMenu);
  }
 }

 // Set the icon for this dialog. The framework does this automatically
 // when the application's main window is not a dialog
 SetIcon(m_hIcon, TRUE); // Set big icon
 SetIcon(m_hIcon, FALSE); // Set small icon

 // TODO: Add extra initialization here
 m_mscom.SetCommPort(1); //串口1
 m_mscom.SetInBufferSize(1024); //设置输入缓冲区的大小,Bytes
 m_mscom.SetOutBufferSize(512); //设置输入缓冲区的大小,Bytes
 if(!m_mscom.GetPortOpen()) //打开串口
 {
  m_mscom.SetPortOpen(true);
 }
 m_mscom.SetInputMode(1); //设置输入方式为二进制方式
 m_mscom.SetSettings("9600,n,8,1"); //设置波特率等参数
 m_mscom.SetRThreshold(1); //为1表示有一个字符即引发事件
 m_mscom.SetInputLen(0);

 return TRUE; // return TRUE unless you set the focus to a control
}
最核心的发送串口数据函数("发送"按钮单击事件)如下:
void CSerialPortActivexDlg::OnSendButton()
{
 // TODO: Add your control notification handler code here
 UpdateData(true);

 CByteArray sendArr;
 WORD wLength;

 wLength = m_send.GetLength();
 sendArr.SetSize(wLength);
 for(int i =0; i<wLength; i++)
 {
  sendArr.SetAt(i, m_send.GetAt(i));
 }
 m_mscom.SetOutput(COleVariant(sendArr));
}
为了处理接收事件,我们需要为MScomm控件添加对应的消息处理函数。如下图,我们通过"MFC类向导"添加了CSerialPortActivexDlg 类的成员函数OnCommMscomm1():
这样,在对话框的头文件中就会自动增加下面两句:
afx_msg void OnCommMscomm1();//函数声明
DECLARE_EVENTSINK_MAP()
同时在对话框的.cpp文件中会增加下列代码实现串口消息映射:
BEGIN_EVENTSINK_MAP(CSerialPortActivexDlg, CDialog)
//{{AFX_EVENTSINK_MAP(CSerialPortActivexDlg)
 ON_EVENT(CSerialPortActivexDlg, IDC_MSCOMM1, 1 /* OnComm */,
 OnCommMscomm1, VTS_NONE)
//}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()
我们定义CSerialPortActivexDlg::OnCommMscomm1()函数主要处理数据的接收,其源代码为:
void CSerialPortActivexDlg::OnCommMscomm1()
{
 // TODO: Add your control notification handler code here
 UpdateData(true);

 VARIANT variant_inp;
 COleSafeArray safearray_inp;

 long i = 0;
 int len;
 char rxdata[1000];
 CString tmp;
 switch (m_mscom.GetCommEvent())
 {
  case 2:
  //表示接收缓冲区内有字符
  {
   variant_inp = m_mscom.GetInput();
   safearray_inp = variant_inp;
   len = safearray_inp.GetOneDimSize();
   for (i = 0; i < len; i++)
   {
    safearray_inp.GetElement(&i, &rxdata[i]);
   }
   rxdata[i] = '\0';
  }
  m_recv += rxdata;
  UpdateData(false);
  break;
  default:
   break;
}
}