您的位置:首页 > 其它

使用P/Invoke来开发用于与串行设备通讯的.NET基类

2006-11-17 15:34 260 查看
转:使用P/Invoke来开发用于与串行设备通讯的.NET基类
/article/7079453.html?Pending=true#Post
本文相关代码下载:NetSerialComm.exe (89KB)

导引:在.NET环境下编写与RS252串口通信的应用程序的唯一方法,就是引用过时了的并且有点限制的MSComm ActiveX控件。这篇文章介绍了用C#安全代码编写一个多线程的,且时尚的与RS232通讯的基础类库。这个类库使用平台调用服务(即Platform Invocation Services)来与Win32 API直接交互。程序员可以通过继承在任何.NET语言下使用这个类库;这个文章还探讨了一些用C#和Visual Basic .NET写的示例程序。

微软.NET框架类库(FCL)提供了相当全面广泛的功能来替代在Win32@API编程下原有的功能,特别是C#与Visual Basic@.NET语言的可互访性。尽管如此,RS232串口通讯是.NET框架类库是明显未被涉及的方面之一。从而很正常的,很多人就把这些接口当成了遗弃物。目前,你还是通过软件层与串行调制解调器进行通讯,比如TAPI与PPP。其它从前使用这些接口的设备现在正在向USB接口移植。不过,一些专业的RS232设备的驱动程序仍然有通讯的需要,比如GPS接收器,barcode and swipe card readers,可编程控制器和一些可预见的程序员将来继续使用的设备。(关于RS232接口的规格信息,可以参看"Hardware Specs".)

平台调用服务 (P/Invoke)是能够使用托管的CLR代码调用非托管DLLs的.NET技术,包括那些实现Win32 API的DLLs。在这篇文章里,我将用C#把与RS232通信的API封装到CLR托管的代码中去。生成的基类库将使用.NET语言开发特定设备的驱动变得相对容易。完整的代码和示例可以从这篇文章顶部的链接中下载到。

设计原理

在把Win32串口通讯功能封装到托管类的时候,这里至少有四种实现方式让你选择:
1.使用P/Invoke把API函数、常数、结构作为静态成员封装到托管类中。虽然我在里面使用了这种方法,但没有这个类暴露给程序员。
2.写一个流的处理角色。这是.NET框架对文件、控制台、网络通讯的一般地、可扩充的提取。咋一看,这个很有吸引力,但近距离审视的时候,这个更适用于传统的调制解调器,而不适合于现在基于命令响应语法的设备。
3.做一个直接替换MSComm OLE Control Extension(OCX)控件的替代品。换句话说,新建一个封装了API文件处理并提供许多基本的方法和事件(比如,Open,Close,Read,Write等等)。你可以在应用程序类里初始化这个类库里的一个对象来达到重用的目的――那就是说,通过COM-style的集合。
4.写一个应用程序需要继承的基类。这是一个充分体现.NET优点――运行时对不同语言继承的无关性――的面向对象的方法。这些基础的方法被继承进应用程序对象中,虚方法将被使用,而不是使用事件。这个应用程序对象将巧妙地提供一个适用于真实RS232设备公共接口(比如,一个GPS接收器驱动可能拥有一些关于经度和纬度的公共属性)。

我将采用第四种方法。这个类库将会包含两个被生明为抽象类的基类(它们不能被示例化),但我将使用继承来把它们作为实现某些特定应用的基类。图1表明了这种继承的层次关系。

图6接收线程的简单版

图6是一个简化版的ReceiveThread。SetCommMask表明当一个新字节到达的时候我希望被通知到。WaitCommEvent可能返回true,在这种情况下已经有一个或更多的字节处于队列中。如果返回附带错误码的ERROR_IO_PENDING,我会挂起这个线程直到有一个字节到达。被传递给WaitCommEvent的OVERLAPPED结构包含一个到AutoResetEvent的句柄,它被当作有字节到达的信号。当我执行AutoResetEvent的WaitOne方法时,执行被挂起直到这个事件被触发。

不管WaitCommEvent立即返回true还是信号不久完成,eventMask变量有一位用于标识SetCommMask正常被引发所需的条件(在实际代码中,我也描述了一些其它的家务管理的条件)。

注意我同样为eventMask使用了手动排列技巧就像先前在OVERLAPPED中描述的一样。我猜测这也许是没必要的,也许自动排列也可以,但在文档中没有精确的描述,因为这样做更安全一些总比遗憾要好的多。用托管变量替换无序的指针作为引用参数好像有用,但那可能只是因为内存没有被重用而已。因为依赖于时间,不只是一个字符要排队,因此每次使用ReadFile来排出一个字节,重复使用,并每次使用一个字符来调用虚方法OnRxChar。当我接收到ERROR_IO_PENDING错误编码时,我就调用CancelIo方法,从而避免等在这里;我想在WaitCommEvent循环等待。

