终于翻译完了,下次总结,先贴出来!

SERIAL COMM
Use P/Invoke to Develop a .NET Base Class Library for Serial Device Communications

SUMMARY


Out-of-the-box, the only way of coding RS232 serial communications applications in the .NET environment is to import the outdated and somewhat limited MSComm ActiveX control. This article describes the development of a lean, multithreaded, and modern RS232 base class library in C# managed code. The library uses Platform Invocation Services to interact with the Win32 API directly. Application programmers can use the library from any .NET language through inheritance; the article explores examples written in C# and Visual Basic .NET.
he Microsoft® .NET Framework Class Library (FCL) provides reasonably comprehensive coverage of the functionality of the underlying Win32® API, greatly contributing to the sophistication of the C# and Visual Basic® .NET languages. However, RS232 serial communications is one area that is conspicuously absent from the library. To be fair, most people probably now regard these ports as legacy baggage. These days, you interact with serial modems via software layers such as TAPI or PPP. Other devices that once used these ports are now migrating to USB. Nevertheless, the need for device drivers for specialized RS232 devices such as GPS receivers, barcode and swipe card readers, programmable controllers, and device programmers will continue for the foreseeable future. (For RS232 port specs, see the sidebar "Hardware Specs".)
Platform Invocation Services (P/Invoke) is the .NET technology that enables managed code in the common language runtime (CLR) to make calls into unmanaged DLLs, including those that implement the Win32 API. In this article, I will wrap the API functions provided for RS232 communications in CLR managed classes using C#. The resultant base class library will make it relatively easy to develop drivers for specific devices using any .NET language. Full source code for the library and examples is available for download from the link at the top of this article.


设计原则


当要把Win32串口通信函数包装的时候至少有4种可以考虑的选择:
1. 使用P/Invoke 来包装API函数、常数和结构定义,把它们作为可管理类中的静态成员,我在内部是这么做的,没有将这些类暴露给应用的编程者。
2. 写一个流处句柄,这是一个Framework用于文件、控制台和网络通信的,广义的、可扩展的抽象。第一次看上去,这很吸引人,可是仔细检查,发现这种方式更适合传统的Modem的通信而不是对现代的应答式的串口设备的通信。
3. 直接创建一个MSComm 控件的替代品。换句话说,创建一个类,包装了API文件的处理并提供相当广泛的方法和事件(打开、关闭、读和写等等)。您可以实例化类库的对象,这样重新使用它们,也就是以聚合的方式,COM风格来使用。
4. 写一个基类让应用来继承它,这是真正的面向对象的方式,这样可以非常棒地使用.NET的语言独立的运行时继承特性。丰富的方法可以被继承到应用的对象中,使用虚方法而不是事件。应用对象可以根据实际的RS232设备(例如,GPS接收器的驱动也许具有经度和纬度的公开属性)提供公开的接口。
我将采用第四种方法。库中将包含2个声明为抽象的基础类(它们是无法实例化的),然而我将使用它们作为基类,通过继承在应用中创建特定的类,图一显示了继承关系。


[无法插入图片,不知道为什么] 
图一

第一个类,CommBase关于数据格式没有做任何假设,仅仅提供了打开和关闭端口,发送和接收数据字节以及与输入和输出控制相关的交互。
第二个库中的类,CommLine,是继承自CommBase的并且作了2个假设,第一、发送和接收的字节为ASCII,第二、保留的ASCII的控制码作为不定长的数据的结束的标志,确保数据的接收和发送。当然,这个模式是可扩展的,比如,您可以写一个CommLine的替代版本来处理Unicode的通信。[这一段话已经很明白了,说的是CommLine是一个更贴近应用的类,我们可以学习,来改进这个类。我看了一下库的结构发现从这个角度看还是比较清晰的,一个基类,一个派生类,一个异常类,那个乒乓类是干什么还不清楚。一个内部类(internal)Win32Com]


使用内部类
2个示例应用, BaseTerm和LineTerm可以提供下载。这些是一般目的上的终端模拟器,允许任何类型的串口设备,我将从一个用户的角度来简要介绍一下BaseTerm,然后对LineTerm给出详细的分析。
 

[无法插入图片,不知道为什么] 
图二 BaseTerm
BaseTerm (参考图二) 是一个完全的Windows的基于窗体的应用,它是从ComeBase继承而来的,提供面向字节的诊断终端。设置按钮可以调出一个窗体,该窗体能够编辑所有的通信设置(参考图3)。
 

