在实际工程中,是否使用 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 的前提下,一个实用的串口程序通常需要解决几个问题:
- 如何防止程序一启动就退出
- 如何同时进行发送和接收
- 如何在没有控制台窗口的情况下输出调试信息
典型做法是:
- 使用 线程 分别处理发送和接收
- 使用 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();
}