在使用工作线程,错误处理和异常需要好好地处理。任何发生在ReceiveThread里的未处理异常,及任何调用它的虚方法,以及由以上调用的方法或引发的事件将会级联下去并由catch子句捕获处理。如果产生的异常不是ThreadAbortException异常,那么它就存储在CommBase类作为一个私有成员,并且这个线程将被中止。下次在主线程里程序代码将调用一个方法然后再引发一个异常,端口就会关闭。这充分利用了内置异常结构的优点,当引发了一个一般性“接收线程错误”异常时,它里面就包含了存储在里面的原线程。ThrowException是继承类里的提供的一个辅助方法;它通过它所调用的线程来调节它的行为。

配置和其它细节

我从CommBaseSettings辅助类对象读取所有配置。Open方法通过调用虚方法CommSettings来获得这个对象,并把所有的置拷贝到API结构中去。CommBaseSettings类还提供了用于保存和覆盖配置到XML配置文件的方法,及大量地应用这些一般性配置。我利用窗口上的智能帮助提示为配置提供了帮助文档。因为继承类提供了自己的继承自CommBaseSettings类配置类,这种配置提供了一种可扩展性基础配置结构。通过这种方式我继承了CommLineSettings类,为CommLine类提供了额外的配置。

共有三个API方法用于配置通讯协议:SetupComm,SetCommState及SetCommTimeouts三个方法。SetupComm方法需要接收缓冲的大小及传输队列。正常情况下你可以把这些设置为0或根据操作系统决定,但对一些文件传输和简单应用程序,可调节的所需的大小是值得的。系统不一定能满足这种需要;在Windowns XP里,这就好像动态传输队列,并仅接收队列的长度是必须的。SetCommState方法在一个称为设备控制块(DCB)的结构里提供了波特传输率、字格式及握手设置等配置信息。

SetCommTimeouts在COMMTIMEOUTS结构里提供了三个接收和两个传输超时值。接收超时值对我所选择的设计没有用处,因为单个字符是异步处理的。如果接收超时时间是必须的,那它必须在一个高的水平上实现(比如,CommLine为它的传输方法提供了一个超时时间)。传输超时时间很有用,特别对于多字节传输。Send方法里的字节的数量由sendTimeoutMultiplier方法啬,然后sendTimeoutConstant被附加到这并提供以微秒为单位的总时间。

一旦端口被打开并被寝化里,Open方法调用了一个AfterOpen虚方法,它将被重写来检查到远程设置的连接状态且尽可能地配置它。如果这个返回false,端口将再次被关闭且Open方法自己将返回false.如果需要的话,还有一个BeforeClose方法来关闭远程设备。

CommB ase还提供了两个重载版本的Send方法,一个提供了一个字节数组为参数另一个提供了另一个单独的字节作为参数。CommLine提供了第三个版本的Send方法,用字符串作为参数。在进行合适的数据转化后,所有这些最终使用字节数组版本的方法。还提供了一个以单个字节为参数的SendImmediate方法。它将在传输队列里将比其它字节前面传输这个字节,并且对实现自定义流控制模式是非常有用的。它还提供了一些用于传输请求、数据终端准备输出插脚及把TX输出到暂停条件。输入插脚-Clear-to-Send(CTS),Data Set Ready(DSR),Received Line Signal Detector(RLSD),以及Ring Detect-可以使用GetModemStatus来直接读,当任意输入或输出插脚改变状态时,虚方法OnStatusChange将被调用。

GetQueuesStatus方法将返回一个QueueStatus对象,并给出传输的大小、内容、接收队列以及如果必要的话,流控制条件现在是块传输。

结论

我使用Platform Invocation Services来填补了FCL功能上的一个空白。但这表明确是一个不平凡但非常可行的锻炼。所存在的绝对多数困难在于还没有用于P/Invoke的完全的工具及文档支持。

最后,我做一下总结。作为这个项目的一部分,并在我考虑ManualResetEvent和AutoResetEvent框架类已经封装了所有我需要的功能之前,我写了和测试了所有的对于Win32 Waitable Events API的完全封装。记住:当时候,你仅需要把你所有的时间都花在写全新的类而不是凑合使用已经存在的你所需的东西。在从新改造之前先检查一下你的硬盘。基于这个原理,我希望这些基类能帮助其它的程序员把RS232设备通讯带入.NET世界。

相关文章请见:
House of COM: ating Native Code to the .NET CLR
Serial Communications Overview
RS232 standard

背景知识请见:

NET and COM: The Complete Interoperability Guide by Adam Nathan (Sams Publishing, 2002)

John Hind是一位位于英国伦敦的自由作者和顾问。他专注于微控制应用程序和控制解决方案。可以通过John.Hind@zen.co.uk和他联系。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: