串口 (COM 口) 通訊是工業控制、儀器儀表、嵌入式互聯的基石。VC++ 提供兩條主流路徑:Microsoft Comm Control 6.0 (MSComm32.ocx) ActiveX 控件 簡單快速,Win32 API (CreateFile/ReadFile/WriteFile/DeviceIoControl) 底層可控、無運行時依賴。本文對比兩者架構、代碼範例、優劣勢、避坑指南。
MSComm m_Comm。// OnInitDialog
m_Comm.SetCommPort(1); // COM1
m_Comm.SetSettings(_T("9600,n,8,1")); // 波特率,校驗,資料位,停止位
m_Comm.SetPortOpen(TRUE); // 打開端口
m_Comm.SetInBufferCount(0); // 清空輸入緩衝
m_Comm.SetInputLen(0); // 0=讀取所有可用字元
m_Comm.SetRThreshold(1); // 觸發 OnComm 事件的最小字元數
ClassWizard 為控件添加 OnComm 事件處理:
void CMyDlg::OnComm() {
switch (m_Comm.GetCommEvent()) {
case comEventReceive: { // 接收到數據
VARIANT var = m_Comm.GetInput(); // 讀取輸入緩衝
// var 為 VT_ARRAY | VT_UI1 (BYTE 陣列) 或 VT_BSTR
// 處理數據...
break;
}
case comEventSend: break; // 發送完成
case comEventBreak: break; // 斷線
}
}
CString cmd = _T("GET_DATA\r\n");
m_Comm.SetOutput(COleVariant(cmd)); // 自動轉 BSTR
// 或二進位:
BYTE data[] = {0x01, 0x02, 0x03};
VARIANT var; var.vt = VT_ARRAY | VT_UI1;
var.parray = SafeArrayCreateVector(VT_UI1, 0, 3);
memcpy(var.parray->pvData, data, 3);
m_Comm.SetOutput(var);
m_Comm.SetPortOpen(FALSE); // OnDestroy 中調用
HANDLE hCom = CreateFile(
_T("COM1"), // 埠名 (COM10+ 需用 \\\\.\\COM10)
GENERIC_READ | GENERIC_WRITE, // 讀寫
0, // 獨佔
NULL, // 安全屬性
OPEN_EXISTING, // 必須存在
FILE_FLAG_OVERLAPPED, // 關鍵:異步 I/O (串口必須)
NULL
);
if (hCom == INVALID_HANDLE_VALUE) { /* 錯誤處理 */ }
DCB dcb = {0}; dcb.DCBlength = sizeof(DCB);
GetCommState(hCom, &dcb); // 獲取當前
dcb.BaudRate = CBR_9600;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
dcb.fBinary = TRUE;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
SetCommState(hCom, &dcb);
COMMTIMEOUTS ct = {0};
// 讀取: 總計最多等待 100ms, 每字元間隔 10ms
ct.ReadIntervalTimeout = 10;
ct.ReadTotalTimeoutMultiplier = 10;
ct.ReadTotalTimeoutConstant = 100;
// 寫入: 總計最多 1000ms
ct.WriteTotalTimeoutMultiplier = 10;
ct.WriteTotalTimeoutConstant = 1000;
SetCommTimeouts(hCom, &ct);
// 讀取
OVERLAPPED ovRead = {0}; ovRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
BYTE buf[256]; DWORD read;
BOOL ok = ReadFile(hCom, buf, 256, &read, &ovRead);
if (!ok && GetLastError() == ERROR_IO_PENDING) {
// 等待完成 (或用 WaitForMultipleObjects 整合到主訊息迴路)
WaitForSingleObject(ovRead.hEvent, INFINITE);
GetOverlappedResult(hCom, &ovRead, &read, TRUE);
// buf[0..read-1] 為接收數據
}
// 寫入
OVERLAPPED ovWrite = {0}; ovWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
BYTE cmd[] = {0xAA, 0x55}; DWORD written;
WriteFile(hCom, cmd, 2, &written, &ovWrite);
// 同樣等待 ovWrite.hEvent
// 執行緒循環等待事件 (RXCHAR, TXEMPTY, BREAK, ERR...)
DWORD evtMask;
WaitCommEvent(hCom, &evtMask, &ovRead); // 異步等待
if (evtMask & EV_RXCHAR) { /* 讀取數據 */ }
CloseHandle(hCom);
CloseHandle(ovRead.hEvent); CloseHandle(ovWrite.hEvent);
| 維度 | MSComm ActiveX | Win32 API |
|---|---|---|
| 部署依賴 | 需註冊 MSCOMM32.OCX (系統目錄) | 純系統 DLL (kernel32.dll), 零依賴 |
| 程式模型 | 事件驅動 (OnComm), 易上手 | 同步/異步 API, 需自行管理執行緒/重疊 I/O |
| 效能/延遲 | ActiveX 包裝層有開銷 | 零開銷, 直接核心轉換 |
| 功能完整性 | 基本功能覆蓋, 進階控制 (DTR/RTS 細粒度、特殊波特率) 受限 | 完全控制 (DCB、Timeouts、Modem 狀態、特殊 IOCTL) |
| 穩定性 | OCX 相容性問題 (64位、Win10+ 易失效) | 系統級 API, 極度穩定 |
| 調試 | Visual Studio 整合良好 | 需自行記錄/斷點, 較繁瑣 |
| 適用場景 | 快速原型、簡單儀表、教學、舊專案維護 | 產品級、高可靠、高效能、64位、長期維護專案 |
\\\\.\\COM10 格式 (Win32) 或 MSComm SetCommPort(10) (6.0 支援)。PurgeComm(hCom, PURGE_RXCLEAR | PURGE_TXCLEAR)。Dialout 群組或管理員 (Linux/Unix),Windows 通常需管理員或 Serial Port 裝置權限。現代專案強烈建議使用 Win32 API 方案,構建專用 CSerialPort 封裝類管理生命週期、異步讀寫、協定解析,獲得最佳穩定性與可移植性。