天天看點

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類),聲明如下:

       {  

           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;  

///<summary>  

    ///</summary>  

    public class CommPort  

    {  

        ///<summary>  

        ///端口名稱(COM1,COM2...COM4...)  

        ///</summary>  

        public string Port = "COM1:";  

        ///波特率9600  

        public int BaudRate = 9600;  

        ///資料位4-8  

        public byte ByteSize = 8; //4-8   

        ///奇偶校驗0-4=no,odd,even,mark,space   

        public byte Parity = 0;   //0-4=no,odd,even,mark,space   

        ///停止位  

        public byte StopBits = 0;   //0,1,2 = 1, 1.5, 2   

        ///逾時長  

        public int ReadTimeout = 200;  

        ///序列槽是否已經打開  

        public bool Opened = false;  

        /// COM口句柄  

        private int hComm = -1;  

        #region "API相關定義"  

        private const string DLLPATH = "\\windows\\coredll.dll"; // "kernel32";  

        /// WINAPI常量,寫标志  

        private const uint GENERIC_READ = 0x80000000;  

        /// WINAPI常量,讀标志  

        private const uint GENERIC_WRITE = 0x40000000;  

        /// WINAPI常量,打開已存在  

        private const int OPEN_EXISTING = 3;  

        /// WINAPI常量,無效句柄  

        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;  

        ///裝置控制塊結構體類型  

        [StructLayout(LayoutKind.Sequential)]  

        public struct DCB  

           ///<summary>  

            /// DCB長度  

            ///</summary>  

            ///<summary>  

            ///指定目前波特率  

            ///标志位  

            ///未使用,必須為0  

            ///指定在XON字元發送這前接收緩沖區中可允許的最小位元組數  

            ///指定在XOFF字元發送這前接收緩沖區中可允許的最小位元組數  

            ///指定端口目前使用的資料位  

            ///指定端口目前使用的奇偶校驗方法,可能為:EVENPARITY,MARKPARITY,NOPARITY,ODDPARITY 0-4=no,odd,even,mark,space   

            ///指定端口目前使用的停止位數,可能為:ONESTOPBIT,ONE5STOPBITS,TWOSTOPBITS 0,1,2 = 1, 1.5, 2   

            ///指定用于發送和接收字元XON的值 Tx and Rx XON character   

            ///指定用于發送和接收字元XOFF值 Tx and Rx XOFF character   

            ///本字元用來代替接收到的奇偶校驗發生錯誤時的值  

            ///當沒有使用二進制模式時,本字元可用來訓示資料的結束  

            ///當接收到此字元時,會産生一個事件  

            ///未使用  

        ///序列槽逾時時間結構體類型  

        private struct COMMTIMEOUTS  

            public int ReadIntervalTimeout;  

            public int ReadTotalTimeoutMultiplier;  

            public int ReadTotalTimeoutConstant;  

            public int WriteTotalTimeoutMultiplier;  

            public int WriteTotalTimeoutConstant;  

        ///溢出緩沖區結構體類型  

        private struct OVERLAPPED  

            public int Internal;  

            public int InternalHigh;  

            public int Offset;  

            public int OffsetHigh;  

            public int hEvent;  

        ///打開序列槽  

        ///<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);  

        ///得到序列槽狀态  

        ///<param name="hFile">通信裝置句柄</param>  

        ///<param name="lpDCB">裝置控制塊DCB</param>  

        private static extern bool GetCommState(int hFile, ref DCB lpDCB);  

        ///建立序列槽裝置控制塊(嵌入版沒有)  

        ///<param name="lpDef">裝置控制字元串</param>  

        ///<param name="lpDCB">裝置控制塊</param>  

        //[DllImport(DLLPATH)]  

        //private static extern bool BuildCommDCB(string lpDef, ref DCB lpDCB);  

        ///設定序列槽狀态  

        private static extern bool SetCommState(int hFile, ref DCB lpDCB);  

        ///讀取序列槽逾時時間  

        ///<param name="lpCommTimeouts">逾時時間</param>  

        private static extern bool GetCommTimeouts(int hFile, ref COMMTIMEOUTS lpCommTimeouts);  

        ///設定序列槽逾時時間  

        private static extern bool SetCommTimeouts(int hFile, ref COMMTIMEOUTS lpCommTimeouts);  

        ///讀取序列槽資料  

        ///<param name="lpBuffer">資料緩沖區</param>  

        ///<param name="nNumberOfBytesToRead">多少位元組等待讀取</param>  

        ///<param name="lpNumberOfBytesRead">讀取多少位元組</param>  

        ///<param name="lpOverlapped">溢出緩沖區</param>  

        private static extern bool ReadFile(int hFile, byte[] lpBuffer, int nNumberOfBytesToRead,  

        ref int lpNumberOfBytesRead, ref OVERLAPPED lpOverlapped);  

        ///寫序列槽資料  

        ///<param name="nNumberOfBytesToWrite">多少位元組等待寫入</param>  

        ///<param name="lpNumberOfBytesWritten">已經寫入多少位元組</param>  

        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);  

        private static extern bool PurgeComm(int hFile, uint dwFlags);  

        ///關閉序列槽  

        ///<param name="hObject">通信裝置句柄</param>  

        private static extern bool CloseHandle(int hObject);  

        ///得到序列槽最後一次傳回的錯誤  

        private static extern uint GetLastError();  

        #endregion  

        ///設定DCB标志位  

        ///<param name="whichFlag"></param>  

        ///<param name="setting"></param>  

        ///<param name="dcb"></param>  

        internal void SetDcbFlag(int whichFlag, int setting, DCB dcb)  

        ///建立與序列槽的連接配接  

        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;  

        ///關閉序列槽,結束通訊  

        public void Close()  

            if (hComm != INVALID_HANDLE_VALUE)  

                CloseHandle(hComm);  

        ///讀取序列槽傳回的資料  

        ///<param name="NumBytes">資料長度</param>  

        public int Read(ref byte[] bytData, int NumBytes)  

                OVERLAPPED ovlCommPort = new OVERLAPPED();  

                int BytesRead = 0;  

                ReadFile(hComm, bytData, NumBytes, ref BytesRead, ref ovlCommPort);  

                return BytesRead;  

        ///向序列槽寫資料  

        ///<param name="WriteBytes">資料數組</param>  

        public int Write(byte[] WriteBytes, int intSize)  

                int BytesWritten = 0;  

                WriteFile(hComm, WriteBytes, intSize, ref BytesWritten, ref ovlCommPort);  

                return BytesWritten;  

        ///清除接收緩沖區  

        ///<returns></returns>  

        public void ClearReceiveBuf()  

                PurgeComm(hComm, PURGE_RXABORT | PURGE_RXCLEAR);  

        ///清除發送緩沖區  

        public void ClearSendBuf()  

                PurgeComm(hComm, PURGE_TXABORT | PURGE_TXCLEAR);  

 }  

後記:我的序列槽程式修改為API方式後,實際發現與SerialPort類遇到同樣的問題,是以SerialPort類還是值得信任的。該API方式的代碼在WinCE平台和PC平台都調試通過。

本文轉自yefanqiu51CTO部落格,原文連結:http://blog.51cto.com/yfsoft/323424,如需轉載請自行聯系原作者