[无法插入图片,不知道为什么] 
图3
该窗体的菜单允许用户用XML格式的文件保存和调用设置,包括一组通用的流控制的预设值。快速提示解释了每一项设置的用法。当保存为XML后,您可以在下次启动程序的时候在命令行中指定这个文件。当连接在线的时候,键入的字符被立即发送到远端的设备。键盘上的键发送相应的ASCII字节,如果您想发送非键盘代码,则使用转义(escape)功能。

为了启动转义,键入左尖括号(<)字符,然后键入一个ASCII控制码或者在0到255之间的一个十进制的数字,要结束一个转义,键入>字符,这导致对应的ASCII立即被发送。<字符如果输入2次可以被发送[就是说,要想发送<字符本身,则输入<<就可以]。在设置对话框中,您可以在“Xon Code”的下拉列表中看到有效的ASCII控制符名称的列表。像http://www.asciitable.com/ 的网站也会提供更多的有用信息。在终端窗体中更大一点的文本框不附加任何解释地显示所有接收到的ASCII或者十六进制字节。
可以使用显示设置来将分断接收到的行,分断方式为接收到特定数量的字符或者特定的某一个字符的时候。[这一段讲的是显示设置,以前没有注意过]状态按钮显示一个传输或者接收队列的状态报告。
LineTerm使用CommLine作为它的基类,并演示了如何在源代码中使用库。您需要在Visual Studio 的IDE中运行它,因为没有任何用于设置的用户界面。在Visual Studio .NET,创建一个新的Visual Basic控制台应用。从工程中删除那些默认的模块[指的是那些自动生成的模块文件]。将LineTerm.vb,CommBase.dll和CommBase.xml拷贝到工程目录中(XML文件为库提供了IntelliSense信息)。在工程管理器中使用“添加已存在项目……”导入LineTerm.vb 并且增加到CommBase.dll的引用。现在您可以编译并运行这个工程了。
图4显示了这个例子的完全的源代码[我将图中的代码转换为C#的代码了,关于其中的继承关系还不是很明白,另外还有一点不明白的是设备返回的数据没有获得,为什么要研究这个例子?因为它没有界面,对于我需要的更有实际意义]。在第一行,我为库导入了它的名称空间[注意这里的说法,为一个库导入名称空间]。然后我创建了一个新的类,LineTerm,这个类继承自CommLine[注意,它继承自哪里这是一个中心问题]。它[经分析,这里指的是CommLine,而不是其派生的类]提供了公开的方法Open和Close(实际继承自CommBase),也提供了受保护的方法Send,我用SendCommand将这个方法公开[也就是说,继承类可以使用其父类中的受保护方法,并通过一个公开的方法来使用它的功能。]在我的新的类中,我对基类中的若干虚方法进行了重载。CommSettings方法由Open方法调用以配置通信端口,它必须返回一个初始化之后的CommSettings的对象。
实际上,我在这里使用了CommLineSettings,它更好,因为它继承自CommBaseSettings。在这个函数的最后2行中,我首先将对象传递给了Setup方法,该方法继承自CommLine,然后将它返回给CommBase。所有的设置都是公开的成员,并且可以被直接设置。然而,还有一个帮助方法,称为SetStandard,该方法可以为CommBase的大多数通用设置做自动配置。您可能需要为该方法[指的是SetStandard方法,该方法有几个参数可用于设置最常用的参数]、终止符以及过滤符成员编辑参数以满足你用于测试的设备的实际情况。
应用的主方法就是创建一个我的类的实例,引发Open方法,然后提供一种通过控制台发送字符串并接收字符串的功能。有2种方法来完成此事儿:阻塞和非阻塞。使用SendCommand开始非阻塞通信。此方法立即返回,然后过一会儿发送完成,OnTxDone会给出一个报告。

同样过一会儿,当远程设备端发送了一个完成得响应行的时候,OnRxLine将会把它显示在控制台上。[我奇怪了几天了,看函数的名称,这个方法应该是能够返回的,但是怎么跟踪都没有发现有被调用到的迹象,但是根据这一段说明,我重载了OnRxChar,发现,这我重载的方法是可以引发的,这证明了我的想法是对的,然而能否使用更好的方法,比如写一个什么方法能够对特定的标志引发事件,让它分辨是哪里来的返回,并将返回的数据放到它需要去的地方,我对这个方法感兴趣的地方在于它的机理而不是这个特定的方法],在这段时间,主要的程序等待用户的输入,然而它能够同时执行其它工作[这一句话翻译的不太准确]。如果你注释掉SendCommand同时启用TransactCommand,执行的将是阻塞的通信方式。这里,主程序等待,直到有了反馈。[可是我在这里测试这个方法,一直报告超时,我还没有找到原因]这里,主程序将在[远端设备]回应之前阻塞在这一行[也就是那一句代码]。您将还能够看到来自OnTxDone的消息,但是您看到的将不是来自OnRxLine的“接收到”这个信息,而是看到来自TransactCommand的响应的消息。

[无法插入图片,不知道为什么]  
图5,GPS的流控制
在实际应用中,例如GPS接受器的驱动,您不要像我在示例中那样简单地将Send和Transact方法公开。实际做法是,您需要为设备的实际功能(例如,像速度和高度属性,或者像PositionChanged这样的事件)公开方法和属性。这些方法将聚合需要的命令,使用Transcat方法然后解析反馈以抽取其中的返回值。图5显示了这样的设备驱动。
发送Sending

在日常生活中大量的串口通信中,发送信息要比接收信息要容易得多。对于接收,你对远端的设备仅仅是一种无望,然而对于发送,你还可以控制时间。然而于串口的典型的2-20000的波特率,与计算机G赫兹范围内的[速度]相比较,你不可能希望把后脚跟等凉[可能是一个短语]来等待传送的完成。Win32的API将串口通信看作一种特殊方式的文件操作,这种文件的操作使用一种称为重叠[overlapped]I/O技术来提供非阻塞操作。

CommBase类提供了公开的Open方法,该方法使用Win32 API的函数CreateFile来打开串口然后将操作的系统句柄作为私有成员变量存储:
hPort = Win32Com.CreateFile(cs.port,
   Win32Com.GENERIC_READ | Win32Com.GENERIC_WRITE, 0, IntPtr.Zero,
   Win32Com.OPEN_EXISTING, Win32Com.FILE_FLAG_OVERLAPPED,
   IntPtr.Zero);
 
第一个参数是一个字符串的端口名称,通常是COM1:或者COM2:,然而理论上,名称可以是什么都无所谓,因此我使用了字符串而不是一个数字编号。我没有发现一种方法可以列出所有可用的端口,因此我选择让调用者尝试打开任何端口,接受一个事实就是允许它失败[打开端口]。失败也发生在此端口村在但是已经被其它应用占用,在那种情况下,Open返回false。我使用FILE_FLAG_OVERLAPPED来表明在此句柄上的任何操作将是非阻塞的,其他参数都是串口通信的琐碎参数。

Win32Com是一个帮助类[helper class],它作为一个容器来包含我将要用到的API函数、结构和常数的静态定义。CreateFile用以下方是在C#中声明:
[DllImport("kernel32.dll", SetLastError=true)]
internal static extern IntPtr CreateFile(String lpFileName,
   UInt32 dwDesiredAccess, UInt32 dwShareMode,
   IntPtr lpSecurityAttributes, UInt32 dwCreationDisposition,
   UInt32 dwFlagsAndAttributes, IntPtr hTemplateFile);
 
不同的常数也在此处定义例如:
internal const UInt32 FILE_FLAG_OVERLAPPED = 0x40000000;

鉴于当前只有非常少的工具支持平台调用[P/Invoke],我必须手工完成这些定义。 其中关键资源包括Win32的API文档和使用C++是要用到的头文件。(在Visual Studio .NET中提供的出色的查找功能对于在头文件中找到定义没什么用处。我仅处于文档的目的才使用[这些功能];您在完成库的编译时不需要它们。) 关于互操作的编组[interop marshaling]技术,通过该技术处理受管理的数据类型翻译为不受管理的API使用的C定义的讨论已经超出本文的范围。然而,您能够从以下Open方法的代码中看到一些端倪:

wo.Offset = 0;
wo.OffsetHigh = 0;
if (checkSends)
    wo.hEvent = writeEvent.Handle;
else
    wo.hEvent = IntPtr.Zero;
ptrUWO = Marshal.AllocHGlobal(Marshal.SizeOf(wo));
Marshal.StructureToPtr(wo, ptrUWO, true);

这里,wo是一个Win32Com.OVERLAPPED类型的局部变量,ptrUWO是一个IntPtr类型的私有类变量。Marshal是一个System.Runtime.InteropServices中的静态对象,它[不明白指的是Marshal对象还是那个名称空间]提供了对互操作的访问。在这段代码中,我手工完成了在调用函数的时候,通常由marshaler自动完成的工作。第一步是分配一块适当大小的非管理的内存,然后将可管理的结构拷贝到其中,将内存按照需要重新布局。在函数调用之后,marshaler将正常地使用Marshal.PtrToStructure 来执行反向拷贝[也就是将数据从非管理内存拷贝到可管理内存中],然后Marshal.FreeHGlobal释放内存。我手工完成这些是因为那个API使用OVERLAPPED结构的特殊方式。我将在WriteFile调用中指明它,但是操作系统将在调用返回后继续使用它。
稍后,我将调用GetOverlappedResult,再次指明相同的结构。如果我不这样而采用自动marshaling,会出现一个风险就是非管理内存会在2次调用之间重新分配[这个重新分配的含义不是很清楚,是怕内存不想分配2次而分配了2次,还是怕内存2次分配之后的布局发生变化?估计是前者]。这种情况下,unmarshaling就不需要了,因为字段不必2次访问。然而,当端口关闭的时候,内存必须被释放[此处使用的是deallocated]:

if (ptrUWO != IntPtr.Zero) Marshal.FreeHGlobal(ptrUWO);
在此基础上,实际发送一组字节就非常直截了当了:
if (!Win32Com.WriteFile(hPort, tosend, (uint)writeCount,
   out sent, ptrUWO))
if (Marshal.GetLastWin32Error != Win32Com.ERROR_IO_PENDING)
   ThrowException("Unexpected failure");

Tosend参数是一个字节数组的指针;writeCount是数组中的字节个数;sent参数将返回实际已经发送的字节数;最后ptrUWO是一个指向已经创建的非管理版本的OVERLAPPED的指针。通常,函数将返回错误,而且错误代码将是ERROR_IO_PENDING. 这是一个伪错误,它能够指示操作已经排队,因为[操作]不可能立即完成。任何其他的错误代码说明操作不能排队[被排进队列]。使用带有缓冲的串行硬件和短的发送字符串,有可能操作立即完成,这种情况下,函数将返回true。
在发送新数据之前,前一次Send的返回结果一定要检查,将对错误条件和超时的侦测置为许可。(很奇怪,API将一个附加操作看作是一个错误,可是一个超时去看作是正常-检测它的唯一办法是发送的数据要比排进队列的数据少。 摆平这个怪异的现象也是完成给库写包装时的一个乐趣!)虽然我可以允许多重附加[pending]发送,每一个带有自己的OVERLAPPED的结构,但是这会大大增加复杂性。相反,我将后续的Send阻塞,直到前一次发送完成。如果阻塞是一个问题,可以通过将checkAllSends成员设置为false来使它无效[disabled],在这种情况下,OVERLAPPED结构被重新使用而且不保证错误或者超时能够捕获到。
接收

正如您所猜测的那样,接收数据就是简单调用API的ReadFile函数。正如前面提到的,困难之处不仅仅接收如此简单,而在于知道什么时候去接收。为了避免应用程序持续不断的查询[poll]数据,需要使用一些回调机制。从一个工作线程中调用的虚拟方法可以完成这个功能。[非虚拟方法的实现是不会变的:无论是在声明它的类的实例上调用该方法还是在派生类的实例上调用,实现都是相同的。与此相反,一个虚拟方法的实现可以由派生类取代。参考地址:ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.2052/csspec/html/vclrfcsharpspec_10_5_3.htm] CommBase在每一个字节接收到的时候调用一个虚拟方法。此方法在CommLine中被重载用于缓冲字节,并且当接收到以行的时候调用一个不同的虚拟方法。

为了使它工作,我在Open方法中创建了第二个执行线程代码如下:

rxThread = new Thread(new ThreadStart(this.ReceiveThread));
rxThread.Name = "ComBaseRx";
rxThread.Priority = ThreadPriority.AboveNormal;
rxThread.Start;
Thread.Sleep(1);

这开始了一个新的线程,该线程运行私有方法ReceiveThread中的代码。对最后一句话的需要是我惊讶;我设想一个新的,高优先级的线程将在Start命令的一开始就占先。由于某种原因,它不是这样并且会引发一些问题因为工作线程在第一次需要的时候并非一直就绪[不是很清楚,大约是说,调用的时候,不管优先级的高低,它还不能使用]。作为第二次尝试,我使用了文档上建议的Sleep(0),说这样可以不浪费时间直接占先(最多一个毫秒,天哪),可是实际上它一样不能使用。
ReceiveThread是一个无限循环,只有当发生里外的时候才会中断。我使用一下代码在关闭端口的时候终止线程:

rxThread.Abort;

这将从线程中抛出一个ThreadAbortException,导致通过catch子句中断,这样可以进行清理。还可以使用一个finally子句,然而在此种情况下用不用都没什么区别因为通过例外的只有退出例程。[估计是这个意思,就是说反正一旦发生意外就退出了,既然退出了,自然有退出的清理过程,因此安放一个finally子句没有什么实际意义]。
图6[就是下面的一段代码]显示了一个ReceiveThread的简单版本。
private void ReceiveThread() {
  byte[] buf = new Byte[1];
  uint gotbytes;

  AutoResetEvent sg = new AutoResetEvent(false);
  Win32Com.OVERLAPPED ov = new Win32Com.OVERLAPPED();
  IntPtr unmanagedOv = Marshal.AllocHGlobal(Marshal.SizeOf(ov));
  ov.Offset = 0; ov.OffsetHigh = 0;
  ov.hEvent = sg.Handle;
  Marshal.StructureToPtr(ov, unmanagedOv, true);
  uint eventMask = 0;
  IntPtr uMask = Marshal.AllocHGlobal(Marshal.SizeOf(eventMask));
  try
  {
    while(true)
    {
      if (!Win32Com.SetCommMask(hPort, Win32Com.EV_RXCHAR))
      {
        throw new CommPortException("IO Error [001]");
      }
      Marshal.WriteInt32(uMask, 0);
      if (!Win32Com.WaitCommEvent(hPort, uMask, unmanagedOv))
      {
        if (Marshal.GetLastWin32Error() == Win32Com.ERROR_IO_PENDING)
        {
          sg.WaitOne();
        }
        else
        {
          throw new CommPortException("IO Error [002]");
        }
      }
      eventMask = (uint)Marshal.ReadInt32(uMask);
      if ((eventMask & Win32Com.EV_RXCHAR) != 0)
      {
        do
        {
          gotbytes = 0;
          if (!Win32Com.ReadFile(hPort, buf, 1, out gotbytes, unmanagedOv))
          {
            if (Marshal.GetLastWin32Error() == Win32Com.ERROR_IO_PENDING)
            {
              Win32Com.CancelIo(hPort);
              gotbytes = 0;
            }
            else
            {
              throw new CommPortException("IO Error [004]");
            }
          }
          if (gotbytes == 1) OnRxChar(buf[0]);
        }
        while (gotbytes > 0);
 }
    }
  }
  catch (Exception e)
  {
    if (uMask != IntPtr.Zero) Marshal.FreeHGlobal(uMask);
    if (unmanagedOv != IntPtr.Zero) Marshal.FreeHGlobal(unmanagedOv);
    if (!(e is ThreadAbortException))
    {
      rxException = e;
      OnRxException(e);
    }
  }
}

SetCommMask表示了在每一个字节到达的时候要通知我。 WaitCommEvent会返回true,这种情况下,将有1个或者多个子介在等待。如果返回了false而且错误代码是ERROR_IO_PENDING,我可以挂起线程,直到有一个字节到来。传递到WaitCommEvent的 OVERLAPPED结构包含了一个AutoResetEvent句柄,它将在有字节到来的时候引发一个信号 。[关于这一点,我困惑了很长时间,现在看来,估计它的原理是这样的,就是把要在线程中执行的方法中植入一个这个玩艺儿,然后在线程的外面等待,它会在满足一定条件的情况下发出信号?这个信号可以被外面的代码抓住?]当我在执行AutoResetEvent的 WaitOne方法时,执行将会挂起,直到事件的发生。[这一点上我又开始了困惑,这种情况下会阻塞吗?如果不阻塞的话,这倒是一个好办法,我上面的理解也需要修正了,根据昨天的学习结果,WaitOne()方法需要一个Set()方法来发出信号,这一点在程序中比较让人费解,首先,这里的AutoResetEvent的对象定义是在线程的代码中,而不是在此函数之外,那么外面的线程如何发信号给它呢,另外这里面使用了sg.Handle,这个属性,这个属性是干什么的还不是很清楚,难道是使用这个方法进行信号的传递的?]

无论WaitCommEvent立即返回true还是稍候发出完成的信号, eventMask变量都会包含一个bitmask来确定在SetCommMask中要求的哪一个条件真正发生了。(在实际的代码中,我还有其他一些保持条件)。

注意,我对eventMask同样使用了如同前面的OVERLAPPED相同的手工marshaling的技巧。我觉得这里有些没有必要,自动得marshaling就应该能够工作,但是关于准确的行为方面的文档没有,因此这样做还是保险一些。将unmarshaled指针替换为作为一个引用参数的可管理的变量似乎是可以工作的,但是除非很幸运地内存不被重用才可以。

基于时间的不同,会有多个字符在等待,因此ReadFile会不断地以一次一个的方式从队列中提取字符,并在每一次都调用虚拟方法OnRxChar。一旦我们得到ERROR_IO_PENDING,我就会调用CancelIo因为我不想在此处等待;我希望继续循环而在WaitCommEvent处等待。

使用工作线程的时候需要非常小心地考虑错误处理和异常。任何发生在ReceiveThread中的未处理的异常,从该代码中调用的任何虚拟方法,以及由此[包括该段代码和其中的虚拟方法]调用的方法或者引发的事件都会向上传播并且由catch子句来捕获。如果异常不是ThreadAbortException,它将被存储在一个CommBase的私有成员中,并且线程终止。下一次应用程序代码在主线程上调用一个方法时,异常会再次引发,端口就会被关闭。这是一个很好的内部错误处理机制,因为会有一个通用的“接收线程”[Receive Thread Fault]意外会引发,它包含了到原始的意外的引用。ThrowException在继承类当中是一个帮助方法[helper method] ;它根据调用了哪个线程修正了其行为[这一句不是很明白,原文如下:it adjusts its behavior according to which thread it is called on.]

设置和其它细节

我从一个帮助对象,就是CommBaseSettings类的对象中读取所有可以配置的设置。Open通过调用虚拟方法CommSettings来取得这个对象,然后将数据拷贝到API结构中。CommBaseSettings同样提供了方法来从XML的配置文件中保存和恢复设置,同时成批应用公共设置[这里的意思大概是说也提供了一个方法可以成批设置串口的属性]。 我为设置提供了智能感应[IntelliSense]帮助的文档。[这句话的意思是说在.NET中的新型注释“///”这种注释会出现自动出现在你需要用到的地方]。这种设计提供了一个可扩展的设置结构,因为继承类能够提供自己的设置类,该类是继承自CommBaseSettings的。我通过这种方法继承了CommLineSettings以便根据CommLine的需要增加了附加的设置。

一共有3个API用于配置通信协议:SetupComm、SetCommState和SetCommTimeouts。SetupComm请求接收和传送队列的大小。通常,您可以简单地将这些设置为0操作系统会决定[它的大小],然而对于某些文件传输或者类似的应用调整请求大小还是值得的。不保证系统都会得益于这个请求;在Windows XP中,看起来是一个动态的传输队列只有接收队列的长度才对此有好处。

SetCommState在一个称为设备控制块(DCB)的机构中提供了波特率的设置,字格式和握手的设置。
SetCommTimeouts 在一个COMMTIMEOUTS结构中提供了3个接收和2个传输超时的值。在我的设计中接收超时不是很有用因为每一个字符都是异步处理的。如果需要一个接收超时,那么需要在一个较高的层次(例如,CommLine为它的Transact方法提供了一个超时)上实现。然而对于多字节传输的超时是有用的。发送的字节乘以sendTimeoutMultiplier然后加上sendTimeoutConstant得出以毫秒计算的允许时间。

一旦端口打开并且配置好了,Open就调用一个虚拟方法AfterOpen,该方法可以重载以检查到远程设备的连接也可能对它配置。如果它[可能指的是AfterOpen]返回false,端口将被再次关闭,并且Open本身也将返回false。[我理解这是说Open的过程,也就是Open执行过程中的调用AfterOpen,如果调用失败则整个Open也会返回false] ,还有一个BeforeClose可以用于关闭远程设备。
CommBase提供了2个重载版本的Send,其中一个处理字节数组,另外一个处理单个字节。CommLine提供了第三个版本的Send,该版本处理字符串。所有这些最终都在适当的数据转换之后用到了字节数组的那个版本。还有一个SendImmediate方法处理单个字节。它在发送队列的任何字节之前发送一个字节,对于用户流量控制[自定义流控制]非常有用。[理解为,流量控制是发送给控制端口的控制命令,而不是端口上要发送的有意义的实际数据,这种情况下需要的就是先于任何实际数据直接发送,否则起不到控制流量的作用]。还有些属性提供对请求发送(RTS)和数据终端就绪(DTR)输出针的直接控制,还有将TX输出置为break条件的能力[也就是另外一个或一些属性提供了这个能力]。输入针——清除-发送(CTS),数据设置就绪(DSR),接收线信号检测(RLSD),和振铃检测仪可以使用GetModemStatus直接读取,当上述的任意输入和输出针的状态发生改变的时候,都会调用虚拟方法OnStatusChange。

GetQueueStatus返回一个QueueStatus对象,给出了发送和接收队列的大小和其中的内容以及如果有的话,是什么流控制条件阻塞了当前的传输。


结论

I've used Platform Invocation Services to address one of the gaps in the functionality of the FCL. This turned out to be a nontrivial but perfectly feasible exercise. Much of the difficulty arises because full tool support and documentation for P/Invoke is not yet in place.

Finally, I have a confession to make. As part of this project, I wrote and tested a complete wrapper for the Win32 Waitable Events API before stumbling on the ManualResetEvent and AutoResetEvent framework classes that already encapsulated all the functionality I needed. Remember: you just might be spending all your free time writing brand new classes from scratch when just what you needed already existed. Check your local hardware store before reinventing the wheel. On this principle, I hope that the base classes developed here will help other programmers bring RS232 device communications into the .NET world.
终于翻译完了啊。


御网杯逆向的干就了! 是一道比较简单的逆向题目,主要考察的是对于汇编语言的理解和逆向分析的能力。下面是解题步骤与答案: 1. 首打开题目给出的可执行文件,可以看到界面上有一个输入框和一个按钮,点击按钮后会出现一串字符。我们打开IDA Pro进行逆向分析。 2. 在IDA Pro中打开可执行文件,查看程序的主函数,可以看到程序是使用C++编写的,但是主要的逻辑都是在一个名为“sub_401000”的函数中实现的。我们进入该函数进行分析。 3. 在sub_401000函数中,可以看到程序使用了一个名为“sub_4085A0”的函数,该函数的作用是将输入的字符串进行加密处理。我们进入该函数进行分析。 4. 在sub_4085A0函数中,可以看到程序使用了一个名为“sub_4083A0”的函数,该函数的作用是将输入的字符串进行一些简单的处理,例如将小写字母转换为大写字母,并且去除字符串中的空格。我们进入该函数进行分析。 5. 在sub_4083A0函数中,可以看到程序使用了一些汇编指令,对于不熟悉汇编语言的同学可能比较难以理解。但是我们可以通过观察代码的逻辑来大致了解程序的实现方式。该函数的作用是将输入的字符串进行一些简单的处理,并将处理后的字符串保存在一个名为“v1”的字符数组中。 6. 接下来程序会对处理后的字符串进行一些计算,并将结果保存在一个名为“v2”的字符数组中。我们可以通过观察代码的逻辑,推断出程序计算的是输入字符串的一个hash值。具体的计算方法是将输入字符串中的每一个字符的ASCII码相加,并将结果保存在“v2”数组中。 7. 最后程序将计算得到的hash值转换为一个字符串,并输出到界面上。我们可以通过观察代码的逻辑,推断出程序将hash值转换为字符串的方法是将hash值的每一位都加上一个固定的值(0x16),然后将结果转换为ASCII码对应的字符,并保存在一个名为“v3”的字符数组中。 8. 因此,我们可以编写一个脚本,将输入字符串转换为hash值,然后将hash值加上0x16,转换为对应的ASCII码字符,最后输出到终端上。具体的脚本代码如下: ```python s = "your_input_string" # 将字符串中的每个字符的ASCII码相加 hash_value = sum([ord(c) for c in s]) # 将hash值加上0x16,转换为对应的ASCII码字符 result = ''.join([chr(hash_value + 0x16)]) print(result) ``` 9. 将生成的字符串输入到程序中,即可得到flag。 答案:flag{Just_Revers1ng}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值