基于多线程技术和自定义消息编程实现Windows 9x异步串行通信

基于多线程技术和自定义消息编程实现Windows 9x异步串行通信

张志明 李蓉艳 王 磊

  要  分析了基于Windows 95/98平台上的异步串行通信程序开发方法,并结合开发实践,用C++Builder语言实现了基于多线程技术和消息响应机制的异步串行通信,给出编程的一般步骤和详细解释。
关键词  串行通信  多线程  消息  API   事件

1  前言

  串行通信具有连接简单、使用灵活方便、数据传递可靠等优点,在工业监控、数据采集和实时控制系统中得到了广泛应用。但由于Windows 95/98对系统底层操作采取了屏蔽的策略,不允许用户对硬件I/O口进行直接操作,进行串行通信只能通过调用API函数来完成;同时Windows 9x通过消息队列驱动管理程序,DOS中断服务例程在其下面也很难实现,且实时性和可靠性都得不到保证;通过基于线程和消息的多任务处理编程可以有效地解决这一问题,且能提高数据传输的吞吐量和应用程序的可靠性。
  Windows 9x支持基于线程的抢先式多任务处理。进程(Process)是应用程序的执行实例,而线程(Thread)则是进程内部执行的路径。每个进程至少有一个主线程,还可包括若干子线程,线程间独立运行。从根本上说,线程是可由系统调度的一个最简单的代码单元,同一进程的每个线程有自己的一组CPU指令、一组CPU寄存器和一个堆栈,由Windows 9x分配CPU时间片,需要小心处理线程的同步问题。基于线程的多任务使得同一程序的两个或多个部分可以同时运行。一个多线程的应用程序实际上在其内部实现了多任务扩展,为代码赋予了并行执行的特性,因而可以执行某些实时性或随机性很强的操作,提高对CPU的利用率,加快通信程序的信息处理速度。
  操作系统在给各个线程分配CPU时间片时,通过其本身的调度机制来评价各个活动线程的优先级,优先执行优先级别高的活动线程,挂起优先级别低的活动线程;当活动线程优先级别相同时,系统调度程序则以轮转方式分配CPU时间片。在抢先式多任务处理中,只要系统调度程序确定有一个优先级别更高的线程准备运行,则系统立刻会将优先级别低的线程挂起(即使处于运行状态),而把CPU时间片分配给优先级别高的线程。
  Windows 9x系统提供的开放式通用功能增强接口Win32 API(应用编程接口)是一个复杂函数、消息的集合。Windows 9x下把对串口和其它通信设备的支持与基本输入输出驱动程序集成为一体,串口的打开、关闭、读取和写入所用的函数与操作文件的函数相同,系统通过被称为设备控制块DCB的数据结构对串行口和串口通信驱动程序进行配置。

2  串行通信的基本编程

  串行通信编程的基本流程如图1所示,首先调用API函数CreateFile( )打开并初始化需要操作的串行端口:

 

1串行通信编成的基本流程

HANDLE CreateFile (LPCTSTR lpFileName,
 /*要打开的通信串口名称*/
DWORD wDesiredAccess,
 /*指定串口的访问方式,一般设置为可读可写方式*/
DWORD dwShareMode,
 /*指定串口操作的共享模式,串口不能共享,所以只能设置为0*/
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
 /*设置串口的安全属性,由于Win9x不支持安全属性,此项只
  能设置为NULL*/
DWORD dwCreationDistribution,
 /*对于通信串口,创建方式只能为OPEN_EXISTING*/
DWORD dwFlagsAndAttributes,
 /*指定串口属性与标志,设置为FILE_FLAG_OVERLAPPED
 (重叠I/O操作),指定串口以异步方式通信*/
HANDLE hTemplateFile
 /*对于串口通信必须设置为NULL*/
);成功打开串行口后,函数返回串口的句柄。
  串口设备属性的配置由以下API函数完成:SetupComm( )设置串行通信端口的输入和输出缓冲区的大小;通过设备控制块DCB修改和设置串口工作状态的参数,如波特率、数据位、奇偶校验位等通信参数,SetCommState( )将DCB结构中的内容写入串口设置;另外,SetCommTimeouts( )设置串口读写操作的溢出时间。事件驱动I/O设备时用SetCommMask( )设置通信事件句柄,WaitCommEvent( )则用来等待通信事件发生。
  设置工作完成后串行通信可用ReadFile( )对串口进行读操作,WriteFile( )对串口进行写操作。
  串行通信结束时调用函数CloseHandle( )来关闭CreateFile函数返回的串口句柄。
  (注:有关的函数可参考C++ Builder在线帮助手册中的详细资料。)
  多线程的串口I/O通信编程中,将对串口的读、写操作视为同一进程的两个不同任务,创建读线程和写线程分别完成对串口的读、写操作;线程间的协调和同步由事件Event和临界区CriticalSection对象实现;由于异步串行通信事件的随机性和实时性,要求通信线程优先于主线程被处理,所以设置各线程的优先级别如下:读线程的优先级>写线程的优先级>主线程的优先级。

3  C++ Builder 3.0/4.0 对多线程编程的支持

  (1)直接使用Win32 SDK中提供的API函数。如CreateThread,SetThreadPriority,ResumeThread,ExitThread等函数,编程较复杂,编程工作量大。
  (2)利用C++ Builder 3.0/4.0 提供的TThread线程对象类。
  线程对象TThread类封装了多线程编程的常用方法和数据,大大简化了多线程编程的工作难度,编程时只要从TThread派生自己的对象类并创建相应的实例即可,常用的重要属性/方法有:
  FreeOnTerminate属性:布尔类型,当设置为True时,在线程结束运行时会强迫TThread对象类自动释放本身,设置为False时,需要显式地在程序代码中调用该对象类的析构函数来释放其本身。缺省设置为False。
  Priority属性:设置该线程的优先级别,C++ Builder定义了枚举类型TThreadPriority (tpIdle,tpLowest,tpLower,tpNormal,tpHigher, tpHighest,tpTimeCritical)来表示线程的优先级,缺省设置为tpNormal。
  Terminated属性:布尔类型,线程结束标志。值为True时表示线程结束,值为False时表示线程正在运行。需要在Execute函数中不断对其检查,为True则结束线程。
  _fastcall TThread(bool CreateSuspended):对象类的构造函数,如果参数CreateSuspended为False,线程创建后自动调用Execute( )方法函数,立即启动运行;如果CreateSuspended为True,线程以挂起状态启动。
  virtual void_fastcall Execute(void):成员函数,创建线程的执行代码部分,在派生类中必须重载该方法函数,在函数体内加入自己的程序代码,实现具体的功能。
  void_fastcall Synchronize (TThread Method&Method):同步方法。执行时挂起线程本身而将控制权交给主线程,由主线程调用参数Method所指定的函数或过程,完成后控制权交还给调用Synchronize方法的线程。Synchronize方法能避免多线程之间的冲突,防止程序“死锁”。
  void_fastcall Terminate(void):调用此成员方法函数将终止线程的运行,并且自动置线程结束标志Terminated值为True。

4  多线程编程实现异步串行通信

  利用Windows 9x的多线程编程技术,编程创建辅助线程实时监视串口通信状态,并由串口通信监视线程根据通信状态向主线程发送相应的消息,由主线程分析处理。多线程串行通信法的最大优点是程序对接收数据具有自主觉察能力,一旦辅助的通信监视线程查询到数据已经发送到串行口上,辅助线程自动接收数据后,向主线程发送数据接收到的消息,应用程序可根据该消息来处理通信串口传送过来的数据,并且采用通信监视线程还不占用CPU时间。
  实践编程中从线程对象TThread类派生建立辅线程串口读线程(TCommReadThread)和串口写线程(TCommWriteThread),完成串口通信操作,分别用来监视和管理串口通信的输入、输出。其中读线程从通信串口读取数据并传输给主线程处理;写线程将从主线程传来的数据写入通信串口输出;主线程除完成串口通信资源的打开、参数配置以及关闭的工作外,还要完成读/写线程的创建及关闭、多线程的协调、数据的中间处理与前端的人机交互等工作。
  图2、图3、图4分别为程序结构流程图、串口写线程流程图和串口读线程流程图。


图2异步串行通信程序流程    图3写线程流程图

 


图4读线程流程图

  针对串行通信的特点,采用事件(Event)对象和临界区(CriticalSection)对象来同步串行通信中各线程对通信端口和存储区数据的访问,避免引起多线程间的冲突和死锁。事件对象的作用是告诉其它线程发生了某一特定事件。相关的API函数有CreateEvent( )创建事件对象,调用成功后用SetEvent( )和ResetEvent( )手工重置事件对象状态,CloseHandle( )解除事件对象;WaitForSingleObject( )和WaitForMultipleObjects( )函数分别等待一个或多个特定事件的发生。临界区对象的作用是保护主线程与读/写线程之间的共享数据,一次只允许一个线程有权访问被保护的数据。InitializeCriticalSection( )初始化临界区对象,DeleteCriticalSection( )删除临界区对象并释放其所占内存,EnterCriticalSection( )和LeaveCriticalSection( )分别是进入和退出数据保护状态。
  线程结构结合各个对象含义具体解释如下:
  (1)发送数据完成事件:写线程建立并等待该事件发生。该事件由写线程将主线程传递过来的数据从通信串口发送成功后产生。
  (2)读取数据完成事件:读线程建立并等待该事件发生。读线程监测通信串口状态,从通信串口成功读取数据并将数据送往主线程后发生。
  (3)通信过程错误事件:读线程建立并等待该事件发生。应用程序利用此事件监视通信过程中发生的错误事件,为此目的需要在串口参数设置时在函数BOOL SetCommMask(HANDLE hFile, DWORD dwEvt Mask)中设置hFile为串口句柄,dwEvtMask=EV_ERR常量。
  (4)写线程/读线程结束事件:分别在写线程/读线程的构造函数中建立并等待该事件发生。在线程执行期间调用Terminate( )函数终止线程时产生。
  (5)临界区对象:主线程和写线程分别定义并建立,用来同步写线程与主线程共享的串口发送数据。
  写线程等待写数据完成事件和线程结束事件;读线程等待读数据完成事件、通信错误事件和线程结束事件。为实现进程内部即主线程与读/写线程之间的数据交换,自定义如下消息在相应的事件发生后发送给主线程作相应的消息处理。
  (1)WM_COMM_WRITE:写线程在成功发送数据后发送给主线程。
  (2)WM_COMM_READ:读线程在接收串口数据成功后向主线程发送,主线程收到此消息后可对接收到的数据进行后续处理。
  (3)WM_COMM_ERROR:由读/写线程发送。其中lParam未用,wParam用于标志消息的发送者,写线程为0,读线程为1。读/写线程在通信过程中发生错误时发送此消息给主线程,主线程挂起相应线程直至通信错误事件处理完毕。
  主线程在运行时与写线程共享串行端口发送数据内存单元,其共享的数据由CriticalSection临界区对象来保护。写线程在对共享数据进行访问前先申请对临界区的所有权,访问完成后释放所有权退出临界区状态,主线程在此期间不能修改共享数据,只能等待写线程临界区状态结束后才可访问共享数据;同样,主线程修改串口发送数据时也由主线程临界区对象加以保护。

5  结束语

  本文已成功地应用于智能大厦监控系统电子密码门锁网络监控分系统的实践中。实践证明,多线程编程实现串行通信对于近距离的RS232接口通信和远距离的RS485接口通信都能取得良好的效果。

张志明(西北工业大学电子信息工程学院  西安 710072)
李蓉艳(西北工业大学电子信息工程学院  西安 710072)
王磊(同济大学  上海 200092)

参考文献

1,郝  杰, 崔晓东, 龚  惠等译. BORLAND C++ BUILDER编程指南. 北京: 电子工业出版社, 1998
2,Borland C++ Builder 3.0/4.0 Online Help

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值