【原】实用的C#串口类CommPort,用于串口通信的源代码类

此CommPort串口类基于网上的mycomm修改,修复了一些bug、完善了事件处理机制,算是相当稳定实用了,不敢独享,贡献给大家想用。

使用注意事项:

1、几个属性的设置,用0、1、2、3等表示相应的选项;

2、port是数字,Open的时候自动串上“\\\\.\\COM”字符串;

3、加入了数据接收事件的触发,由于是从后台调用DataReceived回调函数,在接收和处理数据时可能不能访问UI元素(没试过,如果哪位碰到这个问题,自己加一下Invokee调用吧);

4、几个字段没有加正确性校验,别传错了;

5、读取数据时尽量用Read一次全部读出,然后做分解,而不是用ReadByte一次读一个字节,你想想,如果一块数据好几k,那么你用Read一次就读出来了,而ReadByte要读好几千次!性能马上就掉下来了;

6、低调的实现了startThread函数,你也会看到,如果要做的更好,线程启动时应该判断一下先前的线程是否退出;

7、你也应该看到,访问串口时没有加锁,安全的做法是,在读写串口时应该保证同时只有个读或者写,搞多线程的童鞋这里要注意一下;

8、跑的爽的时候想想我,出错的时候别骂我 :-)

—— HYH || khler

 

[code lang=”C”]

/*
* 作者:何元会
* E-mail:khler@163.com
* QQ:23381103
* 授权:GPL,同时必须保证本说明的完整性!
*
* 此串口类基于网上的一个类 mycomm 修改而来,网址:http://hi.baidu.com/p1220/blog/item/bf014e01d1d6da047aec2cb9.html
*
* 此处提到的mycomm类实际上是有bug的,就是DCB结构体的定义,对几个bit flag的位字段包装有问题,
* DCB中的这些flag每个只占一个bit,而包装中占了一个int,显然传递进去后标志位会错位,肯定会有问题。
* 我碰到的就是每次接收到的数据,大于127的数值都被折半了。修复DCB结构体对几个flag的定义就解决了。
* C#里面没有位字段定义,需要与枚举类型配合使用,此bug已修复;
*
* 另一个问题是,对于’USB转串口’的串口设备,在CreateFile时会打开失败的,报告找不到此文件。
* 通用做法是在前面加’\\\\.\\’,如COM1应该是"\\\\.\\COM1"
*
* 再一个改进:添加了数据接收事件探测,当有数据到达时调用事件处理函数。用法与自带的SerialPort类一样,
* 在定义对象时记得给他的DataReceived串接一个事件处理函数就行了。
*
* 加入功能:
* 1、包装了ClearCommError()函数,用于读取当前串口可读数据,BytesToRead,只读;
* 2、加入CommDataReceivedEventHandler委托,当有数据时向使用者发送消息;
* 3、加入数据监测线程,用于后台监视数据可读;
* 4、加入ReadByte函数,用于读取单个字节;
* 5、基本模拟了C#自带的SerialPort类,使用极其方便;
* 6、在读取数据后手动从BytesToRead中减去刚刚读取掉的字节数,避免多线程异步更新问题;
* 7、属性做了包装和说明;
* 8、修复DCB结构体定义bug;
* 9、修复无法打开’USB转串口’的串口设备的问题;
* 10、添加GetLastError()函数,打开设备失败时提取错误号;
*
*
*
*
* 接口:
* CommPort() ,构造函数
* DataReceived ,事件处理回调
* void Open() ,打开串口
* byte[] Read(int NumBytes) ,读取数据
* byte ReadByte() ,读取字节
* int Write(byte[] WriteBytes) ,写入数据
* void Close() ,关闭串口
*
*
* 使用范例:
*
private void OpenPort(int port)
{
try
{
comm = new CommPort();
comm.PortNum = port;
comm.BaudRate = 115200;
comm.ByteSize = 8;
comm.Parity = 0;
comm.StopBits = 0;
comm.ReadTimeout = 10;
comm.DataReceived += new CommDataReceivedEventHandler(comm_DataReceived);
comm.Open();
Console.WriteLine("COM" + comm.PortNum + " 打开成功");
}
catch (Exception ex)
{
Console.WriteLine("COM" + comm.PortNum + " 打开失败:" + ex.Message);
}
}

void comm_DataReceived(object sender)
{
if (comm.BytesToRead > 0)
{
byte[] buffer = comm.Read(comm.BytesToRead);
// 分解数据

}
}

*
*
**/

using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.IO.Ports;

