爱悠闲 > 纯API函数实现串口读写。

纯API函数实现串口读写。

分类: 技术类库  |  标签: api,byte,character,winapi,struct,wince  |  作者: hanghangaidoudou 相关  |  发布日期 : 2015-07-23  |  热度 : 285°
以最后决定用纯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串口类 叶帆修改 链接地址
    ///</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平台都调试通过。