CSerialPort 类分析及漏洞的修正

2014-12-25 03:18贾小文贺秀良
军事交通学院学报 2014年11期
关键词:监听控件线程

贾小文,贺秀良

(军事交通学院 基础部,天津300161)

串行通信是计算机与外围设备进行数据通信常见的通信方式。按电气标准和协议串行通信标准 有 RS232-C、RS422、RS485、USB 等[1-2]。RS232-C(ANSI/EIA232 标准)是IBM-PC 及其兼容机上的串行连接标准。由于个人计算机的普及,RS232-C 成为使用最为广泛的串口通信标准。通用串行总线(universal serial bus,USB)是一个新的外部总线标准,于20 世纪90 年代由英特尔、康柏、IBM、微软等多家公司联合推出,由于支持热插拔、即插即用、传输速率高等原因,目前被计算机广泛用为与外设进行串行通信的标准接口[3]。笔记本计算机由于其轻薄的特点,绝大多数并不提供RS232-C 串口,也即普通意义上的“COM”串口,但都提供USB 接口。当笔记本计算机与仅支持RS232 串口的设备进行串行通信时,通常需要从USB 到RS232 的转换。这种转换可以通过专用的芯片完成,比较成熟的芯片有PL2303、CP2102 等[4-5]。通过USB—RS232 转换,可以方便地利用笔记本对支持串口通信的设备进行读写操作。

CSerialPort 类是为了方便对RS232 串口操作而开发的一个C + +类,可以代替常用的ActiveX控件进行串口应用开发。该类封装了相关的文件操作Windows API 函数,采用异步文件读写模型,结构良好,功能明确,能够满足绝大多数串口开发应用场合[6]。但是,由于串口应用环境的复杂性,CSerialPort 类存在一些漏洞,特别是在进行USB—RS232 串口读写操作时,不能向串口写入数据。本文分析了这些漏洞产生的原因,并给出了修正漏洞的方案。

1 CSerialPort 类分析和使用

在Windows 环境下,开发RS232 串口经常采用的方法是使用ActiveX 控件或者直接采用Windows API 函数。

当采用ActiveX 控件时,通常使用的是微软开发的MSComm 控件。MSComm 控件属于微软组件对象模型(component object model,COM)的产品,为应用程序提供了串行接口收发数据的简便方法。

MSComm 采用事件驱动方式和查询方式,提供了一系列标准通信命令和接口,可以方便地与串口建立连接并对串口发送数据和接收数据。MSComm 与开发环境无关,无论是VC、VB,还是Dephi 等流行开发工具都能使用。MSComm 的缺点是可定制性不强,对于复杂的串口应用场合缺乏灵活性。MSComm 属于ActiveX 控件,必须注册以后才能使用,增加了程序的体积,给使用上带来不便。当MSComm 不能满足使用要求时,就必须利用Windows API 直接进行串口操作。

利用Windows API 对串口读写操作的困难在于异步文件读写的复杂性,要写出高效稳定的串口读写程序需要相当的编程知识和技巧。这实际上限制了利用API 直接操作串口的可行性。

CSerialPort 本质上属于利用Windows API 对串口进行操作这种形式。但它以类的形式将相关Win32 API 以及对串口文件异步操作所涉及到的多线程、线程同步等复杂编程技术封装起来,大大降低了对串口读写操作的复杂性,提供给使用者比MSComm 更简单和直接的使用接口。与MSComm 控件COM 访问方式相比较,CSerialPort不涉及对读写数据的封装和转换。CSerialPort 以C + +代码类的方式提供,可以直接包含到程序源代码里,不像MSComm 必须安装注册控件才能使用。CSerialPort 结构良好,对串口的操作采用典型成熟的多线程异步Overlapped 文件读写模型,所有的源代码都是公开透明的,使用者可以通过修改或者直接从该类派生而扩展该类的功能,写出功能更强的串口操作应用程序。同时该类也是一份很好的关于串口读写操作Win32 API 编程的技术范例,对于开发自己个性化的串口读写应用具有一定参考价值[2]。

CSerialPort 属于事件驱动,使用非常简单,读写串口前先产生该类的实例,1 个串口对应1 个实例。主程序通过直接调用实例的写操作接口和关闭接口进行写操作和关闭操作,通过响应消息的方式进行读操作。串口检测到的其他事件也以消息的方式发送给主程序,主程序通过响应该消息来处理相应的串口事件。表1 为CSerialPort 定义的基本消息,表2 为CSerialPort 提供的接口函数。关于该类的详细使用方法可参考文献[7]。

表1 CSerialPort 消息

表2 CSerialPort 主要接口函数(不含返回类型和函数参数)

2 CSerialPort 类漏洞的分析和修正

CSerialPort 最初发布只是解决了串口读写操作最关键的部分,亦即多线程异步overlapped 文件读写机制实现,并未充分考虑串口操作所涉及到的一些细节问题,而是把这些问题留给了使用者。使用者可以利用其开源特性,根据自己的应用需求直接对源类进行扩充和修改,实现自己要求的功能。这些细节问题,如果在使用的过程中不注意,就会成为程序的漏洞,严重者会导致串口操作失败。

2.1 写操作漏洞

CSerialPort 提供写操作接口如下:

当要向串口写入数据时,CSerialPort 将写操作事件m_hWriteEvent 置为有信号状态,同时将待写入字符串拷贝到写入缓冲区m_szWriteBuffer,并指定写入字符长度m_nWriteSize。由于写事件为有信号状态,监听线程通过查询该信号状态调用Windows API 函数WriteFile 将数据写入串口。上述写操作函数在写入二进制数据时存在问题。

strcpy 和strlen 函数都是以标准C/C + +字符串作为操作对象。在C/C + + 规范中,标准字符串是以“”结尾,如果写入字符串中间包含“”字符,字符串就会被截断,只有第1 个“”之前的所有字符会被放入写入缓冲区并写入串口,而其他的数据全被丢弃了。在正常的串口读写中,数据经常为二进制数据,如RTKGPS 接收机所用差分数据就是标准的二进制数据流,使用上述写入接口就不能把数据全部完整地写入串口,使差分定位失败。对上述漏洞的修正可以通过指定写入字符串长度和利用memcpy 代替strcpy 来完成。修正的代码如下[7]:

2.2 关闭串口漏洞

CSerialPort 提供关闭串口接口如下:

原理是通过设定关闭事件m_hShutdownEvent为有信号状态,监听线程查询到该事件为有信号后结束线程,从而实现串口关闭。上述方法也存在问题。由于关闭串口操作并没有关闭文件句柄(m_hComm),即并没有释放相应的串口资源,当下一个CSerialPort 实例(或其他线程)尝试打开相同串口就会报错,导致应用程序里操作多个串口时出现无法关闭和重新打开串口的错误。解决办法可考虑在SetEvent(m_hShutdownEvent)后释放串口资源,添加如下代码:

上述方法仍不完善。当主程序调用ClosePort关闭串口时,在将m_hShutdownEvent 置为有信号状态后马上关闭串口资源,如果此时CSerialPort 监听线程正在进行读操作或者写操作,就会出现非法操作导致程序崩溃。因此,在关闭串口前应保证没有读写进行,待监听线程关闭后再关闭串口。应将SetEvent(m_hShutdownEvent)放在一个循环内,主线程反复检测监听线程是否关闭,确认关闭后再执行后面关闭串口资源操作。代码如下:

但是简单的循环也存在问题,会导致程序“死锁”,原因是如果监听线程正在读取数据,则监听线程调用SendMessage 函数将数据发送到主线程并等待主线程返回,而主线程亦在等待监听线程关闭,形成“死锁”状态。因此,应该在循环内添加PeekMessage 消息循环,使主线程在关闭线程的同时仍然能够响应消息[8]。综上,最终关闭代码如下[9]:

2.3 USB 转RS232 时不能写入漏洞

经过上述改进,CSerialPort 进行正常的串口操作没有任何问题,但在对USB 转RS232 的串口进行读写操作时,发现数据不能连续写入。该漏洞以前从没有被发现,在于其具有较大隐蔽性,只有在特定的操作环境下才表现出来。

CSerialPort 监听代码关键部分如下(省略部分无关代码):

可以看到,CSerialPort 类调用WaitCommEvent函数查询是否有数据到达串口,如果串口中有数据,overlapped 变量m_ov 中读操作事件被置为有信号状态,否则被置为无信号状态。由于COM 串口以异步读写overlapped 方式打开,WaitCommEvent 会马上返回,GetLastError 一般应当返回“997”错误号(ERROR_IO_PENDING),表示等待串口发生事件是在后台运行。当主线程将写操作事件置为有信号状态进行写操作时,若此时无数据到达串口,由于WaitForMultipleObjects 会正常返回(等待到写操作事件发生),那么在下一次调用WaitCommEvent 时就会产生“87”错误号(ERROR_INVALID_PARAMETER),这种情况也是正常的,可以忽略(此时m_ov 中读操作事件实际并没有被“等待”)。

但是当通过USB—RS232 串口转换(PL2303芯片)对RS232 串口进行写入操作时,调试发现即使串口无输入数据,WaitCommEvent 也不会返回“87”错误号,而一直返回“997”错误信号,这种情况显示WaitForMultipleObjects(3,port->m_hEventArray,FALSE,INFINITE)函数正常等待到了读事件发生,事实也正是这样,此时该函数返回值event 一直是1,即监听线程被“阻塞”在读状态,导致正常的写操作由于不能被WaitForMultipleObjects“等待到”而无法完成。奇怪的是如果真正有数据达到串口,则写操作可以正常进行。其原因可能是PL2303 芯片本身缺陷造成的,不能够正常识别串口无数据这种状态。这种猜想有待于进一步验证。

这个漏洞是比较隐蔽的。当USB—RS232(PL2303)串口刚好是读写复用时(绝大多数应用场合),不会有任何问题。当读写分开时,就会出现数据不能写入串口的情况。如通过笔记本计算机与Novatel 型号RTKGPS 接收机通信时,由于上位机位置读取串口和差分写入串口是同一个串口,此时用USB—RS232(PL2303)读写串口没有任何问题。但当接收机为“司南”型时,由于上位机位置读取串口和差分写入串口是分开的,此时用USB—RS232(PL2303)就会导致差分数据不能正常写入。

经过分析,这种状态下的读操作事件不存在,因此可以将读操作事件强制设置为无信号状态就能够消除该漏洞。具体方法为在WaitCommEvent函数查询串口状态后添加如下判断:

通过调用WaitForSingleObject 函数检查写事件是否为有信号状态,如果是有信号状态就将读事件强制设为无信号状态。

上述方法实际上是在串口读写事件同时发生时,人为调整写事件的优先级别,使其优先于读事件得到处理。由于计算机写入串口的数据速度非常快,上述操作并不影响读事件的正常响应。对于高速大量的数据读取,其影响需要进一步仔细考虑和实验验证。当然,如果能从USB—RS232 芯片本身出发解决这个问题是更好的办法。

3 结 语

本文介绍了CSerialPort 类的基本情况,详细分析了CSerialPort 类在使用中存在的漏洞及其产生原因,并给出了相应的漏洞修正代码。本文对于推广CSerialPort 类的使用,提高对RS232 串口应用的开发效率具有一定的参考价值。

[1] Telecommunications Industry Association. ANSI/TIA-232-F-1997 Interface Between Data Terminal Equipment and Data Circuit- terminating Equipment Employing Serial Binary Data Interchange[S]. Virgina:Telecommunications Industry Association,1997.

[2] 史培.串口技术应用概览[J].科技资讯,2013(34):6-7.

[3] Wikipedia. USB(Universal Serial Bus)[EB/OL]. (2014-09-23)[2014-09-23].http://en.wikipedia.org/wiki/USB.

[4] 胡家华,徐鹏,郑昌雨,等. PL2303 单片机串口转USB 口实现串口通信[J].单片机与嵌入式系统应用,2013(4):76-81.

[5] 徐民,张博. 基于CP2102/CP2103 的RS232 接口转换为USB 接口的应用设计[J].国外电子元器件,2008(5):15-21.

[6] Remon S. A Communication Class for Serial Port[EB/OL].(2000-02-08)[2014-02-08]. http://codeguru. earthweb.com/network/serialport.html.

[7] 龚建伟,熊光明.串口通信编程实践[M].北京:电子工业出版社,2004.

[8] Microsoft Inc. MSDN. Using Messages and Message Queues[EB/OL].(2014-01-01)[2014-02-08]. http://msdn.microsoft.com/zh-cn/ms644928.

[9] Li Q H. CSerialPort 串口类最新修正版[EB/OL]. (2011-11-06)[2014-01-11]. http://blog. csdn. net/liquanhai/article/details/6941574.

猜你喜欢
监听控件线程
实时操作系统mbedOS 互斥量调度机制剖析
英国风真无线监听耳机新贵 Cambridge Audio(剑桥)Melomania Touch
基于.net的用户定义验证控件的应用分析
千元监听风格Hi-Fi箱新选择 Summer audio A-401
基于国产化环境的线程池模型研究与实现
关于.net控件数组的探讨
网络监听的防范措施
应召反潜时无人机监听航路的规划
计算机中的多线程问题
基于嵌入式MINIGUI控件子类化技术的深入研究与应用