Windows上C++串口通讯回调模型
1 简介
本文采用C++的面向对象以及模板技术,在Windows平台上设计了使用OVERLAPPED机制的串口通讯C++类Serialflexer。通过使用该C++类,可以方便的创建运行在Windows上的具备串口通讯及状态监控功能的程序。
注:类Serialflexer的绝大部分技术思路来源于参考资料[1]和[3],唯一的创造就是使用接口类IDteCallBack作为串口通讯的事件接受器。通过该接收器接口,可以方便的实现多个串口通讯的统一管理。
关键词:
OVERLAPPED Serial C+ + Multithreaded Windows
2 C++类Serialflexer
类Serialflexer名称是Serial communication Multiplexer的缩写,原意是为了能够同时进行多个串口的通讯,不过现在一个类Serialflexer对象只支持一个串口的通讯。
类Serialflexer中包含了一个Reader线程对象和Status Monitor线程对象,同时继承了IDteCallBack接口类。通过线程函数的参数将类Serialflexer本身this指针传递给线程对象,两个线程能够调用IDteCallBack中包含的接口函数,从而在类Serialflexer中进行串口数据接收和状态监控。通过让具体的实现类继承自类Serialflexer,实现IDteCallBack中包含的接口函数,就可以在外部控制串口数据和状态。
类Serialflexer暂时不包含Writer线程。
3 串口通讯的几个问题
开始设计串口通讯程序,需要考虑的问题不外乎:
1. 采用OVERLAPPED或是nonoverlapped模型?
2. 多线程的滥用?
3. 同步亦或异步?
3.1 OVERLAPPED I/O
如果考虑兼容性问题,OVERLAPPED并不是最好的选择,因为大部分的操作系统都不支持。但是OVERLAPPED的优势在于灵活性和效率更高,因此Serialflexer也采用了OVERLAPPED机制。
3.2 多线程问题
过多的线程并不能带给串口通讯更多的效率,但是想在一个线程里同时进行Read和Write也不是好主意。Serial Port I/O一文中讲“Such code is difficult to create, debug, or even reason about successfully and should be avoided. Using separate threads for input and output results in cleaner code, with a nice separation of concerns.”所以在[1]和[3]的例子中都采取了将Read和Write分开成两个线程的设计。
Serialflexer尚未实现Writer线程,因为Writer线程涉及到UI层面,我原先的设计思路是采用Message Queue来实现一个相对UI窗口独立的Writer线程,但暂时还没有时间完成这个工作。
为了实现串口的状态监控,Serialflexer设定了串口事件接收机制。Serialflexer采用了将Read和Status分开成两个线程的做法。而将两者放在同一个线程中也未尝不可,C Win32 Serial Communication中介绍的例子MTTTY就采用了后面的做法。
3.3 同步、异步
第三个问题,同步或是异步,这是一个与项目直接相关的问题。同步,即是DTE有Request,然后需要从DCE获得Response。Serialflexer没有考虑同步问题,只是实现了异步的数据接收和状态监控功能。
4 使用C++模板技术
使用模板技术主要是在设计Read buffer Size的时候想到的。考虑到不同的DCE设备使用不同长度的数据,除了模板机制,我想不到更好的使用C++实现缓冲不同数据长度的解决方法(std::string无法替代固定长度的缓冲区),所以为了让类Serialflexer能够适应不同的场合,我将缓冲区长度放到了模板参数之中。
由于采用了模板机制,所有的代码都在头文件中实现。而为了考虑代码可读性,使用了inl文件,作为头文件的扩充。
有些参数可以用模板来实现,有些参数则应该作为变参实现,这样可以提供更多的用户选项。这些参数主要视不同产品需求而定,类Serialflexer并没有更多考虑该问题。
5 接口IDteCallBack的设计
原先为了实现在Reader线程和Status线程中将数据接收和状态事件反映到串口管理类之中,我首先想到的是全局函数思路。后来才想到了使用ISerialCallback接口类这样面向对象的思路。也就是在ISerialCallback类中定义一系列的纯虚函数,包括DataRece数据接收函数以及Status状态反馈函数,而后,在具体的子类中构造这些回调函数的具体实现。。
使用ISerialCallback的第一步是定义其中的接口函数,第二步是如何将该接口传递到线程之中。我的思路是,类Serialflexer继承ISerialCallback,但是并不实现纯虚函数,而是将实现留给Serialflexer的子类,也就是具体实现类。然后,在其中定义两个嵌套子类ReaderThread和StatusThread,线程函数的参数即是Serialflexer的this指针。这样线程函数不仅可以在权限范围内的操作,还可以通过指针调用ISerialCallback的接口函数。
6 Message Queue机制
POSIX Message Queue是通过读、写各一个信号量保护一个列表实现的多线程安全消息队列机制。Windows并没有直接提供这种机制。不过MSDN Issue 2008-7中有一个比较好的类似Message Queue的功能的实现,不过暂时没有纳入Serialflexer之中。
7 多串口管理机制
原先类Serialflexer的设计思路是可以同时管理多个串口,这主要通过ISerialCallback的每个函数第一个参数都使用HANDLE参数,代表当前调用的Serial Port。不过在现有实现中,并没有实现该机制。
8 串口状态监控
进行串口状态监控的Status线程通过WaitCommEvent监测串口状态。主要的三个状态对象是COMSTAT、Events和Comm Error Flags。
9 代码获取
在Googlecode中的Serialflexer项目中通过SVN获取代码。
10 参考资料
[1] C Win32 Serial Communication, MTTTY sample
[2] Introduction to RS232 Serial Communications,
[3] Serial Port I/O, http://www.flounder.com/serial.htm
[4] Serial Port Programming in Windows and Linux, Maxwell Walter, November 7, 2003
11 Acronyms
DTE | Data Terminal Equip ment |
DCT | Data Communication Equipment |
|
|