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