C API方式串列埠讀寫
阿新 • • 發佈:2018-12-21
在除錯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://blog.csdn.net/yefanqiu ///</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); }