在调试ICU通信设备的时候,由于串口通信老出现故障,所以就怀疑CF实现的SerialPort类是否有问题,所以最后决定用纯API函数实现串口读写。
先从网上搜索相关代码(关键字:C# API 串口),发现网上相关的资料大约来源于一个版本,那就是所谓的msdn提供的样例代码(msdn的具体出处,我没有考证),其它的代码大都是它的变种。
其实这个示例代码是有问题的,也就是说DCB结构体声明的有问题,虽然该代码可以正常通信,不过如果你设置了奇偶校验的话,你会发现奇偶校验无效。
VC中的DCB结构声明如下:
- typedef struct _DCB {
- DWORD DCBlength; /* sizeof(DCB) */
- DWORD BaudRate; /* Baudrate at which running */
- DWORD fBinary: 1; /* Binary Mode (skip EOF check) */
- DWORD fParity: 1; /* Enable parity checking */
- DWORD fOutxCtsFlow:1; /* CTS handshaking on output */
- DWORD fOutxDsrFlow:1; /* DSR handshaking on output */
- DWORD fDtrControl:2; /* DTR Flow control */
- DWORD fDsrSensitivity:1; /* DSR Sensitivity */
- DWORD fTXContinueOnXoff: 1; /* Continue TX when Xoff sent */
- DWORD fOutX: 1; /* Enable output X-ON/X-OFF */
- DWORD fInX: 1; /* Enable input X-ON/X-OFF */
- DWORD fErrorChar: 1; /* Enable Err Replacement */
- DWORD fNull: 1; /* Enable Null stripping */
- DWORD fRtsControl:2; /* Rts Flow control */
- DWORD fAbortOnError:1; /* Abort all reads and writes on Error */
- DWORD fDummy2:17; /* Reserved */
- WORD wReserved; /* Not currently used */
- WORD XonLim; /* Transmit X-ON threshold */
- WORD XoffLim; /* Transmit X-OFF threshold */
- BYTE ByteSize; /* Number of bits/byte, 4-8 */
- BYTE Parity; /* 0-4=None,Odd,Even,Mark,Space */
- BYTE StopBits; /* 0,1,2 = 1, 1.5, 2 */
- char XonChar; /* Tx and Rx X-ON character */
- char XoffChar; /* Tx and Rx X-OFF character */
- char ErrorChar; /* Error replacement char */
- char EofChar; /* End of Input character */
- char EvtChar; /* Received Event character */
- WORD wReserved1; /* Fill for now. */
- } DCB, *LPDCB;
有问题的代码DCB结构声明如下:
[StructLayout(LayoutKind.Sequential)]
- public struct DCB
- {
- public int DCBlength;
- public int BaudRate;
- public int fBinary;
- public int fParity;
- public int fOutxCtsFlow;
- public int fOutxDsrFlow;
- public int fDtrControl;
- public int fDsrSensitivity;
- public int fTXContinueOnXoff;
- public int fOutX;
- public int fInX;
- public int fErrorChar;
- public int fNull;
- public int fRtsControl;
- public int fAbortOnError;
- public int fDummy2;
- public uint flags;
- public ushort wReserved;
- public ushort XonLim;
- public ushort XoffLim;
- public byte ByteSize;
- public byte Parity;
- public byte StopBits;
- public byte XonChar;
- public byte XoffChar;
- public byte ErrorChar;
- public byte EofChar;
- public byte EvtChar;
- public ushort wReserved1;
- }
对C++比较熟悉网友应该知道,结构体中这种格式的声明,如DWORD fBinary: 1;是以位为单位进行变量设置的,DCB中相关位一共占4个字节,也就是相当于C#中的一个int变量所占的空间。很明显上面的DCB结构会有问题,实际上后面你设置的串口参数,如奇偶校验由于偏移有问题,虽然你设置了,其实都没有设置成功。
其实也不是我说人家的DCB声明错了就错了,在SerialPort类中你就可以找到微软官方自己的DCB声明(需要反编译SerialPort类),声明如下:
[StructLayout(LayoutKind.Sequential)]
- public struct DCB
- {
- public int DCBlength;
- public int BaudRate;
- public uint Flags;
- public ushort wReserved;
- public ushort XonLim;
- public ushort XoffLim;
- public byte ByteSize;
- public byte Parity;
- public byte StopBits;
- public byte XonChar;
- public byte XoffChar;
- public byte ErrorChar;
- public byte EofChar;
- public byte EvtChar;
- public ushort wReserved1;
- }
并且专门有一个设置位标志的函数,如下:
- internal void SetDcbFlag(int whichFlag, int setting)
- {
- uint num;
- setting = setting << whichFlag;
- if ((whichFlag == 4) || (whichFlag == 12))
- {
- num = 3;
- }
- else if (whichFlag == 15)
- {
- num = 0x1ffff;
- }
- else
- {
- num = 1;
- }
- dcb.flags &= ~(num << whichFlag);
- dcb.flags |= (uint)setting;
- }
经过修改能正确运行的API代码如下(注意,由于我是在WinCE平台上运行,所以DLL的路径为"\\windows\\coredll.dll",你修改为"kernel32"后即可在PC机使用):
- ///<summary>
- /// API串口类 叶帆修改 http://yfsoft.blog.51cto.com
- ///</summary>
- public class CommPort
- {
- ///<summary>
- ///端口名称(COM1,COM2...COM4...)
- ///</summary>
- public string Port = "COM1:";
- ///<summary>
- ///波特率9600
- ///</summary>
- public int BaudRate = 9600;
- ///<summary>
- ///数据位4-8
- ///</summary>
- public byte ByteSize = 8; //4-8
- ///<summary>
- ///奇偶校验0-4=no,odd,even,mark,space
- ///</summary>
- public byte Parity = 0; //0-4=no,odd,even,mark,space
- ///<summary>
- ///停止位
- ///</summary>
- public byte StopBits = 0; //0,1,2 = 1, 1.5, 2
- ///<summary>
- ///超时长
- ///</summary>
- public int ReadTimeout = 200;
- ///<summary>
- ///串口是否已经打开
- ///</summary>
- public bool Opened = false;
- ///<summary>
- /// COM口句柄
- ///</summary>
- private int hComm = -1;
- #region "API相关定义"
- private const string DLLPATH = "\\windows\\coredll.dll"; // "kernel32";
- ///<summary>
- /// WINAPI常量,写标志
- ///</summary>
- private const uint GENERIC_READ = 0x80000000;
- ///<summary>
- /// WINAPI常量,读标志
- ///</summary>
- private const uint GENERIC_WRITE = 0x40000000;
- ///<summary>
- /// WINAPI常量,打开已存在
- ///</summary>
- private const int OPEN_EXISTING = 3;
- ///<summary>
- /// WINAPI常量,无效句柄
- ///</summary>
- private const int INVALID_HANDLE_VALUE = -1;
- private const int PURGE_RXABORT = 0x2;
- private const int PURGE_RXCLEAR = 0x8;
- private const int PURGE_TXABORT = 0x1;
- private const int PURGE_TXCLEAR = 0x4;
- ///<summary>
- ///设备控制块结构体类型
- ///</summary>
- [StructLayout(LayoutKind.Sequential)]
- public struct DCB
- {
- ///<summary>
- /// DCB长度
- ///</summary>
- public int DCBlength;
- ///<summary>
- ///指定当前波特率
- ///</summary>
- public int BaudRate;
- ///<summary>
- ///标志位
- ///</summary>
- public uint flags;
- ///<summary>
- ///未使用,必须为0
- ///</summary>
- public ushort wReserved;
- ///<summary>
- ///指定在XON字符发送这前接收缓冲区中可允许的最小字节数
- ///</summary>
- public ushort XonLim;
- ///<summary>
- ///指定在XOFF字符发送这前接收缓冲区中可允许的最小字节数
- ///</summary>
- public ushort XoffLim;
- ///<summary>
- ///指定端口当前使用的数据位
- ///</summary>
- public byte ByteSize;
- ///<summary>
- ///指定端口当前使用的奇偶校验方法,可能为:EVENPARITY,MARKPARITY,NOPARITY,ODDPARITY 0-4=no,odd,even,mark,space
- ///</summary>
- public byte Parity;
- ///<summary>
- ///指定端口当前使用的停止位数,可能为:ONESTOPBIT,ONE5STOPBITS,TWOSTOPBITS 0,1,2 = 1, 1.5, 2
- ///</summary>
- public byte StopBits;
- ///<summary>
- ///指定用于发送和接收字符XON的值 Tx and Rx XON character
- ///</summary>
- public byte XonChar;
- ///<summary>
- ///指定用于发送和接收字符XOFF值 Tx and Rx XOFF character
- ///</summary>
- public byte XoffChar;
- ///<summary>
- ///本字符用来代替接收到的奇偶校验发生错误时的值
- ///</summary>
- public byte ErrorChar;
- ///<summary>
- ///当没有使用二进制模式时,本字符可用来指示数据的结束
- ///</summary>
- public byte EofChar;
- ///<summary>
- ///当接收到此字符时,会产生一个事件
- ///</summary>
- public byte EvtChar;
- ///<summary>
- ///未使用
- ///</summary>
- public ushort wReserved1;
- }
- ///<summary>
- ///串口超时时间结构体类型
- ///</summary>
- [StructLayout(LayoutKind.Sequential)]
- private struct COMMTIMEOUTS
- {
- public int ReadIntervalTimeout;
- public int ReadTotalTimeoutMultiplier;
- public int ReadTotalTimeoutConstant;
- public int WriteTotalTimeoutMultiplier;
- public int WriteTotalTimeoutConstant;
- }
- ///<summary>
- ///溢出缓冲区结构体类型
- ///</summary>
- [StructLayout(LayoutKind.Sequential)]
- private struct OVERLAPPED
- {
- public int Internal;
- public int InternalHigh;
- public int Offset;
- public int OffsetHigh;
- public int hEvent;
- }
- ///<summary>
- ///打开串口
- ///</summary>
- ///<param name="lpFileName">要打开的串口名称</param>
- ///<param name="dwDesiredAccess">指定串口的访问方式,一般设置为可读可写方式</param>
- ///<param name="dwShareMode">指定串口的共享模式,串口不能共享,所以设置为0</param>
- ///<param name="lpSecurityAttributes">设置串口的安全属性,WIN9X下不支持,应设为NULL</param>
- ///<param name="dwCreationDisposition">对于串口通信,创建方式只能为OPEN_EXISTING</param>
- ///<param name="dwFlagsAndAttributes">指定串口属性与标志,设置为FILE_FLAG_OVERLAPPED(重叠I/O操作),指定串口以异步方式通信</param>
- ///<param name="hTemplateFile">对于串口通信必须设置为NULL</param>
- [DllImport(DLLPATH)]
- private static extern int CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode,
- int lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, int hTemplateFile);
- ///<summary>
- ///得到串口状态
- ///</summary>
- ///<param name="hFile">通信设备句柄</param>
- ///<param name="lpDCB">设备控制块DCB</param>
- [DllImport(DLLPATH)]
- private static extern bool GetCommState(int hFile, ref DCB lpDCB);
- ///<summary>
- ///建立串口设备控制块(嵌入版没有)
- ///</summary>
- ///<param name="lpDef">设备控制字符串</param>
- ///<param name="lpDCB">设备控制块</param>
- //[DllImport(DLLPATH)]
- //private static extern bool BuildCommDCB(string lpDef, ref DCB lpDCB);
- ///<summary>
- ///设置串口状态
- ///</summary>
- ///<param name="hFile">通信设备句柄</param>
- ///<param name="lpDCB">设备控制块</param>
- [DllImport(DLLPATH)]
- private static extern bool SetCommState(int hFile, ref DCB lpDCB);
- ///<summary>
- ///读取串口超时时间
- ///</summary>
- ///<param name="hFile">通信设备句柄</param>
- ///<param name="lpCommTimeouts">超时时间</param>
- [DllImport(DLLPATH)]
- private static extern bool GetCommTimeouts(int hFile, ref COMMTIMEOUTS lpCommTimeouts);
- ///<summary>
- ///设置串口超时时间
- ///</summary>
- ///<param name="hFile">通信设备句柄</param>
- ///<param name="lpCommTimeouts">超时时间</param>
- [DllImport(DLLPATH)]
- private static extern bool SetCommTimeouts(int hFile, ref COMMTIMEOUTS lpCommTimeouts);
- ///<summary>
- ///读取串口数据
- ///</summary>
- ///<param name="hFile">通信设备句柄</param>
- ///<param name="lpBuffer">数据缓冲区</param>
- ///<param name="nNumberOfBytesToRead">多少字节等待读取</param>
- ///<param name="lpNumberOfBytesRead">读取多少字节</param>
- ///<param name="lpOverlapped">溢出缓冲区</param>
- [DllImport(DLLPATH)]
- private static extern bool ReadFile(int hFile, byte[] lpBuffer, int nNumberOfBytesToRead,
- ref int lpNumberOfBytesRead, ref OVERLAPPED lpOverlapped);
- ///<summary>
- ///写串口数据
- ///</summary>
- ///<param name="hFile">通信设备句柄</param>
- ///<param name="lpBuffer">数据缓冲区</param>
- ///<param name="nNumberOfBytesToWrite">多少字节等待写入</param>
- ///<param name="lpNumberOfBytesWritten">已经写入多少字节</param>
- ///<param name="lpOverlapped">溢出缓冲区</param>
- [DllImport(DLLPATH)]
- private static extern bool WriteFile(int hFile, byte[] lpBuffer, int nNumberOfBytesToWrite,
- ref int lpNumberOfBytesWritten, ref OVERLAPPED lpOverlapped);
- [DllImport(DLLPATH, SetLastError = true)]
- private static extern bool FlushFileBuffers(int hFile);
- [DllImport(DLLPATH, SetLastError = true)]
- private static extern bool PurgeComm(int hFile, uint dwFlags);
- ///<summary>
- ///关闭串口
- ///</summary>
- ///<param name="hObject">通信设备句柄</param>
- [DllImport(DLLPATH)]
- private static extern bool CloseHandle(int hObject);
- ///<summary>
- ///得到串口最后一次返回的错误
- ///</summary>
- [DllImport(DLLPATH)]
- private static extern uint GetLastError();
- #endregion
- ///<summary>
- ///设置DCB标志位
- ///</summary>
- ///<param name="whichFlag"></param>
- ///<param name="setting"></param>
- ///<param name="dcb"></param>
- internal void SetDcbFlag(int whichFlag, int setting, DCB dcb)
- {
- uint num;
- setting = setting << whichFlag;
- if ((whichFlag == 4) || (whichFlag == 12))
- {
- num = 3;
- }
- else if (whichFlag == 15)
- {
- num = 0x1ffff;
- }
- else
- {
- num = 1;
- }
- dcb.flags &= ~(num << whichFlag);
- dcb.flags |= (uint)setting;
- }
- ///<summary>
- ///建立与串口的连接
- ///</summary>
- public int Open()
- {
- DCB dcb = new DCB();
- COMMTIMEOUTS ctoCommPort = new COMMTIMEOUTS();
- // 打开串口
- hComm = CreateFile(Port, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
- if (hComm == INVALID_HANDLE_VALUE)
- {
- return -1;
- }
- // 设置通信超时时间
- GetCommTimeouts(hComm, ref ctoCommPort);
- ctoCommPort.ReadTotalTimeoutConstant = ReadTimeout;
- ctoCommPort.ReadTotalTimeoutMultiplier = 0;
- ctoCommPort.WriteTotalTimeoutMultiplier = 0;
- ctoCommPort.WriteTotalTimeoutConstant = 0;
- SetCommTimeouts(hComm, ref ctoCommPort);
- //设置串口参数
- GetCommState(hComm, ref dcb);
- dcb.DCBlength = Marshal.SizeOf(dcb);
- dcb.BaudRate = BaudRate;
- dcb.flags = 0;
- dcb.ByteSize = (byte)ByteSize;
- dcb.StopBits = StopBits;
- dcb.Parity = (byte)Parity;
- //------------------------------
- SetDcbFlag(0, 1, dcb); //二进制方式
- SetDcbFlag(1, (Parity == 0) ? 0 : 1, dcb);
- SetDcbFlag(2, 0, dcb); //不用CTS检测发送流控制
- SetDcbFlag(3, 0, dcb); //不用DSR检测发送流控制
- SetDcbFlag(4, 0, dcb); //禁止DTR流量控制
- SetDcbFlag(6, 0, dcb); //对DTR信号线不敏感
- SetDcbFlag(9, 1, dcb); //检测接收缓冲区
- SetDcbFlag(8, 0, dcb); //不做发送字符控制
- SetDcbFlag(10, 0, dcb); //是否用指定字符替换校验错的字符
- SetDcbFlag(11, 0, dcb); //保留NULL字符
- SetDcbFlag(12, 0, dcb); //允许RTS流量控制
- SetDcbFlag(14, 0, dcb); //发送错误后,继续进行下面的读写操作
- //--------------------------------
- dcb.wReserved = 0; //没有使用,必须为0
- dcb.XonLim = 0; //指定在XOFF字符发送之前接收到缓冲区中可允许的最小字节数
- dcb.XoffLim = 0; //指定在XOFF字符发送之前缓冲区中可允许的最小可用字节数
- dcb.XonChar = 0; //发送和接收的XON字符
- dcb.XoffChar = 0; //发送和接收的XOFF字符
- dcb.ErrorChar = 0; //代替接收到奇偶校验错误的字符
- dcb.EofChar = 0; //用来表示数据的结束
- dcb.EvtChar = 0; //事件字符,接收到此字符时,会产生一个事件
- dcb.wReserved1 = 0; //没有使用
- if (!SetCommState(hComm, ref dcb))
- {
- return -2;
- }
- Opened = true;
- return 0;
- }
- ///<summary>
- ///关闭串口,结束通讯
- ///</summary>
- public void Close()
- {
- if (hComm != INVALID_HANDLE_VALUE)
- {
- CloseHandle(hComm);
- }
- }
- ///<summary>
- ///读取串口返回的数据
- ///</summary>
- ///<param name="NumBytes">数据长度</param>
- public int Read(ref byte[] bytData, int NumBytes)
- {
- if (hComm != INVALID_HANDLE_VALUE)
- {
- OVERLAPPED ovlCommPort = new OVERLAPPED();
- int BytesRead = 0;
- ReadFile(hComm, bytData, NumBytes, ref BytesRead, ref ovlCommPort);
- return BytesRead;
- }
- else
- {
- return -1;
- }
- }
- ///<summary>
- ///向串口写数据
- ///</summary>
- ///<param name="WriteBytes">数据数组</param>
- public int Write(byte[] WriteBytes, int intSize)
- {
- if (hComm != INVALID_HANDLE_VALUE)
- {
- OVERLAPPED ovlCommPort = new OVERLAPPED();
- int BytesWritten = 0;
- WriteFile(hComm, WriteBytes, intSize, ref BytesWritten, ref ovlCommPort);
- return BytesWritten;
- }
- else
- {
- return -1;
- }
- }
- ///<summary>
- ///清除接收缓冲区
- ///</summary>
- ///<returns></returns>
- public void ClearReceiveBuf()
- {
- if (hComm != INVALID_HANDLE_VALUE)
- {
- PurgeComm(hComm, PURGE_RXABORT | PURGE_RXCLEAR);
- }
- }
- ///<summary>
- ///清除发送缓冲区
- ///</summary>
- public void ClearSendBuf()
- {
- if (hComm != INVALID_HANDLE_VALUE)
- {
- PurgeComm(hComm, PURGE_TXABORT | PURGE_TXCLEAR);
- }
- }
- }
后记:我的串口程序修改为API方式后,实际发现与SerialPort类遇到同样的问题,所以SerialPort类还是值得信任的。该API方式的代码在WinCE平台和PC平台都调试通过。