在实际工程中,是否使用 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
示例代码
#include<windows.h>#include<thread>#include<atomic>#include<cstdio>std::atomic<bool>running(true);/*************** 打印工具函数 ***************/voiddebugPrint(constchar*fmt,...){charbuf[512];va_list args;va_start(args,fmt);vsnprintf(buf,sizeof(buf),fmt,args);va_end(args);OutputDebugStringA(buf);}/*************** 串口接收线程 ***************/voidserialReadThread(HANDLE hSerial){charbuffer[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);}}/*************** 串口发送线程 ***************/voidserialWriteThread(HANDLE hSerial){constchar*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);}}intmain(){// 打开串口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::threadtRead(serialReadThread,hSerial);std::threadtWrite(serialWriteThread,hSerial);// 主线程永久阻塞HANDLE hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);WaitForSingleObject(hEvent,INFINITE);running=false;tRead.join();tWrite.join();CloseHandle(hSerial);return0;}五、这种方式的优缺点
优点
- 不依赖 Qt,不涉及授权问题
- 仅使用 Windows 官方 API
- 可用于任何纯 C++ 工程
- 行为可控、透明
缺点
- 代码量明显增加
- 需要手动管理线程和资源
- 不具备跨平台能力
六、Qt 仍然有价值,但不是唯一解
在授权允许、工程条件合适的情况下,Qt 的QSerialPort依然是非常优秀的选择:
- 代码简洁
- 可维护性高
- 跨平台
但当“不能使用 Qt” 成为硬性条件时,直接使用 Windows API 并不是退而求其次,而是唯一合规且可靠的工程路径。
#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(); }