1.MSComm控件
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 on #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, 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(); //一个非常重要的函数,获得串口上刚发生的事件("事件"可以理解为软件意义上的 //"消息"或硬件意义上的"中断"),事件的发送会导致On 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)设置与获取串口上发生的事件接口函数。
2.例程
程序的功能和界面(如下图)都与本文连载三中《基于WIN32 API的串口编程》相同,不同的只是连载三的串口通信以API实现,而本节的串口通信则以MSComm控件实现。
|
使用第1节的方法将控件添加入工程并添加mscomm.h和mscomm.cpp文件后,为了使用控件,我们将控件拖入对话框内任意一个位置(运行时"电话"图标会隐藏),其操作如下图:
|
有趣而极富人性化的是我们可以直接右键单击这个"电话",来设置串口的属性,如下图:
|
接着,我们需要为控件添加一个对应的成员变量m_mscom,其对应的变量类型为CMSComm,如下图:
|
这样就建立了m_mscom和IDC_MSCOMM1控件的相互映射:
void CSerialPortActivexDlg::DoDataExchange(CDataExchange* pDX)
{ CDialog::DoDataExchange(pDX); //{{AFX_DA DDX_Text(pDX, IDC_RECV_EDIT, m_recv); DDX_Text(pDX, IDC_SEND_EDIT, m_send); DDX_Control(pDX, IDC_MSCOMM1, m_mscom); //}}AFX_DA } |
同时,在对话框的头文件也会由"MFC类向导"自动定义CSerialPortActivexDlg类的CMSComm型成员变量m_mscom:
CMSComm m_mscom; |
在对话框初始化时(即在CSerialPortActivexDlg::On
BOOL CSerialPortActivexDlg::On
{ CDialog::On // Add "About..." 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::On
{ // TODO: Add your control notification handler co UpdateData(true); m_mscom.SetOutput(COleVariant(m_send)); } |
为了处理接收事件,我们需要为MScomm控件添加对应的消息处理函数。如下图,我们通过"MFC类向导"添加了CSerialPortActivexDlg 类的成员函数On
这样,在对话框的头文件中就会自动增加下面两句:
afx_msg void On
DECLARE_EVENTSINK_MAP() |
来自AFX_MSG部分:
// Generated message map functions
//{{AFX_MSG(CSerialPortActivexDlg) virtual BOOL On afx_msg void On afx_msg void On afx_msg HCURSOR On afx_msg void On afx_msg void On afx_msg void On DECLARE_EVENTSINK_MAP() //}}AFX_MSG |
同时在对话框的.cpp文件中会增加下列代码实现串口消息映射:
BEGIN_EVENTSINK_MAP(CSerialPortActivexDlg, CDialog)
//{{AFX_EVENTSINK_MAP(CSerialPortActivexDlg) ON_EVENT(CSerialPortActivexDlg, IDC_MSCOMM1, 1 /* On On //}}AFX_EVENTSINK_MAP END_EVENTSINK_MAP() |
我们定义CSerialPortActivexDlg::On
void CSerialPortActivexDlg::On { // TODO: Add your control notification handler co VARIANT variant_inp; COleSafeArray safearray_inp; long len,i; char rxdData; if (m_Comm.GetCommEvent()==2) { variant_inp=m_Comm.GetInput(); safearray_inp=variant_inp; len=safearray_inp.GetOneDimSize(); for (i=0;i<len;i++) { safearray_inp.GetElement(&i,&rxdData); m_strRxd+=rxdData; } UpdateData(FALSE); } } |
最后,与连载三类似,再次借助"串口调试助手"以实例验证了本程序的正确性,如下图:
|
最后,需要特别提示的是:如果要在基于"文档/视图"的框架结构程序而非对话框程序中使用串口控件,我们不能轻松地使用"MFC类向导",这时候必须手动地添加相关代码。
在MainFrm.h头文件中加入:
afx_msg void On
DECLARE_EVENTSINK_MAP() |
并定义CMSComm成员变量:
CMSComm m_ComPort; |
在MainFrm.cpp文件中添加
BEGIN_EVENTSINK_MAP(CMainFrame, CFrameWnd)
ON_EVENT(CMainFrame,ID_COMMCTRL,1,On //映射ACTIVEX控件的事件 END_EVENTSINK_MAP() |
在MainFrm.cpp文件的On
ComPort.Create(NULL, WS_VISIBLE | WS_CHILD, CRect(0,0,0,0),this, ID_COMMCTRL); |
以创建CMSComm控件。
此后,我们就可以在CMainFrame类的函数中使用串口控件对应的ComPort控件成员变量。