厭倦 MSComm 等 ActiveX 控件需註冊、部署麻煩,參考 Delphi SpComm 設計風格,二次封裝 PJ Naughter 的 CSerialPort,打造「零註冊、介面極簡、模式可切」的 C++ 串口類庫。
CComPort 聚合 CSerialPort + 關聯 CReadComThread,四種接收模式由執行緒 Execute() 分派。
#include "ComPort.h"
LsComm::CComPort m_ComPort;
m_ComPort.Open(2, LsComm::CComPort::ManualReceiveByQuery);
// 接收輪詢
DWORD cnt = m_ComPort.GetInBufferCount();
if (cnt > 0) {
BYTE buf[2048];
m_ComPort.GetInput(buf, cnt);
}
// 發送
char cmd[] = "abcdefg";
m_ComPort.Output(cmd, sizeof(cmd));
m_ComPort.Open(2, LsComm::CComPort::ManualReceiveByConst);
// 1000ms 內嘗試讀 10 位元組
DWORD cnt = m_ComPort.GetInput(buf, 10, 1000);
if (cnt == 0) return; // 逾時
// 處理 buf[0..cnt-1]
// 1. 定義回調
void OnReceiveData(LPVOID pSender, void* pBuf, DWORD InBufferCount) {
CLsCommDemoDlg* pDlg = (CLsCommDemoDlg*)pSender;
CListBox* pList = (CListBox*)pDlg->GetDlgItem(IDC_LIST1);
// 格式化顯示...
}
// 2. 開啟並註冊
m_ComPort.Open(2, LsComm::CComPort::AutoReceiveBySignal);
m_ComPort.SetReceiveFunc((FOnReceiveData)OnReceiveData, this);
m_ComPort.SetBreakHandleFunc(OnComBreak); // 可選:中斷事件
// 3. 發送同模式 1
m_ComPort.Open(2, LsComm::CComPort::AutoReceiveByBreak);
m_ComPort.SetReceiveFunc((FOnReceiveData)OnReceiveData, this);
// 功能同 Signal 模式,保留相容性
// 取得底層 CSerialPort 指標,直接操作 DCB、Timeouts 等
DCB dcb;
m_ComPort.GetSerialPort()->GetState(dcb);
dcb.BaudRate = 115200;
m_ComPort.GetSerialPort()->SetState(dcb);
void CComPort::Open(int nPort, ReceiveMode mode, DWORD dwBaud, Parity parity,
BYTE DataBits, StopBits stopbits, FlowControl fc) {
m_pPort = new CSerialPort();
m_IsOverlapped = (mode != ManualReceiveByQuery);
m_RecvMode = mode;
// 參數轉換後呼叫底層 Open
m_pPort->Open(nPort, dwBaud, spParity, DataBits, spStopbits, spFC, m_IsOverlapped);
m_pPort->Setup(4096, 4096); // 輸入/輸出緩衝區
m_pPort->Purge(PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
// 超時調整
COMMTIMEOUTS to; m_pPort->GetTimeouts(to);
to.ReadIntervalTimeout = 100;
m_pPort->SetTimeouts(to);
// 建立關閉事件 + 讀取執行緒
m_hCloseEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
m_pReadThread = new CReadComThread();
m_pReadThread->BandSerialPort(this);
m_pReadThread->Create(); m_pReadThread->Resume();
if (m_IsOverlapped) m_hWriteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
}
DWORD CComPort::Output(void* pBuf, DWORD Count) {
DWORD dwWritten = 0;
if (m_IsOverlapped) {
m_pPort->Write(pBuf, Count, m_WriteOverlapped);
if (WaitForSingleObject(m_WriteOverlapped.hEvent, INFINITE) == WAIT_OBJECT_0)
m_pPort->GetOverlappedResult(m_WriteOverlapped, dwWritten, FALSE);
} else {
dwWritten = m_pPort->Write(pBuf, Count);
}
return dwWritten;
}
void CReadComThread::Execute() {
switch (m_pPort->GetReceiveMode()) {
case ManualReceiveByQuery: ExecuteByManualQueryRecvMode(); break;
case ManualReceiveByConst: ExecuteByManualConstRecvMode(); break;
case AutoReceiveBySignal: ExecuteByAutoSignalRecvMode(); break;
default: ExecuteByAutoBreakRecvMode(); break;
}
}
void CReadComThread::ExecuteByAutoSignalRecvMode() {
HANDLE handles[3] = { m_pPort->GetCloseHandle(), m_ReadOverlapped.hEvent, m_BreakOverlapped.hEvent };
m_pPort->GetSerialPort()->SetMask(EV_ERR | EV_RLSD | EV_RING);
SetBreakEvent(dwMask); SetReadEvent(m_ReadOverlapped);
for (;;) {
DWORD sig = WaitForMultipleObjects(3, handles, FALSE, INFINITE);
switch (sig) {
case WAIT_OBJECT_0: goto EndThread; // 關閉事件
case WAIT_OBJECT_0+1: // 讀取事件
if (!HandleReadEvent(m_ReadOverlapped)) goto EndThread;
if (!SetReadEvent(m_ReadOverlapped)) goto EndThread;
break;
case WAIT_OBJECT_0+2: // 中斷事件
if (!HandleBreakEvent(dwMask)) goto EndThread;
if (!SetBreakEvent(dwMask)) goto EndThread;
break;
}
}
EndThread:
m_pPort->GetSerialPort()->Purge(PURGE_RXABORT | PURGE_RXCLEAR);
CloseHandle(m_ReadOverlapped.hEvent);
CloseHandle(m_BreakOverlapped.hEvent);
}
bool CReadComThread::HandleReadEvent(OVERLAPPED& ov) {
if (m_pPort->GetSerialPort()->GetOverlappedResult(ov, m_InBufferCount, FALSE))
return HandleData();
return (GetLastError() != ERROR_INVALID_HANDLE);
}
bool CReadComThread::HandleData() {
if (m_InBufferCount > 0) {
m_pBuffer = new BYTE[m_InBufferCount];
memcpy(m_pBuffer, m_InputBuffer, m_InBufferCount);
m_pPort->ReceiveData(m_pBuffer, m_InBufferCount); // 呼叫使用者回調
delete[] m_pBuffer;
}
return true;
}
支援模式切換、收發顯示、參數設定的完整測試介面
GetSerialPort() 洩漏封裝,使用者可繞過類別直接關閉埠ManualReceiveByConst 精度受 CommTimeouts 影響LsComm v1.0 確立了「核心封裝 + 執行緒分派 + 回調介面」的經典架構。歡迎下載原始碼研究、測試、回報 Issue(Milo2002@sohu.com),共同推向 v2.0。