转:http://blog.csdn.net/zcube/article/details/8591972
可到http://download.csdn.net/detail/zcube/5079651下载排版好的PDF版本。
原文链接:http://msdn.microsoft.com/en-us/library/ms810467.aspx
艾伦戴夫
微软Windows开发者支持中心
1995/12/11
利用于:
摘要:学习Win32中的串口通讯和16位Windows操作系统中的串口通讯有很大的不同。这篇文章假定读者已熟习Win32下多线程和同步的基本原理。另外,如果对Win32中的heap功能如果有基础的了解,将使读者在完全理解这篇文章中提到的多线程TTY(MTTTY)例子的内存管理方法上是很有用的。
Win32中的串口通讯和16位Windows中的串口通讯有显著的不同。那些熟习16位串口通讯函数的开发人员将不能不重新学习许多系统部份的知识,以便能编写正确的串口通讯程序。这篇文章将帮助实现这个目标。那些不熟习串口通讯的人员将发现这篇文章会为他们以后研究发展奠定坚实的基础。
这篇文章假定读者已熟习Win32下多线程和同步的基本原理。另外,如果对Win32中的heap功能如果有基础的了解,将使读者在完全理解这篇文章中提到的MTTTY例子的内存管理方法上是很有用的。
关于这些函数的更多信息,请查阅平台SDK文档:微软Win32知识库或微软开发者联机文库。虽然那些控制用户界面特性的利用程序接口(APIs)和对话框在这里其实不讨论,但是对完全理解这篇文章所提供的例程还是很有用的。不熟习1般的Windows编程的读者在开始处理串行通讯前首先应当学习1些Windows编程基础。换句话说,冒失地潜水前先沾湿你的脚。
这篇文章主要介绍利用程序接口(APIs)和微软Windows NT和Windows 95所兼容的方法。因此,只讨论在NT和95这两个平台上都被支持的APIs。Windows 95支持Win32电话API(TAPI),但是Windows NT3.x却不支持。因此,这里不对TAPI进行讨论。但是,TAPI值得1提的时,它在调制解调器的连接和调用控制上是非常好的工具。如果1个利用程序产品触及调制解调器工作和电话拨号,那末利用TAPI接口可以实现这些功能。它允许和用户可能有的TAPI程序实现无缝结合。另外,这篇文章也不讨论Win32中的1些配置函数,像GetCommProperties。
这篇文章包括的例子,MTTTY:多线程TTY(4918.exe),实现了许多这里所要讨论的功能。在它的实现中使用了3个线程:1个用户界面线程实现内存管理、1个写线程实现控制所有的写操作、还有1个读/状态线程实现读数据和处理端口上产生改变的状态。该例子采取1些不同的数据堆实现内存管理。它也广泛使用同步方法增进线程之间的通讯。
使用CreateFile函数可以打开1个通讯端口。调用CreateFile打开通讯端口有两种方式:堆叠的和非堆叠的。下面是使用堆叠方式打开1个通讯资源的例子:
移除CreateFile中的FILE_FLAG_OVERLAPPED标志可指定为非堆叠方式。下1章节将对堆叠和非堆叠方式进行讨论。
在Win32软件开发工具包(SDK)程序员参考手册(概述,窗口管理,系统服务)中规定,当打开1个通讯端口时候,调用CreateFile有以下要求:
需要注意1件事,惯例上它们有4个端口分别为:COM1、COM2、COM3和COM4。Win32 API没有提供任何途径去肯定系统中存在的端口。Windows NT和Windows 95在配置串口方面相互其实不相同,所以任何1种方法都不能确保对所有的Win 32平台都是可移植的。1些系统乃至有比惯例上的最大数量4个端口还要多的端口。硬件厂商和串行装备驱动的作者可以用他们所喜欢的方式去自由命名端口。为此,如果用户可以去指定他们想用的端口名是最好的选择。如果1个端口不存在,在企图打开这个端口的时候1个毛病(ERROR_FILE_NOT_FOUND)将会出现,应当正告用户这个端口是不可用的。
从通讯端口读和写在Win32中极为类似于Win32中文件的输入/输出(I/O)。实际上,实现文件I/O的函数和用于串行I/O的函数是相同的。Win32中的I/O可以通过两种方式被使用:堆叠和非堆叠。在Win32 SDK文档中用异步和同步这样的术语去暗示这些I/O操作的类型。但是,这篇文章中将用堆叠和非堆叠这样的术语。
非堆叠I/O对大多数开发者来讲是熟习的,由于这属于传统的I/O操作情势,当函数返回的时候1个被要求的操作将被假定为已完成。就堆叠I/O来讲,即便操作没有完成系统也能够立刻返回给调用者,当操作完成的时候将会用信号通知调用者。程序可以利用I/O要求和结束这段时间去履行1些“后台”工作。
在Win32中和16位Windows中对串行通讯端口的读和写有显著的不同。16位Windows只有ReadComm和WriteComm函数。Win32中的读写操作可能牵涉更多的函数和选择。这些问题在下文将会被讨论。
非堆叠I/O非常简单,虽然它还有1些限制。1个操作的履行将致使调用它的线程被阻塞。1旦该操作完成,函数返回,线程继续工作。这类类型的I/O对多线程利用程序来讲是非常有用的,由于当I/O操作的时候即便1个线程被阻塞,其它线程依然可以履行工作。利用程序有责任正确无误的处理连续的端口操作。如果1个线程被阻塞去等待I/O操作的完成,随后的其它线程如果调用1个通讯API也将可能被阻塞,直到最初的操作完成。例如,如果1个线程正在等待ReadFile函数返回,其它线程如果调用WriteFile函数将会被阻塞。
在非堆叠和堆叠操作之间做出选择的诸多因素中,其中之1是要斟酌到可移植性。堆叠操作不是1个好的选择,由于大多数操作系统其实不支持它。但是大多数操作系统支持多线程,所以多线程的非堆叠I/O操作从可移植性上面斟酌的话是最好的选择。
堆叠I/O不像非堆叠I/O那样简单的,但是提供了更多的灵活性和效力。当1个端口打开的时候,对堆叠操作来讲,允许线程与此同时履行I/O操作和其它的工作,即便这个操作正处于不肯定状态。另外,堆叠操作允许单线程发出许多不同的要求和履行后台工作,即便这个操作处于不肯定状态。
对单线程和多线程利用程序,在发出要求和得到结果之间必须产生1些同步性。1个线程将会被阻塞,直到1个操作的结果变成有效的。堆叠I/O的优势所在是允许1个线程在要求和完成之间去做1些工作。如果没有工作可以被做,然后对堆叠I/O只有1种可能,就是它将允许为更好的用户提供响应。
堆叠I/O是MTTTY例子中所使用的1种操作类型。它创建1个线程来负责读取端口的数据和状态。它也履行定期的后台工作。程序创建另外1个线程专门用来从端口写出数据。
注意:有时利用程序创建太多的线程,滥用多线程操作系统。虽然利用多线程可以解决很多困难的问题,但是创建过量的线程在利用程序中其实不是最有效的方式。在系统中线程没有进程紧张,但是依然会占用系统资源,像CPU时间和内存。如果1个利用程序创建过量的线程,可能对全部系统的性能产生不利的影响。线程的1个更好的使用方式是对每一个工作类型创建1个不同的要求队列,有1个工作者线程通过发出1个I/O要求使其进入要求队列。上述方法将会被这篇文章中所提到的MTTTY这个例子用到。
1个堆叠I/O操作包括两部份:创建操作和检测是不是完成。创建操作必须建立1个OVERLAPPED结构体,为同步创建1个手工重置事件,然后在调用特定的函数(ReadFile或WriteFile)。I/O操作可能也可能不会立即的完成。对1个程序来讲,如果假定1个堆叠操作要求总是产生1个堆叠操作是毛病的。如果1个操作完成后,利用程序需要准备继续正常地运行。堆叠操作的第2部份是检测它是不是完成。检测操作是不是完成包括等待事件处理,检查堆叠结果和处理数据。有很多工作牵涉到堆叠操作的缘由是存在很多故障点。如果1个非堆叠操作失败了,函数只是会返回1个毛病返回的结果。如果1个堆叠操作失败了,它可能在创建操作时候失败或是使操作处于等待状态。你也可能有1个超时操作或只是1个超时去等待操作完成的信号。
ReadFile函数将产生1个读的操作。ReadFileEx也产生1个读操作,但是由于它在Windows 95上是不可用的,所以在这篇文章中不对它做讨论。这里的代码段详细说明了怎样产生1个读操作。注意,如果ReadFile函数返回TRUE,它的功能是将调用1个函数去处理数据。如果操作变成了堆叠方式,这个处理数据的函数也是1样的。注意在代码段中定义的fWaitingOnRead标记变量,用它来指明1个读操作是不是为堆叠的。它通经常使用来避免在1个读操作还没有完成的时候又重新创建1个新的读操作。
堆叠操作的第2部份是检测它是不是完成。OVERLAPPED结构体中的事件句柄会被传递到WaitForSingleObject函数中进行等待,直到对象被传递信号。1旦事件被传递信号,则代表操作完成了。这其实不意味着操作是被成功地完成,仅仅是完成而已。GetOverlappedResult函数将报告操作的结果。如果产生了毛病,GetOverlappedResult函数将返回FALSE,GetLastError函数将返回毛病代码。如果操作被成功的完成,GetOverlappedResult将返回TRUE。
注意:GetOverlappedResult函数可以探测操作是不是完成,也能够返回操作的失败状态。如果操作没有完成,GetOverlappedResult将返回FALSE且GetLastError函数将返回ERROR_IO_INCOMPLETE。另外,GetOverlappedResult可能会被阻塞直到操作完成。实际上,堆叠操作转变成非堆叠操作可以通过给GetOverlappedResult函数中的bWait参数传递TRUE来实现。
这里的代码段展现了1种检查1个堆叠读操作是不是完成的方法。注意下面的代码也调用的处理数据函数和上面是相同的,该函数在操作完成后立即调用。也要注意使用的fWaitingOnRead标记,在这里它用来控制是不是履行检测代码,由于它只有在1个读操作还没有完成时候才应当被调用。