译自Change DCB fields from SerialPort instance C#
C# SerialPort自定义串口DCB
DCB(Device Control Block)在C++ 里面是用bitfield(位域)表示的,C#没有bitfield,但有一个枚举位标志。C#有自己的方法来设置怎么存取DCB,而且“SerialStream”里确实也有“SetDcbFlag”方法(该方法接收2个int
参数)。在研究了.Net源码后,我创建了一组常量用来等效原来的DCB int字段和他们的新int代码,我暂时还没有整理出所有位标志,不过你可以在DCB文档里面查阅它们。因为这是一个Internal方法只对.Net内部程序集可见,所以我在获取该方法时用到了System.Reflection(反射)。
下面就是代码,整体上是一个.Net 2.0+SerialPort
的扩展类。扩展类中有三个方法,SetField(string name, object value)
方法用来设置所有不是以'f'开头的位,SetFlag(int Flag, int Value)
方法用来处理以'f'开头的位(我提供了一个针对Flag
参数的const列表),最后UpdateComm()
方法用来在你更改DCB后刷新串口连接,它本来是SetField
方法的一部分,但是如果设置DCB时每次都调用它,那花的时间就有点长了。
注意:
确保开启串口连接后,再使用这些方法。
用法:
SerialPort COM = new SerialPort("COM7"); COM.Open(); COM.DiscardInBuffer(); COM.DiscardOutBuffer(); COM.SetFlag(FBINARY, 1); COM.SetFlag(FPARITY, 0); COM.SetFlag(FDTRCONTROL, 0x00); COM.SetFlag(FRTSCONTROL, 0x01); COM.SetField("BaudRate", (UInt32)115200); COM.SetField("StopBits", (byte)0); COM.SetField("ByteSize", (byte)8); COM.SetField("Parity", (byte)0); COM.SetField("XonChar", (byte)0x11); COM.SetField("XoffChar", (byte)0x13); COM.SetField("EvtChar", (byte)0x1A); COM.SetField("XonLim", (ushort)256); COM.SetField("XoffLim", (ushort)256); COM.UpdateComm(); /* Do Stuff */ COM.Close();
常量:
internal const int FBINARY = 0; internal const int FPARITY = 1; internal const int FOUTXCTSFLOW = 2; internal const int FOUTXDSRFLOW = 3; internal const int FDTRCONTROL = 4; internal const int FDSRSENSITIVITY = 6; internal const int FTXCONTINUEONXOFF = 7; internal const int FOUTX = 8; internal const int FINX = 9; internal const int FERRORCHAR = 10; internal const int FNULL = 11; internal const int FRTSCONTROL = 12; internal const int FABORTONOERROR = 14; internal const int FDUMMY2 = 15;
最后,扩展类
internal static class SerialPortExtensions { [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] public static void SetField(this SerialPort port, string field, object value) { if (port == null) throw new NullReferenceException(); if (port.BaseStream == null) throw new InvalidOperationException("Cannot change fields until after the port has been opened."); try { object baseStream = port.BaseStream; Type baseStreamType = baseStream.GetType(); FieldInfo dcbFieldInfo = baseStreamType.GetField("dcb", BindingFlags.NonPublic | BindingFlags.Instance); object dcbValue = dcbFieldInfo.GetValue(baseStream); Type dcbType = dcbValue.GetType(); dcbType.GetField(field).SetValue(dcbValue, value); dcbFieldInfo.SetValue(baseStream, dcbValue); } catch (SecurityException) { throw; } catch (OutOfMemoryException) { throw; } catch (Win32Exception) { throw; } catch (Exception) { throw; } } [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] public static void SetFlag(this SerialPort port, int flag, int value) { object BaseStream = port.BaseStream; Type SerialStream = BaseStream.GetType(); SerialStream.GetMethod("SetDcbFlag", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(BaseStream, new object[] { flag, value }); } [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] public static void UpdateComm(this SerialPort port) { object baseStream = port.BaseStream; Type baseStreamType = baseStream.GetType(); FieldInfo dcbFieldInfo = baseStreamType.GetField("dcb", BindingFlags.NonPublic | BindingFlags.Instance); object dcbValue = dcbFieldInfo.GetValue(baseStream); SafeFileHandle portFileHandle = (SafeFileHandle)baseStreamType.GetField("_handle", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(baseStream); IntPtr hGlobal = Marshal.AllocHGlobal(Marshal.SizeOf(dcbValue)); try { Marshal.StructureToPtr(dcbValue, hGlobal, false); if (!SetCommState(portFileHandle, hGlobal)) throw new Win32Exception(Marshal.GetLastWin32Error()); } finally { if (hGlobal != IntPtr.Zero) Marshal.FreeHGlobal(hGlobal); } } [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool SetCommState(SafeFileHandle hFile, IntPtr lpDCB); }
Change DCB fields from SerialPort instance C
The DCB struct requires a C++ class known as a bitfield, which C# does not have, so instead they have a field called "Flags" stored as a UInt32
.They have their own melarchy set up there as to how it's stored and read from, but they did put a method in the SerialStream
called SetDcbFlag
which accepts two int
s.After digging through the .net source a bit, I managed to come up with a set of constants equating to the original DCB Fields and their new int code, I haven't yet made a list of values for these flags, but those can be easily found in the DCB Documentation.I used system.reflection to gain access to the method as it was an internal method only to be used by internal .NET source.
So here it is, the code, it's an extension class for the SerialPort
class which is shipped stock with .NET 2.0+. My extension class adds three methods, SetField(string name, object value)
which will set any of the fields that aren't prefixed with "f", SetFlag(int Flag, int Value)
which will take care of those fields prefixed with "f" (I'll provide a list of constants for use with the Flag
parameter), and finally UpdateComm()
this will update the serial connection once you have changed all of your values, it was originally part of the SetField
method, but it takes slightly longer to complete things if it's calling that every single time during initialization.
NOTE:
The serial port must be opened before using any of these methods!
Usage:
略
Constants:
略
And finally, the extension class:
略
PS:中文DCB结构详解表
MSDN的DCB文档和这个内容基本相同
成员 | 取值 | 说明 |
---|---|---|
DCBlength | DCB结构大小,即sizeof(DCB),在调用SetCommState来更新DCB前必须作设置 | |
BaudRate | 指定当前采用的波特率,应与所连接的通讯设备相匹配 | |
fBinary | 指定是否允许二进制模式。Win32 API不支持非二进制模式传输,应设置为true | |
fParity | 指定奇偶校验是否允许,在为true时具体采用何种校验看Parity 设置 | |
Parity | 指定端口数据传输的校验方法。以下是可取值及其意义: | |
EVENPARITY | 偶校验(2) | |
MARKPARITY | 标记校验,所发信息帧第9位恒为1(3) | |
NOPARITY | 无校验(0) | |
ODDPARITY | 奇校验(1) | |
StopBits | 指定端口当前使用的停止位数,可取值: | |
ONESTOPBIT | 1停止位(0) | |
ONE5STOPBITS | 1.5停止位(1) | |
TWOSTOPBITS | 2停止位(2) | |
fErrorChar | 该值为TRUE,则用ErrorChar指定的字符代替奇偶校验错误的接收字符 | |
ErrorChar | 指定ErrorChar字符(代替接收到的奇偶校验发生错误时的字节) | |
EvtChar | 当接收到此字符时,会产生一个EV_RXFLAG事件,如果用SetCommMask函数中指定了EV_RXFLAG ,则可用WaitCommEvent 来监测该事件 | |
EofChar | 指定用于标示数据结束的字符 | |
fNull | 为TRUE时,接收时自动去掉空(0值)字节 | |
fAbortOnError | 读写操作发生错误时是否取消操作。若设置为true,则当发生读写错误时,将取消所有读写操作(错误状态置为ERROR_IO_ABORTED),直到调用ClearCommError函数后才能重新进行通讯操作 | |
fOutxCtsFlow | 是否监控CTS(clear-to-send)信号来做输出流控。当设置为true时:若CTS为低电平,则数据发送将被挂起,直至CTS变为高。CTS的信号一般由DCE(通常是一个Modem)来控制,而DTE(通常是计算机)发送数据时监测CTS信号。也就是说DCE通过把CTS置高来表明自己可以接收数据了 | |
fRtsControl | 设置RTS (request-to-send)流控,若为0则缺省取值 RTS_CONTROL_HANDSHAKE。以下是可取值及其意义: | |
RTS_CONTROL_DISABLE | 打开设备时置RTS信号为低电平,应用程序可通过调用EscapeCommFunction函数来改变RTS线电平状态 | |
RTS_CONTROL_ENABLE | 打开设备时置RTS信号为高电平,应用程序可通过调用EscapeCommFunction函数来改变RTS线电平状态 | |
RTS_CONTROL_HANDSHAKE | 允许RTS信号握手,此时应用程序不能调用EscapeCommFunction函数。当输入缓冲区已经有足够空间接收数据时,驱动程序置RTS为高以便允许DCE来发送;反之置RTS为低以阻止DCE发送数据。 | |
RTS_CONTROL_TOGGLE | 有字节要发送时RTS变高,当所有缓冲字节已经被发送完毕后,RTS变低。此时应用程序不能调用EscapeCommFunction函数。该值在Windows 95系统被忽略 | |
fOutxDsrFlow | 是否监控DSR (data-set-ready) 信号来做输出流控。当设置为true时:若DSR为低电平,则数据发送将被挂起,直至DSR变为高。DSR的信号一般由DCE来控制 | |
fDtrControl | DTR (data-terminal-ready)流控,可取值如下: | |
DTR_CONTROL_DISABLE | 打开设备时置DTR信号为低电平,应用程序可通过调用EscapeCommFunction函数来改变DTR线电平状态 | |
DTR_CONTROL_ENABLE | 打开设备时置DTR信号为高电平,应用程序可通过调用EscapeCommFunction函数来改变DTR线电平状态 | |
DTR_CONTROL_HANDSHAKE | 允许DTR信号握手,此时应用程序不能调用EscapeCommFunction函数 | |
fDsrSensitivity | 通讯设备是否对DSR信号敏感。若设置为TRUE,则当DSR为低时将会忽略所有接收的字节 | |
fTXContinueOnXoff | 当输入缓冲区满且驱动程序已发出XOFF字符时,是否停止发送。当为TRUE时,XOFF被发送后发送仍然会继续;为FALSE时,发送停止,直至输入缓冲区有XonLim字节的空余空间、驱动程序已发送XON字符之后发送继续。 | |
fOutX | XON/XOFF 流量控制在发送时是否可用。如果为TRUE, 当 XOFF 值被收到的时候,发送停止;当 XON 值被收到的时候,发送继续 | |
fInX | XON/XOFF 流量控制在接收时是否可用。如果为TRUE, 当 输入缓冲区已接收满XoffLim 字节时,发送XOFF字符;当输入缓冲区已经有XonLim 字节的空余容量时,发送XON字符 | |
XonLim | 在XON字符发送前接收缓冲区内可允许的最小字节数 | |
XoffLim | 在XOFF字符发送前接收缓冲区内可允许的最大字节数 | |
XonChar | 指定XON字符 | |
XoffChar | 指定XOFF字符 | |
fDummy2 | 保留,未启用 | |
wReserved | 未启用,必须设置为0 | |
wReserved1 | 保留,未启用 |