串口在工業應用是極爲普遍的,我用API封裝了同步和異步的串口類,以及一個具有監視線程的異步串口類;使用簡單高效,具有工業強度,我在BC, BCB, VC, BCBX, GCC下編譯通過,相信足夠應付大多數情況,而且還可以繼承擴展,下面簡單介紹使用方法, 後附源代碼(_com.h);
庫的層次結構:
_base_com:虛基類,基本接口,可自行擴展自己的串口類
_sync_com:_base_com 的子類, 同步應用,適合簡單應用
_asyn_com:_base_com 的子類, 異步應用(重疊I/O),適合較高效應用,NT平臺
_thread_com:_asyn_com 的子類, 異步應用,監視線程,適合較複雜應用,窗口通知消息和繼承擴展的使用方式;
幾個問題:
結束線程
如何從WaitCommEvent(pcom->_com_handle, &mask, &pcom->_wait_o)這個API退出以便順利結束線程:
方案1:
SetCommMask(_com_handle, 0); 這個方法在MSDN有載,當在一些情況下並不完全有效,原因未知;
方案2:
SetEvent(_wait_o.hEvent); 直接激活重疊IO結構中的事件句柄,絕對有效; 這份代碼我兩種都用;
打開10以上的COM端口
在NT/2000下打開編號10以上端口用
_com_handle = CreateFile(
“COM10“,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, //重疊I/O
NULL
);
將提示錯誤, 這樣就OK:
_com_handle = CreateFile(
“////.//COM10“,//對應的就是//./COM10
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, //重疊I/O
NULL
);
線程中循環的低效率問題
使用SetCommMask(pcom->_com_handle, EV_RXCHAR | EV_ERR)監視接受字符和錯誤消息;一旦有個字符來就會激活WaitCommEvent 通常作以下接受操作:
if(!WaitCommEvent(pcom->_com_handle, &mask, &pcom->_wait_o))
{
if(GetLastError() == ERROR_IO_PENDING)
{
GetOverlappedResult(pcom->_com_handle, &pcom->_wait_o, &length, true);
}
}
if(mask & EV_ERR) // == EV_ERR
ClearCommError(pcom->_com_handle, &error, &stat);
if(mask & EV_RXCHAR) // == EV_RXCHAR
{
pcom->on_receive();//接收到字符
//或發送到窗口消息
}
這樣頻繁的函數調用或接受發送消息,效率低下,我添加掃描緩衝區的代碼,當字符數超過設定的字符數才作接受字符的操作;
if(mask & EV_RXCHAR) // == EV_RXCHAR
{
ClearCommError(pcom->_com_handle, &error, &stat);
if(stat.cbInQue > pcom->_notify_num) //_notify_num 是設定得字符數
pcom->on_receive();
}
類似於流的輸出方式
我編了一個簡單的寫串口的方式,可以類似於流將簡單的數據類型輸出
template<typename T>
_asyn_com& operator << (T x)
{
strstream s;
s << x ;
write(s.str(), s.pcount());
return *this;
}
就可以這樣使用
_sync_com com1;
com1.open(1, 9600);
com1 << “ then random() 's return value is “<< rand() << “ ./n“ ;
com1.close();
本串口類庫的主要接口
class _base_com
{
bool open(int port);
bool open(int port, int baud_rate);
bool open(int port, char * set_str); // set_str : “9600, 8, n, 1“
bool set_state(int BaudRate, int ByteSize = 8, int Parity = NOPARITY, int StopBits = ONESTOPBIT)
//設置內置結構串口參數:波特率,停止位
bool set_state(char *set_str)
bool is_open();
HANDLE get_handle();
virtual bool open_port()=0; //繼承用的重要函數
virtual close();
}
class _sync_com :public _base_com //同步
{
int read(char *buf, int buf_size); //自動補上'/0',將用去一個字符的緩衝區
int write(char *buf, int len);
int write(char *buf);
}
class _asyn_com :public _base_com //異步
{
int read(char *buf, int buf_size); //自動補上'/0',將用去一個字符的緩衝區
int write(char *buf, int len);
int write(char *buf);
}
class _thread_com :public _asyn_com //線程
{
virtual void on_receive() //供線程接受到字符時調用, 可繼承替換之
{
if(_notify_hwnd)
PostMessage(_notify_hwnd, ON_COM_RECEIVE, WPARAM(_port), LPARAM(0));
else
{
if(_func)
_func(_port);
}
}
void set_hwnd(HWND hwnd); //設置窗口句柄, 發送 ON_COM_RECEIVE WM_USER + 618
void set_func(void (*f)(int)); //設置調用函數 ,窗口句柄優先
void set_notify_num(int num); //設定發送通知, 接受字符最小值
}
一些應用範例
當然首先 #include "_com.h"
一、打開串口1同步寫
char str[] = "com_class test";
_sync_com com1; //同步
com1.open(1); // 相當於 com1.open(1, 9600); com1.open(1, "9600,8,n,1");
for(int i=0; i<100; i++)
{
Sleep(500);
com1.write(str); //也可以 com1.write(str, strlen(str));
}
com1.close();
二、打開串口2異步讀
char str[100];
_asyn_com com2; //異步
com2.open(2); // 相當於 com2.open(2, 9600); com2.open(2, "9600,8,n,1");
if(!com2.is_open())
cout << "COM2 not open , error : " << GetLastError() << endl;
/*
也可以如下用法
if(!com2.open(2))
cout << "COM2 not open , error : " << GetLastError() << endl;
*/
for(int i=0; i<100; i++)
{
Sleep(500);
if(com2.read(str, 100) > 0) //異步讀,返回讀取字符數
cout << str;
}
com2.close();
三、擴展應用具有監視線程的串口類
class _com_ex : public thread_com
{
public:
virtual on_receive()
{
char str[100];
if(read(str, 100) > 0) //異步讀,返回讀取字符數
cout << str;
}
};
int main(int argc, char *argv[])
{
try
{
char str[100];
_com_ex com2; //異步擴展
com2.open(2);
Sleep(10000);
com2.close();
}
catch(exception &e)
{
cout << e.what() << endl;
}
return 0;
}
四、桌面應用可發送消息到指定窗口(在C++ Builder 和 VC ++ 測試通過)
VC ++
接受消息
BEGIN_MESSAGE_MAP(ComDlg, CDialog)
//{{AFX_MSG_MAP(ComDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_DESTROY()
//}}AFX_MSG_MAP
ON_MESSAGE(ON_COM_RECEIVE, On_Receive)
END_MESSAGE_MAP()
打開串口,傳遞窗口句柄
_thread_com com2;
com2.open(2);
com2.set_hwnd(ComDlg->m_hWnd);
處理消息
LRESULT ComDlg::On_Receive(WPARAM wp, LPARAM lp)
{
char str[100];
com2.read(str, 100);
char com_str[10];
strcpy(com_str, "COM");
ltoa((long)wp, com_str + 3, 10); // WPARAM 保存端口號
MessageBox(str, com_str, MB_OK);
return 0;
}
C++ Builder
class TForm1 : public TForm
{
__published: // IDE-managed Components
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
void __fastcall FormCreate(TObject *Sender);
private: // User declarations
public: // User declarations
void On_Receive(TMessage& Message);
__fastcall TForm1(TComponent* Owner);
_thread_com com2;
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(ON_COM_RECEIVE, TMessage, On_Receive)
END_MESSAGE_MAP(TForm)
};
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
com2.close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
com2.open(2);
com2.set_hwnd(Handle);
}
//---------------------------------------------------------------------------
void TForm1::On_Receive(TMessage& Message)
{
char xx[20];
int port = Message.WParam;
if(com2.read(xx, 20) > 0)
ShowMessage(xx);
}
錯誤和缺陷在所難免,歡迎來信批評指正;[email protected]
附完整源代碼 _com.h
/*
串口基礎類庫(WIN32) ver 0.1
編譯器 : BC++ 5; C++ BUILDER 4, 5, 6, X; VC++ 5, 6; VC.NET; GCC;
class _base_com : 虛基類 基本串口接口;
class _sync_com : 同步I/O 串口類;
class _asyn_com : 異步I/O 串口類;
class _thread_com : 異步I/O 輔助讀監視線程 可轉發窗口消息 串口類(可繼承虛函數on_receive用於讀操作);
class _com : _thread_com 同名
copyright(c) 2004.8 llbird [email protected]
*/
/*
Example :
*/
#ifndef _COM_H_
#define _COM_H_
#pragma warning(disable: 4530)
#pragma warning(disable: 4786)
#pragma warning(disable: 4800)
#include <cassert>
#include <strstream>
#include <algorithm>
#include <exception>
#include <iomanip>
using namespace std;
#include <windows.h>
class _base_com //虛基類 基本串口接口
{
protected:
volatile int _port; //串口號
volatile HANDLE _com_handle;//串口句柄
char _com_str[20];
DCB _dcb; //波特率,停止位,等
COMMTIMEOUTS _co; // 超時時間
virtual bool open_port() = 0;
void init() //初始化
{
memset(_com_str, 0, 20);
memset(&_co, 0, sizeof(_co));
memset(&_dcb, 0, sizeof(_dcb));
_dcb.DCBlength = sizeof(_dcb);
_com_handle = INVALID_HANDLE_VALUE;
}
virtual bool setup_port()
{
if(!is_open())
return false;
if(!SetupComm(_com_handle, 8192, 8192))
return false; //設置推薦緩衝區
if(!GetCommTimeouts(_com_handle, &_co))
return false;
_co.ReadIntervalTimeout = 0xFFFFFFFF;
_co.ReadTotalTimeoutMultiplier = 0;
_co.ReadTotalTimeoutConstant = 0;
_co.WriteTotalTimeoutMultiplier = 0;
_co.WriteTotalTimeoutConstant = 2000;
if(!SetCommTimeouts(_com_handle, &_co))
return false; //設置超時時間
if(!PurgeComm(_com_handle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ))
return false; //清空串口緩衝區
return true;
}
inline void set_com_port(int port)
{
char p[12];
_port = port;
strcpy(_com_str, "////.//COM");
ltoa(_port, p, 10);
strcat(_com_str, p);
}
public:
_base_com()
{
init();
}
virtual ~_base_com()
{
close();
}
//設置串口參數:波特率,停止位,等 支持設置字符串 "9600, 8, n, 1"
bool set_state(char *set_str)
{
if(is_open())
{
if(!GetCommState(_com_handle, &_dcb))
return false;
if(!BuildCommDCB(set_str, &_dcb))
return false;
return SetCommState(_com_handle, &_dcb) == TRUE;
}
return false;
}
//設置內置結構串口參數:波特率,停止位
bool set_state(int BaudRate, int ByteSize = 8, int Parity = NOPARITY, int StopBits = ONESTOPBIT)
{
if(is_open())
{
if(!GetCommState(_com_handle, &_dcb))
return false;
_dcb.BaudRate = BaudRate;
_dcb.ByteSize = ByteSize;
_dcb.Parity = Parity;
_dcb.StopBits = StopBits;
return SetCommState(_com_handle, &_dcb) == TRUE;
}
return false;
}
//打開串口 缺省 9600, 8, n, 1
inline bool open(int port)
{
return open(port, 9600);
}
//打開串口 缺省 baud_rate, 8, n, 1
inline bool open(int port, int baud_rate)
{
if(port < 1 || port > 1024)
return false;
set_com_port(port);
if(!open_port())
return false;
if(!setup_port())
return false;
return set_state(baud_rate);
}
//打開串口
inline bool open(int port, char *set_str)
{
if(port < 1 || port > 1024)
return false;
set_com_port(port);
if(!open_port())
return false;
if(!setup_port())
return false;
return set_state(set_str);
}
inline bool set_buf(int in, int out)
{
return is_open() ? SetupComm(_com_handle, in, out) : false;
}
//關閉串口
inline virtual void close()
{
if(is_open())
{
CloseHandle(_com_handle);
_com_handle = INVALID_HANDLE_VALUE;
}
}
//判斷串口是或打開
inline bool is_open()
{
return _com_handle != INVALID_HANDLE_VALUE;
}
//獲得串口句炳
HANDLE get_handle()
{
return _com_handle;
}
operator HANDLE()
{
return _com_handle;
}
};
class _sync_com : public _base_com
{
protected:
//打開串口
virtual bool open_port()
{
if(is_open())
close();
_com_handle = CreateFile(
_com_str,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL ,
NULL
);
assert(is_open());
return is_open();//檢測串口是否成功打開
}
public:
_sync_com()
{
}
//同步讀
int read(char *buf, int buf_len)
{
if(!is_open())
return 0;
buf[0] = '/0';
COMSTAT stat;
DWORD error;
if(ClearCommError(_com_handle, &error, &stat) && error > 0) //清除錯誤
{
PurgeComm(_com_handle, PURGE_RXABORT | PURGE_RXCLEAR); /*清除輸入緩衝區*/
return 0;
}
unsigned long r_len = 0;
buf_len = min(buf_len - 1, (int)stat.cbInQue);
if(!ReadFile(_com_handle, buf, buf_len, &r_len, NULL))
r_len = 0;
buf[r_len] = '/0';
return r_len;
}
//同步寫
int write(char *buf, int buf_len)
{
if(!is_open() || !buf)
return 0;
DWORD error;
if(ClearCommError(_com_handle, &error, NULL) && error > 0) //清除錯誤
PurgeComm(_com_handle, PURGE_TXABORT | PURGE_TXCLEAR);
unsigned long w_len = 0;
if(!WriteFile(_com_handle, buf, buf_len, &w_len, NULL))
w_len = 0;
return w_len;
}
//同步寫
inline int write(char *buf)
{
assert(buf);
return write(buf, strlen(buf));
}
//同步寫, 支持部分類型的流輸出
template<typename T>
_sync_com& operator << (T x)
{
strstream s;
s << x;
write(s.str(), s.pcount());
return *this;
}
};
class _asyn_com : public _base_com
{
protected:
OVERLAPPED _ro, _wo; // 重疊I/O
virtual bool open_port()
{
if(is_open())
close();
_com_handle = CreateFile(
_com_str,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, //重疊I/O
NULL
);
assert(is_open());
return is_open();//檢測串口是否成功打開
}
public:
_asyn_com()
{
memset(&_ro, 0, sizeof(_ro));
memset(&_wo, 0, sizeof(_wo));
_ro.hEvent = CreateEvent(NULL, true, false, NULL);
assert(_ro.hEvent != INVALID_HANDLE_VALUE);
_wo.hEvent = CreateEvent(NULL, true, false, NULL);
assert(_wo.hEvent != INVALID_HANDLE_VALUE);
}
virtual ~_asyn_com()
{
close();
if(_ro.hEvent != INVALID_HANDLE_VALUE)
CloseHandle(_ro.hEvent);
if(_wo.hEvent != INVALID_HANDLE_VALUE)
CloseHandle(_wo.hEvent);
}
//異步讀
int read(char *buf, int buf_len, int time_wait = 20)
{
if(!is_open())
return 0;
buf[0] = '/0';
COMSTAT stat;
DWORD error;
if(ClearCommError(_com_handle, &error, &stat) && error > 0) //清除錯誤
{
PurgeComm(_com_handle, PURGE_RXABORT | PURGE_RXCLEAR); /*清除輸入緩衝區*/
return 0;
}
if(!stat.cbInQue)// 緩衝區無數據
return 0;
unsigned long r_len = 0;
buf_len = min((int)(buf_len - 1), (int)stat.cbInQue);
if(!ReadFile(_com_handle, buf, buf_len, &r_len, &_ro)) //2000 下 ReadFile 始終返回 True
{
if(GetLastError() == ERROR_IO_PENDING) // 結束異步I/O
{
//WaitForSingleObject(_ro.hEvent, time_wait); //等待20ms
if(!GetOverlappedResult(_com_handle, &_ro, &r_len, false))
{
if(GetLastError() != ERROR_IO_INCOMPLETE)//其他錯誤
r_len = 0;
}
}
else
r_len = 0;
}
buf[r_len] = '/0';
return r_len;
}
//異步寫
int write(char *buf, int buf_len)
{
if(!is_open())
return 0;
DWORD error;
if(ClearCommError(_com_handle, &error, NULL) && error > 0) //清除錯誤
PurgeComm(_com_handle, PURGE_TXABORT | PURGE_TXCLEAR);
unsigned long w_len = 0, o_len = 0;
if(!WriteFile(_com_handle, buf, buf_len, &w_len, &_wo))
if(GetLastError() != ERROR_IO_PENDING)
w_len = 0;
return w_len;
}
//異步寫
inline int write(char *buf)
{
assert(buf);
return write(buf, strlen(buf));
}
//異步寫, 支持部分類型的流輸出
template<typename T>
_asyn_com& operator << (T x)
{
strstream s;
s << x ;
write(s.str(), s.pcount());
return *this;
}
};
//當接受到數據送到窗口的消息
#define ON_COM_RECEIVE WM_USER + 618 // WPARAM 端口號
class _thread_com : public _asyn_com
{
protected:
volatile HANDLE _thread_handle; //輔助線程
volatile HWND _notify_hwnd; // 通知窗口
volatile long _notify_num;//接受多少字節(>_notify_num)發送通知消息
volatile bool _run_flag; //線程運行循環標誌
void (*_func)(int port);
OVERLAPPED _wait_o; //WaitCommEvent use
//線程收到消息自動調用, 如窗口句柄有效, 送出消息, 包含窗口編號
virtual void on_receive()
{
if(_notify_hwnd)
PostMessage(_notify_hwnd, ON_COM_RECEIVE, WPARAM(_port), LPARAM(0));
else
{
if(_func)
_func(_port);
}
}
//打開串口,同時打開監視線程
virtual bool open_port()
{
if(_asyn_com::open_port())
{
_run_flag = true;
DWORD id;
_thread_handle = CreateThread(NULL, 0, com_thread, this, 0, &id); //輔助線程
assert(_thread_handle);
if(!_thread_handle)
{
CloseHandle(_com_handle);
_com_handle = INVALID_HANDLE_VALUE;
}
else
return true;
}
return false;
}
public:
_thread_com()
{
_notify_num = 0;
_notify_hwnd = NULL;
_thread_handle = NULL;
_func = NULL;
memset(&_wait_o, 0, sizeof(_wait_o));
_wait_o.hEvent = CreateEvent(NULL, true, false, NULL);
assert(_wait_o.hEvent != INVALID_HANDLE_VALUE);
}
~_thread_com()
{
close();
if(_wait_o.hEvent != INVALID_HANDLE_VALUE)
CloseHandle(_wait_o.hEvent);
}
//設定發送通知, 接受字符最小值
void set_notify_num(int num)
{
_notify_num = num;
}
int get_notify_num()
{
return _notify_num;
}
//送消息的窗口句柄
inline void set_hwnd(HWND hWnd)
{
_notify_hwnd = hWnd;
}
inline HWND get_hwnd()
{
return _notify_hwnd;
}
inline void set_func(void (*f)(int))
{
_func = f;
}
//關閉線程及串口
virtual void close()
{
if(is_open())
{
_run_flag = false;
SetCommMask(_com_handle, 0);
SetEvent(_wait_o.hEvent);
if(WaitForSingleObject(_thread_handle, 100) != WAIT_OBJECT_0)
TerminateThread(_thread_handle, 0);
CloseHandle(_com_handle);
CloseHandle(_thread_handle);
_thread_handle = NULL;
_com_handle = INVALID_HANDLE_VALUE;
ResetEvent(_wait_o.hEvent);
}
}
/*輔助線程控制*/
//獲得線程句柄
HANDLE get_thread()
{
return _thread_handle;
}
//暫停監視線程
bool suspend()
{
return _thread_handle != NULL ? SuspendThread(_thread_handle) != 0xFFFFFFFF : false;
}
//恢復監視線程
bool resume()
{
return _thread_handle != NULL ? ResumeThread(_thread_handle) != 0xFFFFFFFF : false;
}
//重建監視線程
bool restart()
{
if(_thread_handle) /*只有已有存在線程時*/
{
_run_flag = false;
SetCommMask(_com_handle, 0);
SetEvent(_wait_o.hEvent);
if(WaitForSingleObject(_thread_handle, 100) != WAIT_OBJECT_0)
TerminateThread(_thread_handle, 0);
CloseHandle(_thread_handle);
_run_flag = true;
_thread_handle = NULL;
DWORD id;
_thread_handle = CreateThread(NULL, 0, com_thread, this, 0, &id);
return (_thread_handle != NULL); //輔助線程
}
return false;
}
private:
//監視線程
static DWORD WINAPI com_thread(LPVOID para)
{
_thread_com *pcom = (_thread_com *)para;
if(!SetCommMask(pcom->_com_handle, EV_RXCHAR | EV_ERR))
return 0;
COMSTAT stat;
DWORD error;
for(DWORD length, mask = 0; pcom->_run_flag && pcom->is_open(); mask = 0)
{
if(!WaitCommEvent(pcom->_com_handle, &mask, &pcom->_wait_o))
{
if(GetLastError() == ERROR_IO_PENDING)
{
GetOverlappedResult(pcom->_com_handle, &pcom->_wait_o, &length, true);
}
}
if(mask & EV_ERR) // == EV_ERR
ClearCommError(pcom->_com_handle, &error, &stat);
if(mask & EV_RXCHAR) // == EV_RXCHAR
{
ClearCommError(pcom->_com_handle, &error, &stat);
if(stat.cbInQue > pcom->_notify_num)
pcom->on_receive();
}
}
return 0;
}
};
typedef _thread_com _com; //名稱簡化
#endif //_COM_H_