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 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, 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)设置与获取串口上发生的事件接口函数。
摘要:本文介绍了在Microsoft Visual C++ 6.0环境下通过对Active X控件的编程来实现串口的通信的一般方法。
一、 引言
当我们在Windows操作系统下开发串行通信程序时通常不得不面对许多复杂的API函数,因为在Windows操作系统下不能直接对设备端口进行操作,也不能在系统级(Ring 3级别)使用任何DOS或BIOS中断,如要对端口进行编程则只能以文件的形式来对端口进行操作,这就使开发人员不得不面对非常烦琐的API函数编程。本文对此提出了另外一种封装性很好的使用Microsoft Visual C++ 6.0自带的"Microsoft Communications Control,version 6.0"Active X控件的编程方法,通过对该控件的正确使用,我们可以比较轻松地编写出所需的串行通信程序。
下面,我们将结合一个实际的程序示例来对此方法进行说明。本程序的编程环境是Windows 98和Microsoft Visual C++ 6.0。在本程序示例中对为避免阻塞而对线程的使用以及在使用中遇到的一些问题也做了详细的介绍。
二、 程序的设计实现
在开始进行代码编程前,首先以在工程中插入组件或控件的方式将Active X控件"Microsoft Communications Control,version 6.0"加入到工程中来,此时将会在工程中添加一个关于此控件的新类。使用该控件的一些方法和属性时不能象使用类一样简单的声明一个实例对象,而要通ClassWizard为该控件和一个成员变量建立起绑定关系,在此我们将该控件同变量m_Comm相绑定后就可以通过该控件提供的方法来对串口的各种通讯参数进行设置了。为了编程方便起见,也可以在资源视图中直接对该控件的属性进行设置,如无特别要求,对下表所列属性进行设置就基本可以满足编程要求了。现将常用的属性列表如下:
属性 设定值 属性说明
CommPort 1 串口号,一般从1到4
InBufferSize 30720 接收缓冲区大小,为保持程序的稳定,建议设得值足够大
InputMode 0-Text 接收数据的类型,0表示文本类型,1表示二进制类型
InputLen 0 从接收缓冲区读取的字节数,0表示全部读取
OutBufferSize 512 发送缓冲区大小
Settings 4800,n,8,1 串口的参数设置,依次为波特率、奇偶校验(n-无校验,e-偶校验,o-奇校验)、数据位数、停止位数
RThreshold 1 设定当接收几个字符时触发OnComm事件,0表示不产生事件,
1表示每接收一个字符就产生一个事件
SThreshold 0 设定在触发OnComm事件前,发送缓冲区内所允许的最少的字符数,
0表示发送数据时不产生事件,1表示当发送缓冲区空时产生OnComm事件
我们要求能在程序启动的同时就打开串口以便即时对从串口到达的数据进行接收、处理。一般来说可以将下面的打开端口的代码写在OnCreate()、OnInitialUpdate()、InitInstance ()等程序入口函数中:
……
if(!m_Comm.GetPortOpen()) //检测是否已经打开过端口
m_Comm.SetPortOpen(TRUE); //如没有打开则将端口打开
接下来的工作就是对数据的发送与接收了,这也是本文所要介绍的重点所在。发送数据的代码原则上是可以写到一个成员函数中被直接调用的,但这并不是一个良好的编程习惯:我们应当把比较耗时的操作,如文件拷贝、打印、端口传输等工作放到一个单独的线程当中,以避免其在工作时会引起整个进程的阻塞,以提高整个系统对CPU的利用率。例如我们可以在视类中菜单或按钮的响应函数中用AfxBeginThread(WriteProc,this)函数来开启一个名为"WriteProc"的线程,由于在线程中还需要使用视类的函数和变量,为了不产生新的视类的实例对象,我们通过该函数的第二个参数将指向当前的视类的指针this作为参数传递给线程。在线程中可以用如下两种方法之中的一种调用视类的成员函数:
((COLECommView*) pParam)->DoSendProc();
或是:
COLECommView* view=(COLECommView*) pParam;
View->DoSendProc();
其中从pParam传来的变量就是指向视类的指针。在线程中通过调用视类中的DoSendProc函数来完成对数据的发送,正是由于该函数是被全局的线程所调用的,我们就不可以使用取编辑框上的数据时通常所用的UpdateData()函数了,取而带之的是API 函数GetDlgItemText(),取到输入的数据后通过控件的SetOutput() 方法就把数据从串口发出去了,其中发送数据必须经ColeVariant类将其转换为通用的VARIANT型变量。实现
主要代码如下:
……
char a[255];
HWND hwnd=GetSafeHwnd();
::GetDlgItemText(hwnd,IDC_EDIT1,a,255);
int i=0;
CString str;
while(a[i]!='\0')
{
str.Format("%c",a[i]);
m_SendData+=str;
i++;
}
str.Format("%c",10);
m_SendData+=str;
m_Comm.SetOutput(COleVariant(m_SendData));
……
至于数据的接收,我们可以通过让MS Comm控件响应其OnComm事件来完成,通过ClassWizard加入其对事件的响应后,通过下面的事件映射,当有字符到达时便会通知 OnComm()函数去处理,从而实现数据的异步接收:
……
BEGIN_EVENTSINK_MAP(COLECommView, CFormView)
//{{AFX_EVENTSINK_MAP(COLECommView)
ON_EVENT(COLECommView, IDC_MSCOMM1, 1 /* OnComm */, OnComm, VTS_NONE)
//}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()
……
void COLECommView::OnComm()
{
VARIANT Input;
if(m_Comm.GetCommEvent()==2)//接收缓冲区内有字符
{
Input=m_Comm.GetInput();//读取缓冲区内的数据
CString msg=Input.bstrVal;
CString str;
str.Format("%c",10);
if(msg.Right(1)==str)
{
m_RecvData+=msg;
m_History.AddString(m_RecvData);
m_RecvData="";
}
else
m_RecvData+=msg;
}
}
当数据被接收到接收缓冲区后,对于字符可以从VARIANT型结构变量的bstrVal成员变量中获取,VARIANT数据结构相当复杂,并牵扯到COM(Component Object Model,组件对象模型)中的一些概念,具体详情请参阅Microsoft Corpration发布的MSDN中的有关论述。
三、 测试与实验
编译运行程序之前有必要对机器的端口做一番检查,以确保端口的完好,可以用常见的DOS程序Comdebug来检查。在确认串口工作正常后,可用串口线将两台机器的串口相连,同时在两台机子上运行该程序,如果没有条件也可只用一台微机,将其串口的2脚和3脚短接,使其处于自发自收状态。经过数据的传输实验证明该程序是可靠、正确的。
小结:利用通讯控件可以很容易的编写出串行通信程序。但相对来说通讯控件在VC中的使用要比在VB、Delphi中复杂的多,要想对串口通讯开发出更多更灵活的使用方法还需要不断的实践中摸索。本程序在
Windows 98下,由Microsoft Visual C++ 6.0编译通过。