namespace HYHComm
{
public delegate void CommDataReceivedEventHandler(object sender);

class CommPort
{
#region 构造函数
public CommPort()
{
}
#endregion// 构造函数

#region 属性
/// <summary>
/// 数据接收事件
/// </summary>
public event CommDataReceivedEventHandler DataReceived;

//comm port win32 file handle
private int hComm = -1;

/// <summary>
/// 打开标示
/// </summary>
public bool Opened;
/// <summary>
/// 串口号:1,2,3,4…
/// </summary>
public int PortNum;
/// <summary>
/// 波特率:1200,2400,4800,9600,115200
/// </summary>
public int BaudRate;
/// <summary>
/// 数据位 8 bits
/// </summary>
public byte ByteSize;
/// <summary>
/// 数据校验 0-4=no,odd,even,mark,space
/// </summary>
public byte Parity;
/// <summary>
/// 停止位 0,1,2 = 1, 1.5, 2
/// </summary>
public byte StopBits;
/// <summary>
/// 超时时间 //10
/// </summary>
public int ReadTimeout;
/// <summary>
/// 缓存中可读字节数
/// </summary>
public int BytesToRead
{
get { return _BytesToRead; }
}
private int _BytesToRead;

private enum ComStatFlags
{
fCtsHold = 0x1,
fDsrHold = 0x2,
fRlsdHold = 0x4,
fXoffHold = 0x8,
fXoffSent = 0x10,
fEof = 0x20,
fTxim = 0x40
}
[StructLayout(LayoutKind.Sequential)]
private struct COMSTAT
{
public ComStatFlags flags;
public uint cbInQue;
public uint cbOutQue;
};

private const uint GENERIC_READ = 0x80000000;
private const uint GENERIC_WRITE = 0x40000000;
private const int OPEN_EXISTING = 3;
private const int INVALID_HANDLE_VALUE = -1;

private enum DCBFlags
{
fBinary = 0x01, // binary mode, no EOF check
fParity = 0x02, // enable parity checking
fOutxCtsFlow = 0x04, // CTS output flow control
fOutxDsrFlow = 0x08, // DSR output flow control
fDtrControl = 0x30, // DTR flow control type , 2bits
fDsrSensitivity = 0x40, // DSR sensitivity
fTXContinueOnXoff = 0x80, // XOFF continues Tx
fOutX = 0x100, // XON/XOFF out flow control
fInX = 0x200, // XON/XOFF in flow control
fErrorChar = 0x400, // enable error replacement
fNull = 0x800, // enable null stripping
fRtsControl = 0x3000, // RTS flow control , 2bits
fAbortOnError = 0x4000, // abort on error
fDummy2 = unchecked((int)0xffff8000), // reserved , 17bits
};
[StructLayout(LayoutKind.Sequential)]
private struct DCB
{
//taken from c struct in platform sdk
public int DCBlength; // sizeof(DCB)
public int BaudRate; // current baud rate
public DCBFlags fFlags; // DCBFlags
public ushort wReserved; // not currently used
public ushort XonLim; // transmit XON threshold
public ushort XoffLim; // transmit XOFF threshold
public byte ByteSize; // number of bits/byte, 4-8
public byte Parity; // 0-4=no,odd,even,mark,space
public byte StopBits; // 0,1,2 = 1, 1.5, 2
public char XonChar; // Tx and Rx XON character
public char XoffChar; // Tx and Rx XOFF character
public char ErrorChar; // error replacement character
public char EofChar; // end of input character
public char EvtChar; // received event character
public ushort wReserved1; // reserved; do not use
}
[StructLayout(LayoutKind.Sequential)]
private struct COMMTIMEOUTS
{
public int ReadIntervalTimeout;
public int ReadTotalTimeoutMultiplier;
public int ReadTotalTimeoutConstant;
public int WriteTotalTimeoutMultiplier;
public int WriteTotalTimeoutConstant;
}
[StructLayout(LayoutKind.Sequential)]
private struct OVERLAPPED
{
public int Internal;
public int InternalHigh;
public int Offset;
public int OffsetHigh;
public int hEvent;
}
#endregion// 属性

#region 方法
[DllImport("kernel32.dll")]
private static extern bool ClearCommError(int hFile, ref int lpdwErrorFlag, ref COMSTAT lpCommStat);
[DllImport("kernel32.dll")]
private static extern int CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode, int lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, int hTemplateFile);
[DllImport("kernel32.dll")]
private static extern bool GetCommState(int hFile, ref DCB lpDCB);
[DllImport("kernel32.dll")]
private static extern bool BuildCommDCB(string lpDef, ref DCB lpDCB);
[DllImport("kernel32.dll")]
private static extern bool SetCommState(int hFile, ref DCB lpDCB);
[DllImport("kernel32.dll")]
private static extern bool GetCommTimeouts(int hFile, ref COMMTIMEOUTS lpCommTimeouts);
[DllImport("kernel32.dll")]
private static extern bool SetCommTimeouts(int hFile, ref COMMTIMEOUTS lpCommTimeouts);
[DllImport("kernel32.dll")]
private static extern bool ReadFile(int hFile, byte[] lpBuffer, int nNumberOfBytesToRead, ref int lpNumberOfBytesRead, ref OVERLAPPED lpOverlapped);
[DllImport("kernel32.dll")]
private static extern bool WriteFile(int hFile, byte[] lpBuffer, int nNumberOfBytesToWrite, ref int lpNumberOfBytesWritten, ref OVERLAPPED lpOverlapped);
[DllImport("kernel32.dll")]
private static extern UInt32 GetLastError();
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(int hObject);
private void startThread()
{
// 匿名方法
ParameterizedThreadStart threadRun = delegate(object self)
{
//COMM comm = (COMM)self;
int dwErrorFlags = 0;
COMSTAT ComStat = new COMSTAT();

while (Opened)
{
// 打破无限循环,降低CPU使用率
Thread.Sleep(0);
if (!ClearCommError(hComm, ref dwErrorFlags, ref ComStat))
{
continue;
}

if (dwErrorFlags != 0)
{
//getMsg("上一次对串口的操作存在错误");
continue;
}

_BytesToRead = (int)ComStat.cbInQue;

if (_BytesToRead > 0)
{
if (DataReceived != null)
{
DataReceived(this);
}
}
}
};
Thread thrHasData = new Thread(threadRun);
thrHasData.Start(this);
}

public void Open()
{
DCB dcbCommPort = new DCB();
COMMTIMEOUTS ctoCommPort = new COMMTIMEOUTS();

// OPEN THE COMM PORT.
hComm = CreateFile("\\\\.\\COM" + PortNum, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);

// IF THE PORT CANNOT BE OPENED, BAIL OUT.
if (hComm == INVALID_HANDLE_VALUE)
{
UInt32 dwErr = GetLastError();
throw (new ApplicationException("Comm Port Can Not Be Opened, Error Code:" + dwErr));
}

// SET THE COMM TIMEOUTS.
GetCommTimeouts(hComm, ref ctoCommPort);
ctoCommPort.ReadTotalTimeoutConstant = ReadTimeout;
ctoCommPort.ReadTotalTimeoutMultiplier = 0;
ctoCommPort.WriteTotalTimeoutMultiplier = 0;
ctoCommPort.WriteTotalTimeoutConstant = 0;
SetCommTimeouts(hComm, ref ctoCommPort);

// SET BAUD RATE, PARITY, WORD SIZE, AND STOP BITS.
// THERE ARE OTHER WAYS OF DOING SETTING THESE BUT THIS IS THE EASIEST.
// IF YOU WANT TO LATER ADD CODE FOR OTHER BAUD RATES, REMEMBER
// THAT THE ARGUMENT FOR BuildCommDCB MUST BE A POINTER TO A STRING.
// ALSO NOTE THAT BuildCommDCB() DEFAULTS TO NO HANDSHAKING.

dcbCommPort.DCBlength = Marshal.SizeOf(dcbCommPort);
GetCommState(hComm, ref dcbCommPort);
dcbCommPort.BaudRate = BaudRate;
dcbCommPort.Parity = Parity;
dcbCommPort.ByteSize = ByteSize;
dcbCommPort.StopBits = StopBits;
SetCommState(hComm, ref dcbCommPort);

Opened = true;

startThread();
}

public byte[] Read(int NumBytes)
{
byte[] BufBytes = null;
byte[] OutBytes = null;
BufBytes = new byte[NumBytes];
if (hComm != INVALID_HANDLE_VALUE)
{
OVERLAPPED ovlCommPort = new OVERLAPPED();
int BytesRead = 0;
if (ReadFile(hComm, BufBytes, NumBytes, ref BytesRead, ref ovlCommPort))
{
_BytesToRead -= BytesRead;
OutBytes = new byte[BytesRead];
Array.Copy(BufBytes, OutBytes, BytesRead);
}
}
else
{
throw (new ApplicationException("Comm Port Not Open"));
}
return OutBytes;
}

public byte ReadByte()
{
byte[] BufBytes = new byte[1];
if (hComm != INVALID_HANDLE_VALUE)
{
OVERLAPPED ovlCommPort = new OVERLAPPED();
int BytesRead = 0;
if (ReadFile(hComm, BufBytes, 1, ref BytesRead, ref ovlCommPort))
{
_BytesToRead -= 1;
}
}
else
{
throw (new ApplicationException("Comm Port Not Open"));
}
return BufBytes[0];
}

public int Write(byte[] WriteBytes)
{
int BytesWritten = 0;
if (hComm != INVALID_HANDLE_VALUE)
{
OVERLAPPED ovlCommPort = new OVERLAPPED();
WriteFile(hComm, WriteBytes, WriteBytes.Length, ref BytesWritten, ref ovlCommPort);
}
else
{
throw (new ApplicationException("Comm Port Not Open"));
}
return BytesWritten;
}

public void Close()
{
// 置为false,监测线程将会退出
Opened = false;
if (hComm != INVALID_HANDLE_VALUE)
{
CloseHandle(hComm);
}
}
#endregion// 方法
}
}

[/code]

发表评论

电子邮件地址不会被公开。