天天看點

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 uint Flags;

并且專門有一個設定位标志的函數,如下:

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:";

        ///波特率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)]

           ///<summary>

            /// DCB長度

            ///</summary>

            ///<summary>

            ///指定目前波特率

            ///标志位

            ///未使用,必須為0

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

            ///</summary>

            ///指定在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);

        ///<summary>

        ///寫序列槽資料

        ///<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平台都調試通過。

繼續閱讀