最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于C++實現(xiàn)輕量且線程安全的Windows串口通信封裝類

 更新時間:2026年04月14日 09:30:18   作者:沈躍泉  
這篇文章主要為大家詳細介紹了如何基于C++實現(xiàn)輕量且線程安全的Windows串口通信封裝類,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下

概述

在 Windows 平臺下操作串口,需要調用 Win32 API CreateFileReadFile、WriteFile 等,代碼稍顯繁瑣。本類對串口的打開、配置、讀寫操作進行封裝,提供簡潔的 C++ 接口,并內置了接收線程和回調機制。

源碼共 3 個文件:

文件說明
SerialPort.h類聲明
SerialPort.cpp完整實現(xiàn)
main.cpp使用示例

頭文件SerialPort.h

#pragma once
#include <windows.h>
#include <string>
#include <vector>
#include <thread>
#include <atomic>
#include <functional>
#include <mutex>
class SerialPort {
public:
    SerialPort();
    ~SerialPort();
    bool open(const std::string& portName, unsigned long baudRate);
    void close();
    bool isOpen() const;
    std::string getLastError() const;
    // 發(fā)送數(shù)據(jù)(線程安全)
    bool write(const std::vector<unsigned char>& data);
    bool write(const std::string& s);
    // 設置接收回調
    void setReceiveCallback(std::function<void(const std::vector<unsigned char>&)> cb);
private:
    void receiveLoop();
    HANDLE m_handle;
    std::atomic<bool> m_running;
    std::thread m_thread;
    std::string m_lastError;
    std::function<void(const std::vector<unsigned char>&)> m_callback;
    std::mutex m_writeMutex;
};

設計要點

  • std::atomic<bool> m_running:跨線程共享的運行標志,比 volatile bool 更安全。
  • std::mutex m_writeMutex:寫操作加鎖,保證多線程并發(fā)調用 write() 時的線程安全。
  • std::function 回調:用戶可通過 lambda、函數(shù)指針等靈活注冊數(shù)據(jù)接收處理邏輯。
  • RAII 析構close() 在析構函數(shù)中自動調用,確保資源釋放。

實現(xiàn)SerialPort.cpp

構造函數(shù)與析構

SerialPort::SerialPort()
    : m_handle(INVALID_HANDLE_VALUE), m_running(false)
{
}

SerialPort::~SerialPort()
{
    close();
}

析構調用 close(),保證對象銷毀時串口一定被關閉。

錯誤信息輔助函數(shù)

static std::string GetLastErrorAsString(DWORD err)
{
    if (err == 0) return std::string();
    LPSTR messageBuffer = nullptr;
    DWORD size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, nullptr);
    std::string message;
    if (messageBuffer && size > 0) message.assign(messageBuffer, size);
    if (messageBuffer) LocalFree(messageBuffer);
    return message;
}

將 Windows API 的錯誤碼轉換為可讀的字符串,供調試使用。

打開串口open()

bool SerialPort::open(const std::string& portName, unsigned long baudRate)
{
    if (isOpen()) return true;

    std::string fullName = portName;
    if (portName.rfind("\\\\.", 0) != 0) {
        fullName = "\\\\.\\" + portName;
    }

    m_handle = CreateFileA(fullName.c_str(),
        GENERIC_READ | GENERIC_WRITE,
        0, nullptr, OPEN_EXISTING, 0, nullptr);
  1. 路徑格式:Windows 上超過 COM9 的串口名(如 COM10、COM\.\ PHYSICALCOM0)需要加 \\.\ 前綴。代碼自動補全。
  2. 同步模式:使用同步 I/O,接收由獨立線程負責,避免阻塞主線程。
    DCB dcb;
    SecureZeroMemory(&dcb, sizeof(dcb));
    dcb.DCBlength = sizeof(dcb);
    if (!GetCommState(m_handle, &dcb)) { /* ... */ }

    dcb.BaudRate = baudRate;
    dcb.ByteSize = 8;
    dcb.Parity = NOPARITY;
    dcb.StopBits = ONESTOPBIT;
    if (!SetCommState(m_handle, &dcb)) { /* ... */ }

通過 DCB 結構配置波特率、數(shù)據(jù)位、校驗位、停止位,默認 8N1。

    COMMTIMEOUTS timeouts;
    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutMultiplier = 0;
    timeouts.ReadTotalTimeoutConstant = 50;
    timeouts.WriteTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutConstant = 50;
    SetCommTimeouts(m_handle, &timeouts);

    PurgeComm(m_handle, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);

    m_running = true;
    m_thread = std::thread(&SerialPort::receiveLoop, this);
    return true;
}
  • 超時設置:Read 每次最多等待 50ms,防止 ReadFile 永久阻塞。
  • 清空緩沖區(qū)PurgeComm 丟棄舊數(shù)據(jù)。
  • 啟動接收線程receiveLoop() 在獨立線程中運行。

關閉串口close()

void SerialPort::close()
{
    if (!isOpen()) return;

    m_running = false;
    CancelIoEx(m_handle, nullptr);   // 取消阻塞中的 IO

    if (m_thread.joinable()) m_thread.join();

    if (m_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(m_handle);
        m_handle = INVALID_HANDLE_VALUE;
    }
}

關鍵點:

  1. m_running = false 通知接收線程退出。
  2. CancelIoEx 中斷 ReadFile,配合超時設置使線程盡快退出。
  3. join() 等待線程結束,避免析構時線程仍運行。
  4. 最后才 CloseHandle,保證線程已安全退出。

發(fā)送數(shù)據(jù)write()

bool SerialPort::write(const std::vector<unsigned char>& data)
{
    if (!isOpen()) { m_lastError = "Port not open"; return false; }

    std::lock_guard<std::mutex> lock(m_writeMutex);

    DWORD bytesWritten = 0;
    BOOL ok = WriteFile(m_handle, data.data(), static_cast<DWORD>(data.size()), &bytesWritten, nullptr);
    if (!ok) { m_lastError = "WriteFile failed: " + GetLastErrorAsString(GetLastError()); return false; }

    return bytesWritten == data.size();
}

bool SerialPort::write(const std::string& s)
{
    return write(std::vector<unsigned char>(s.begin(), s.end()));
}
  • 寫鎖std::lock_guard 保證多線程同時調用 write() 時不會產生競態(tài)。
  • 兩個重載:一個接受字節(jié)數(shù)組,一個接受字符串,使用更方便。

接收回調setReceiveCallback()

void SerialPort::setReceiveCallback(std::function<void(const std::vector<unsigned char>&)> cb)
{
    m_callback = std::move(cb);
}

使用 std::move 避免不必要的拷貝。

接收線程receiveLoop()

void SerialPort::receiveLoop()
{
    const DWORD bufSize = 1024;
    std::vector<unsigned char> buffer(bufSize);

    while (m_running && isOpen()) {
        DWORD bytesRead = 0;
        BOOL ok = ReadFile(m_handle, buffer.data(), bufSize, &bytesRead, nullptr);
        if (!ok) {
            DWORD err = GetLastError();
            if (err != ERROR_IO_PENDING && err != ERROR_TIMEOUT && err != ERROR_SUCCESS) {
                m_lastError = "ReadFile failed: " + GetLastErrorAsString(err);
                break;
            }
        }

        if (bytesRead > 0) {
            std::vector<unsigned char> data(buffer.begin(), buffer.begin() + bytesRead);
            if (m_callback) {
                try { m_callback(data); }
                catch (...) { /* 忽略回調異常 */ }
            }
            else {
                // 默認打印:可打印字符 + HEX
                std::cout << "[串口接收] 字符: " << printable << "  HEX: " << ossHex.str() << std::endl;
            }
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

邏輯說明:

  1. 循環(huán)讀取,直到 m_running 為 false 或發(fā)生錯誤。
  2. ReadFile 在超時設置下最多阻塞 50ms,之后返回,即使未讀到任何數(shù)據(jù)。
  3. 讀到數(shù)據(jù)后:優(yōu)先調用用戶回調;無回調時默認打印 HEX + 可打印字符。
  4. 回調異常捕獲:防止用戶回調中的崩潰影響串口接收線程。
  5. 每次循環(huán) sleep_for(10ms) 降低 CPU 占用。

完整源碼

SerialPort.h

#pragma once
#include <windows.h>
#include <string>
#include <vector>
#include <thread>
#include <atomic>
#include <functional>
#include <mutex>

class SerialPort {
public:
    SerialPort();
    ~SerialPort();

    bool open(const std::string& portName, unsigned long baudRate);
    void close();
    bool isOpen() const;
    std::string getLastError() const;

    // 發(fā)送數(shù)據(jù)(線程安全)
    bool write(const std::vector<unsigned char>& data);
    bool write(const std::string& s);

    // 設置接收回調
    void setReceiveCallback(std::function<void(const std::vector<unsigned char>&)> cb);

private:
    void receiveLoop();

    HANDLE m_handle;
    std::atomic<bool> m_running;
    std::thread m_thread;
    std::string m_lastError;
    std::function<void(const std::vector<unsigned char>&)> m_callback;
    std::mutex m_writeMutex;
};

SerialPort.cpp

#include "SerialPort.h"
#include <iostream>
#include <sstream>
#include <chrono>
#include <iomanip>
#include <mutex>

SerialPort::SerialPort()
    : m_handle(INVALID_HANDLE_VALUE), m_running(false)
{
}

SerialPort::~SerialPort()
{
    close();
}

static std::string GetLastErrorAsString(DWORD err)
{
    if (err == 0) return std::string();
    LPSTR messageBuffer = nullptr;
    DWORD size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, nullptr);
    std::string message;
    if (messageBuffer && size > 0) message.assign(messageBuffer, size);
    if (messageBuffer) LocalFree(messageBuffer);
    return message;
}

bool SerialPort::open(const std::string& portName, unsigned long baudRate)
{
    if (isOpen()) return true;

    std::string fullName = portName;
    if (portName.rfind("\\\\.", 0) != 0) {
        fullName = "\\\\.\\" + portName;
    }

    m_handle = CreateFileA(fullName.c_str(),
        GENERIC_READ | GENERIC_WRITE,
        0,
        nullptr,
        OPEN_EXISTING,
        0,
        nullptr);

    if (m_handle == INVALID_HANDLE_VALUE) {
        m_lastError = "CreateFile failed: " + GetLastErrorAsString(GetLastError());
        return false;
    }

    DCB dcb;
    SecureZeroMemory(&dcb, sizeof(dcb));
    dcb.DCBlength = sizeof(dcb);
    if (!GetCommState(m_handle, &dcb)) {
        m_lastError = "GetCommState failed: " + GetLastErrorAsString(GetLastError());
        CloseHandle(m_handle);
        m_handle = INVALID_HANDLE_VALUE;
        return false;
    }

    dcb.BaudRate = baudRate;
    dcb.ByteSize = 8;
    dcb.Parity = NOPARITY;
    dcb.StopBits = ONESTOPBIT;
    if (!SetCommState(m_handle, &dcb)) {
        m_lastError = "SetCommState failed: " + GetLastErrorAsString(GetLastError());
        CloseHandle(m_handle);
        m_handle = INVALID_HANDLE_VALUE;
        return false;
    }

    COMMTIMEOUTS timeouts;
    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutMultiplier = 0;
    timeouts.ReadTotalTimeoutConstant = 50;
    timeouts.WriteTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutConstant = 50;
    SetCommTimeouts(m_handle, &timeouts);

    PurgeComm(m_handle, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);

    m_running = true;
    m_thread = std::thread(&SerialPort::receiveLoop, this);
    return true;
}

void SerialPort::close()
{
    if (!isOpen()) return;

    m_running = false;

    // 取消可能的阻塞 IO,嘗試使 ReadFile 返回
    CancelIoEx(m_handle, nullptr);

    if (m_thread.joinable()) m_thread.join();

    if (m_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(m_handle);
        m_handle = INVALID_HANDLE_VALUE;
    }
}

bool SerialPort::isOpen() const
{
    return m_handle != INVALID_HANDLE_VALUE;
}

std::string SerialPort::getLastError() const
{
    return m_lastError;
}

bool SerialPort::write(const std::vector<unsigned char>& data)
{
    if (!isOpen()) {
        m_lastError = "Port not open";
        return false;
    }

    std::lock_guard<std::mutex> lock(m_writeMutex);

    DWORD bytesWritten = 0;
    BOOL ok = WriteFile(m_handle, data.data(), static_cast<DWORD>(data.size()), &bytesWritten, nullptr);
    if (!ok) {
        m_lastError = "WriteFile failed: " + GetLastErrorAsString(GetLastError());
        return false;
    }

    return bytesWritten == data.size();
}

bool SerialPort::write(const std::string& s)
{
    return write(std::vector<unsigned char>(s.begin(), s.end()));
}

void SerialPort::setReceiveCallback(std::function<void(const std::vector<unsigned char>&)> cb)
{
    m_callback = std::move(cb);
}

void SerialPort::receiveLoop()
{
    const DWORD bufSize = 1024;
    std::vector<unsigned char> buffer(bufSize);

    while (m_running && isOpen()) {
        DWORD bytesRead = 0;
        BOOL ok = ReadFile(m_handle, buffer.data(), bufSize, &bytesRead, nullptr);
        if (!ok) {
            DWORD err = GetLastError();
            if (err != ERROR_IO_PENDING && err != ERROR_TIMEOUT && err != ERROR_SUCCESS) {
                m_lastError = "ReadFile failed: " + GetLastErrorAsString(err);
                break;
            }
        }

        if (bytesRead > 0) {
            std::vector<unsigned char> data(buffer.begin(), buffer.begin() + bytesRead);
            if (m_callback) {
                try {
                    m_callback(data);
                }
                catch (...) {
                    // 忽略回調異常
                }
            }
            else {
                std::ostringstream ossHex;
                std::string printable;
                for (unsigned char b : data) {
                    if (b >= 0x20 && b <= 0x7E) printable.push_back(static_cast<char>(b));
                    else printable.push_back('.');
                    ossHex << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
                        << static_cast<int>(b) << ' ';
                }
                // 注意:此處為線程中打印,若需線程安全或按序輸出可改為其他機制
                std::cout << "[串口接收] 字符: " << printable << "  HEX: " << ossHex.str() << std::endl;
            }
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

使用示例main.cpp

#include <conio.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <thread>
#include "SerialPort.h"
#include <io.h>
#include <fcntl.h>
#include <windows.h>

void recvFunc(const std::vector<unsigned char>& data)
{
    std::ostringstream ossHex;
    std::string printable;
    for (unsigned char b : data) {
        if (b >= 0x20 && b <= 0x7E) printable.push_back(static_cast<char>(b));
        else printable.push_back('.');
        ossHex << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
            << static_cast<int>(b) << ' ';
    }
    // use ASCII prefix to avoid encoding issues in callback thread
    std::cout << "[Receive] ASCII: " << printable << "  HEX: " << ossHex.str() << std::endl;
}

int main()
{
    // 演示串口類的簡單使用(需要真實串口才能收到數(shù)據(jù))
    SerialPort sp;
    // 示例:打開 COM3,115200 波特(根據(jù)實際串口修改)
    if (sp.open("COM3", CBR_115200)) {
        std::cout << "start recv" << std::endl;

        // Start the receive thread
		sp.setReceiveCallback(recvFunc);

        // 示例:發(fā)送字節(jié) 0x55 0xAA
        {
            std::vector<unsigned char> pkt = { 0x55, 0xAA };
            if (sp.write(pkt)) {
                std::cout << "已發(fā)送: 0x55 0xAA" << std::endl;
            } else {
                std::cout << "發(fā)送失敗: " << sp.getLastError() << std::endl;
            }
        }

        system("pause");

        sp.close();
        std::cout << "串口已關閉。\n";
    }
    else {
        std::cout << "打開串口失敗:\n";
        std::cout << sp.getLastError() << "\n";
    }

    return 0;
}

以上就是基于C++實現(xiàn)輕量且線程安全的Windows串口通信封裝類的詳細內容,更多關于C++串口通信類的資料請關注腳本之家其它相關文章!

相關文章

  • STL中的string你了解嗎

    STL中的string你了解嗎

    這篇文章主要為大家詳細介紹了STL中的string,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • VC實現(xiàn)給窗體的一個按鈕添加事件的方法

    VC實現(xiàn)給窗體的一個按鈕添加事件的方法

    這篇文章主要介紹了VC實現(xiàn)給窗體的一個按鈕添加事件的方法,通過三個簡單步驟實現(xiàn)窗體按鈕添加事件,需要的朋友可以參考下
    2015-05-05
  • C語言實現(xiàn)哈希搜索算法及原理詳解

    C語言實現(xiàn)哈希搜索算法及原理詳解

    本文主要介紹了C語言實現(xiàn)哈希搜索算法及原理詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-06-06
  • 詳解C語言初階基礎

    詳解C語言初階基礎

    這篇文章主要介紹了C語言中的初階基礎,介紹了其相關概念,具有一定參考價值。需要的朋友可以了解下,希望能夠給你帶來幫助
    2021-11-11
  • 使用C++和代理IP實現(xiàn)天氣預報的采集

    使用C++和代理IP實現(xiàn)天氣預報的采集

    在當今的互聯(lián)網(wǎng)時代,網(wǎng)絡信息的獲取變得日益重要,天氣預報信息作為日常生活的重要參考,其獲取方式也隨著技術的發(fā)展而不斷變化,在本文中,我們將探討如何使用C++和代理IP來采集天氣預報信息,文中通過代碼講解的非常詳細,需要的朋友可以參考下
    2023-12-12
  • 用C語言畫一個圓

    用C語言畫一個圓

    大家好,本篇文章主要講的是用C語言畫一個圓,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2022-01-01
  • C++中的 .h 和 .cpp詳解(推薦)

    C++中的 .h 和 .cpp詳解(推薦)

    在C++編程過程中,隨著項目的越來越大,代碼也會越來越多,并且難以管理和分析,于是,在C++中就要分出了頭(.h)文件和實現(xiàn)(.cpp)文件,并且也有了Package的概念,這篇文章主要介紹了C++中的.h和.cpp詳解,需要的朋友可以參考下
    2020-11-11
  • 基于Matlab實現(xiàn)數(shù)字音頻分析處理系統(tǒng)

    基于Matlab實現(xiàn)數(shù)字音頻分析處理系統(tǒng)

    這篇文章主要為大家介紹了如何利用Matlab制作一個帶GUI的數(shù)字音頻分析與處理系統(tǒng)。文中的示例代碼講解詳細,感興趣的小伙伴可以學習一下
    2022-02-02
  • C++中輸入輸出流及文件流操作總結

    C++中輸入輸出流及文件流操作總結

    這篇文章主要為大家總結了C++中輸入輸出流及文件流操作,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • C++中防止頭文件重復包含的幾種方法

    C++中防止頭文件重復包含的幾種方法

    在 C/C++ 編程中,當一個項目比較大時,往往都是分文件,這時候有可能不小心把同一個頭文件 include 多次,或者頭文件嵌套包含,這些會導致一系列的問題,如符號重定義、編譯錯誤等,因此,防止頭文件的重復包含是至關重要的,本文給大家介紹了C++中防止頭文件重復包含的兩種方法
    2024-05-05

最新評論

梨树县| 海盐县| 云阳县| 通许县| 海安县| 金华市| 沧州市| 松滋市| 佛山市| 汉阴县| 高陵县| 上蔡县| 苏尼特左旗| 保康县| 昆山市| 兴山县| 通化县| 化德县| 怀柔区| 旬阳县| 新乐市| 特克斯县| 昆山市| 余姚市| 赣州市| 四子王旗| 吴桥县| 乌恰县| 璧山县| 南召县| 香港 | 永嘉县| 宣武区| 报价| 汶川县| 旅游| 斗六市| 玉屏| 洮南市| 横峰县| 岗巴县|