当项目不让使用 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();
}
相关推荐
是店小二呀3 小时前
Visual Studio C++ 工程架构深度解析:从 .vcxproj 到 Qt MOC 的文件管理实录
c++·qt·visual studio
枫叶丹43 小时前
【Qt开发】Qt系统(十二)-> Qt视频
c语言·开发语言·c++·qt·音视频
浅碎时光8073 小时前
Qt (信号与槽 Widget控件 qrc文件)
开发语言·qt
郝学胜-神的一滴3 小时前
跨平台通信的艺术与哲学:Qt与Linux Socket的深度对话
linux·服务器·开发语言·网络·c++·qt·软件构建
初次见面我叫泰隆13 小时前
Qt——3、常用控件
开发语言·qt·客户端
无小道14 小时前
Qt——QWidget
开发语言·qt
派葛穆17 小时前
Python-PyQt5 安装与配置教程
开发语言·python·qt
初次见面我叫泰隆19 小时前
Qt——4、Qt窗口
开发语言·qt·客户端开发
墨月白20 小时前
[QT]QProcess的相关使用
android·开发语言·qt