首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

LsComm 串口類 v1.0 設計全紀錄:從封裝動機到四種接收模式實現

     編輯:關於VC++

專案緣起

厭倦 MSComm 等 ActiveX 控件需註冊、部署麻煩,參考 Delphi SpComm 設計風格,二次封裝 PJ Naughter 的 CSerialPort,打造「零註冊、介面極簡、模式可切」的 C++ 串口類庫。

核心架構

類別圖
CComPort 聚合 CSerialPort + 關聯 CReadComThread,四種接收模式由執行緒 Execute() 分派。

四種接收模式完整用法

模式 1:ManualReceiveByQuery(同步查詢)


#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));

模式 2:ManualReceiveByConst(定時阻塞讀取)


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]

模式 3:AutoReceiveBySignal(事件驅動,推薦)


// 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

模式 4:AutoReceiveByBreak(中斷觸發,已棄用)


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);

核心流程源碼解析

Open():建立埠、啟動讀取執行緒


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);
}

Output():同步/非同步寫入統一介面


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;
}

讀取執行緒 Execute():四模式分派


void CReadComThread::Execute() {
    switch (m_pPort->GetReceiveMode()) {
        case ManualReceiveByQuery:  ExecuteByManualQueryRecvMode(); break;
        case ManualReceiveByConst:  ExecuteByManualConstRecvMode(); break;
        case AutoReceiveBySignal:   ExecuteByAutoSignalRecvMode(); break;
        default:                    ExecuteByAutoBreakRecvMode(); break;
    }
}

AutoReceiveBySignal 事件迴圈核心


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;
}

Demo 程式畫面

Demo 介面
支援模式切換、收發顯示、參數設定的完整測試介面

已知限制(v1.0)

  • 流控制未完整測試
  • GetSerialPort() 洩漏封裝,使用者可繞過類別直接關閉埠
  • ManualReceiveByConst 精度受 CommTimeouts 影響
  • 單機環境測試,跨機實測不足

結語

LsComm v1.0 確立了「核心封裝 + 執行緒分派 + 回調介面」的經典架構。歡迎下載原始碼研究、測試、回報 Issue(Milo2002@sohu.com),共同推向 v2.0。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved