当项目不让使用 Qt!如何实现串口通信?

在实际工程中,是否使用 Qt 往往并不是技术人员单方面能决定的事情。

常见的限制包括:

  • 商业项目对 Qt LGPL / 商业授权 的顾虑
  • 项目交付形式不允许引入大型框架
  • 甲方或合规部门明确要求 禁止使用 Qt
  • 工程本身是纯 C / C++ 工具链,不希望引入额外依赖

在这种背景下,串口通信 依然是绕不开的需求。

当项目因版权或授权原因不能使用 Qt 时,如何在 Windows 平台上用纯 C++ 实现稳定的串口通信?


一、为什么不能使用 Qt 会影响串口实现?

Qt 提供了 QSerialPort,使用起来非常方便:

  • 自动管理线程
  • 信号槽驱动
  • 跨平台

但它的前提是:

  • 项目已经使用 Qt
  • 授权模型可接受
  • 可以接受引入完整 Qt 运行环境

一旦这些前提不成立,QSerialPort 就不再是可选项。

这时,唯一可靠的选择就是:

直接使用 Windows API 提供的串口接口


二、Windows 下串口通信的"官方路径"

在 Windows 系统中,串口本质上是一个设备文件,可以像普通文件一样操作。

核心 API 包括:

  • CreateFile ------ 打开串口
  • GetCommState / SetCommState ------ 配置串口参数
  • ReadFile / WriteFile ------ 数据收发
  • COMMTIMEOUTS ------ 读写超时控制

这些接口全部属于 Windows SDK,不涉及任何第三方库或授权问题。


三、设计一个可用的纯 C++ 串口程序

在不使用 Qt 的前提下,一个实用的串口程序通常需要解决几个问题:

  1. 如何防止程序一启动就退出
  2. 如何同时进行发送和接收
  3. 如何在没有控制台窗口的情况下输出调试信息

典型做法是:

  • 使用 线程 分别处理发送和接收
  • 使用 WinAPI 事件 阻塞主线程
  • 使用 OutputDebugString 输出调试信息

四、完整示例:纯 C++ / Windows API 串口通信

下面是一份完整、可直接使用的示例代码,实现了:

  • 打开串口
  • 定时发送数据
  • 接收并打印数据
  • 程序长期运行,不依赖 Qt

示例代码

cpp 复制代码
#include <windows.h>
#include <thread>
#include <atomic>
#include <cstdio>

std::atomic<bool> running(true);

/*************** 打印工具函数 ***************/
void debugPrint(const char* fmt, ...)
{
    char buf[512];
    va_list args;
    va_start(args, fmt);
    vsnprintf(buf, sizeof(buf), fmt, args);
    va_end(args);

    OutputDebugStringA(buf);
}

/*************** 串口接收线程 ***************/
void serialReadThread(HANDLE hSerial)
{
    char buffer[256];
    DWORD bytesRead;

    while (running)
    {
        if (ReadFile(hSerial, buffer, sizeof(buffer) - 1, &bytesRead, NULL))
        {
            if (bytesRead > 0)
            {
                buffer[bytesRead] = '\0';
                debugPrint("[RX] %s\n", buffer);
            }
        }
        Sleep(10);
    }
}

/*************** 串口发送线程 ***************/
void serialWriteThread(HANDLE hSerial)
{
    const char* msg = "Hello Serial\r\n";
    DWORD bytesWritten;

    while (running)
    {
        BOOL ok = WriteFile(hSerial, msg, strlen(msg), &bytesWritten, NULL);
        if (ok && bytesWritten > 0)
        {
            debugPrint("[TX] %s", msg);
        }
        else
        {
            debugPrint("[TX] 发送失败,错误码=%lu\n", GetLastError());
        }

        Sleep(1000);
    }
}

int main()
{
    // 打开串口
    HANDLE hSerial = CreateFile(
        L"\\\\.\\COM8",   // ← 改成你的串口号
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        0,
        NULL
        );

    if (hSerial == INVALID_HANDLE_VALUE)
    {
        debugPrint("串口打开失败,错误码=%lu\n", GetLastError());
        return -1;
    }

    debugPrint("串口打开成功\n");

    // 配置串口
    DCB dcb = { 0 };
    dcb.DCBlength = sizeof(dcb);
    GetCommState(hSerial, &dcb);

    dcb.BaudRate = CBR_9600;
    dcb.ByteSize = 8;
    dcb.StopBits = ONESTOPBIT;
    dcb.Parity   = NOPARITY;

    SetCommState(hSerial, &dcb);

    // 超时
    COMMTIMEOUTS timeouts = { 0 };
    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutConstant = 50;
    timeouts.ReadTotalTimeoutMultiplier = 10;
    SetCommTimeouts(hSerial, &timeouts);

    // 启动线程
    std::thread tRead(serialReadThread, hSerial);
    std::thread tWrite(serialWriteThread, hSerial);

    // 主线程永久阻塞
    HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    WaitForSingleObject(hEvent, INFINITE);

    running = false;
    tRead.join();
    tWrite.join();
    CloseHandle(hSerial);

    return 0;
}

五、这种方式的优缺点

优点

  • 不依赖 Qt,不涉及授权问题
  • 仅使用 Windows 官方 API
  • 可用于任何纯 C++ 工程
  • 行为可控、透明

缺点

  • 代码量明显增加
  • 需要手动管理线程和资源
  • 不具备跨平台能力

六、Qt 仍然有价值,但不是唯一解

授权允许、工程条件合适 的情况下,Qt 的 QSerialPort 依然是非常优秀的选择:

  • 代码简洁
  • 可维护性高
  • 跨平台

但当 "不能使用 Qt" 成为硬性条件 时,直接使用 Windows API 并不是退而求其次,而是唯一合规且可靠的工程路径

Qt 复制代码
#include <QCoreApplication>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTimer>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QSerialPort serial;

    // 1. 串口参数
    serial.setPortName("COM8");      // 串口号
    serial.setBaudRate(QSerialPort::Baud9600);
    serial.setDataBits(QSerialPort::Data8);
    serial.setStopBits(QSerialPort::OneStop);
    serial.setParity(QSerialPort::NoParity);
    serial.setFlowControl(QSerialPort::NoFlowControl);

    // 2. 打开串口
    if (!serial.open(QIODevice::ReadWrite))
    {
        qDebug() << "串口打开失败:" << serial.errorString();
        return -1;
    }

    qDebug() << "串口打开成功";

    // 3. 接收数据(信号槽)
    QObject::connect(&serial, &QSerialPort::readyRead, [&]()
                     {
                         QByteArray data = serial.readAll();
                         qDebug() << "[RX]" << data;
                     });

    // 4. 定时发送
    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, [&]()
                     {
                         QByteArray msg = "Hello Serial";
                         serial.write(msg);
                         qDebug() << "[TX]" << msg;
                     });

    timer.start(1000);   // 1 秒发送一次


    return a.exec();
}
相关推荐
用户8055336980312 小时前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner12 小时前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz6 天前
QML Hello World 入门示例
qt
xcyxiner9 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner9 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner9 天前
DicomViewer (添加模型类)3
qt
xcyxiner10 天前
DicomViewer (目录调整) 2
qt
xcyxiner10 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能12 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G12 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt