1. 程式人生 > >C API方式串列埠讀寫

C API方式串列埠讀寫

                在除錯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);            }