在工业软件、设备配置工具或上位机系统中,**固件升级(Firmware Upgrade)**是一个非常常见但又容易出问题的功能点。
典型需求包括:
- 调用厂商提供的 Windows DLL 执行升级
- 升级过程不能阻塞 UI
- 实时显示升级进度
- 升级过程中禁止用户误操作
- 升级完成后给出明确反馈
本文通过一个真实工程级示例 ,完整讲解如何使用 Qt + Windows DLL 实现一个可靠的固件升级进度弹窗。
一、应用场景说明(为什么要这样做)
在实际项目中,固件升级逻辑通常由硬件厂商提供的 DLL完成,例如:
- LVDS / V-by-One 显示控制器
- FPGA / MCU 配置工具
- 工控采集设备
- 显示驱动板卡升级程序
Qt 作为上位机 UI 层,只负责:
- 加载 DLL
- 调用升级接口
- 显示升级状态
- 控制用户交互
升级过程本身必须在后台线程执行,否则 UI 会卡死。
二、设计思路概览
整体架构如下:
- 使用
LoadLibrary / GetProcAddress动态加载 DLL - 在后台线程中调用升级函数
- 通过定时器轮询 DLL 中的升级进度接口
- 使用
QDialog + QProgressBar显示进度 - 升级完成后释放 DLL 并允许用户关闭窗口
这是工业软件中最常见、最稳定的一种升级实现方式。
三、DLL 接口定义说明
假设厂商 DLL 中提供如下接口:
cpp
// 启动固件升级
void UploadProgram(const char* filePath);
// 查询升级进度(0 ~ 100)
int GetUploadProgramSchedule();
Qt 端通过函数指针方式调用。
四、核心实现代码(完整案例)
1. 函数整体结构
cpp
void LVDSVbyOneSignalCollector::firmwareUpInformationBox(QString path)
该函数的职责是:
- 弹出升级对话框
- 启动固件升级
- 显示升级进度
- 等待升级完成
2. 动态加载 DLL
cpp
HMODULE hDll = LoadLibraryA("LVDSVbyOneSignalCollectorDLL.dll");
if (!hDll) {
return;
}
auto pUploadProgram =
(PFN_UploadProgram)GetProcAddress(hDll, "UploadProgram");
auto pGetUploadProgramSchedule =
(PFN_GetUploadProgramSchedule)GetProcAddress(hDll, "GetUploadProgramSchedule");
if (!pUploadProgram || !pGetUploadProgramSchedule) {
FreeLibrary(hDll);
return;
}
为什么使用动态加载?
- DLL 由第三方提供
- 可选功能模块
- 避免启动时强依赖
- 方便版本替换
3. 构建升级 UI(不可中断)
cpp
QDialog* dialog = new QDialog(nullptr);
dialog->setWindowTitle(tr("Firmware Upgrade"));
dialog->setWindowModality(Qt::ApplicationModal);
dialog->setFixedSize(360, 140);
// 禁止右上角关闭
dialog->setWindowFlags(dialog->windowFlags() & ~Qt::WindowCloseButtonHint);
这是一个应用级模态窗口,升级过程中用户无法操作主界面。
4. UI 元素布局
cpp
QLabel* label = new QLabel(tr("Upgrading firmware, please wait..."), dialog);
QProgressBar* progressBar = new QProgressBar(dialog);
progressBar->setRange(0, 100);
progressBar->setValue(0);
QPushButton* btnOk = new QPushButton(tr("OK"), dialog);
btnOk->setEnabled(false);
升级未完成前,OK 按钮不可点击,避免误关闭。
5. 后台线程执行升级(关键)
cpp
QFuture<void> future = QtConcurrent::run([=]() {
pUploadProgram(rInfor.filePath);
});
这里使用 QtConcurrent::run 的目的非常明确:
- 避免 UI 阻塞
- 升级逻辑与界面解耦
- 不需要手写 QThread
这是 Qt 工程中推荐的做法之一。
6. 使用 QTimer 轮询升级进度
cpp
QTimer* timer = new QTimer(dialog);
QObject::connect(timer, &QTimer::timeout, dialog, [=]() {
int value = pGetUploadProgramSchedule(); // 0~100
progressBar->setValue(value);
if (future.isFinished()) {
timer->stop();
progressBar->setValue(100);
label->setText(tr("Firmware upgrade completed."));
btnOk->setEnabled(true);
FreeLibrary(hDll);
}
});
timer->start(300);
这种设计的优点:
- 不需要 DLL 回调
- 逻辑简单、稳定
- 工业项目中极其常见
7. 升级完成后退出
cpp
QObject::connect(btnOk, &QPushButton::clicked, dialog, [=]() {
dialog->accept();
dialog->deleteLater();
});
只有在升级完成后,用户才能关闭窗口。
五、工程级注意事项(非常重要)
1️⃣ 文件路径生命周期问题
cpp
std::string str = path.toStdString();
rInfor.filePath = str.c_str();
如果 DLL 在内部异步使用该指针 ,这里存在风险。
更安全的方式是保证字符串长期有效(如成员变量或深拷贝)。
2️⃣ DLL 卸载时机
FreeLibrary 必须确保:
- 升级线程完全结束
- DLL 内部没有残留线程
否则可能导致随机崩溃。
3️⃣ 建议增加失败与超时处理
生产环境中建议补充:
- 升级失败状态
- 超时检测
- 错误码提示
- 日志记录
六、总结
本文通过一个真实工业项目中的固件升级案例,展示了:
- Qt 如何调用 Windows DLL
- 如何在后台线程执行耗时任务
- 如何安全地显示升级进度
- 如何设计不可中断的升级 UI
这种结构在 工业上位机 / 设备工具 / 显示控制软件 中非常通用,具有很高的工程参考价值。
cpp
void LVDSVbyOneSignalCollector::firmwareUp()
{
QMessageBox msgBox;
msgBox.setWindowTitle("提示");
msgBox.setText("你确定要继续吗?");
msgBox.setIcon(QMessageBox::Question);
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
msgBox.button(QMessageBox::Ok)->setText("更新");
msgBox.button(QMessageBox::Cancel)->setText("取消");
int ret = msgBox.exec();
if (ret == QMessageBox::Ok) {
qDebug() << "用户点击了确定按钮";
QString path = ui.lineEdit_bootPath->text();
QFileInfo fileInfo(path);
if (fileInfo.exists()) {
if (fileInfo.isDir()) {
qDebug() << "路径是有效的目录";
}
else if (fileInfo.isFile()) {
qDebug() << "路径是有效的文件";
}
}
else {
qDebug() << "路径无效";
QMessageBox::information(
this,
"path",
QString("The path is invalid.")
); return;
}
firmwareUpInformationBox(path);
}
else if (ret == QMessageBox::Cancel) {
qDebug() << "用户点击了关闭按钮";
}
}
void LVDSVbyOneSignalCollector::firmwareUpInformationBox(QString path)
{
HMODULE hDll = LoadLibraryA("LVDSVbyOneSignalCollectorDLL.dll");
if (!hDll) {
return;
}
PFN_UploadProgram pUploadProgram =
(PFN_UploadProgram)GetProcAddress(hDll, "UploadProgram");
PFN_GetUploadProgramSchedule pGetUploadProgramSchedule =
(PFN_GetUploadProgramSchedule)GetProcAddress(hDll, "GetUploadProgramSchedule");
if (!pUploadProgram || !pGetUploadProgramSchedule) {
FreeLibrary(hDll);
return;
}
std::string str = path.toStdString();
rInfor.filePath = str.c_str();
/* ================== UI ================== */
QDialog* dialog = new QDialog(nullptr);
dialog->setWindowTitle(tr("Firmware Upgrade"));
dialog->setWindowModality(Qt::ApplicationModal);
dialog->setFixedSize(360, 140);
// 禁止关闭
dialog->setWindowFlags(dialog->windowFlags() & ~Qt::WindowCloseButtonHint);
QLabel* label = new QLabel(tr("Upgrading firmware, please wait..."), dialog);
QProgressBar* progressBar = new QProgressBar(dialog);
progressBar->setRange(0, 100);
progressBar->setValue(0);
QPushButton* btnOk = new QPushButton(tr("OK"), dialog);
btnOk->setEnabled(false); // 完成前不可点击
QVBoxLayout* layout = new QVBoxLayout(dialog);
layout->addWidget(label);
layout->addWidget(progressBar);
layout->addWidget(btnOk, 0, Qt::AlignRight);
dialog->setLayout(layout);
dialog->show();
/* ================== 后台线程 ================== */
QFuture<void> future = QtConcurrent::run([=]() {
pUploadProgram(rInfor.filePath);
});
/* ================== 进度轮询 ================== */
QTimer* timer = new QTimer(dialog);
QObject::connect(timer, &QTimer::timeout, dialog, [=]() {
int value = pGetUploadProgramSchedule(); // 0~100
progressBar->setValue(value);
if (future.isFinished()) {
timer->stop();
progressBar->setValue(100);
label->setText(tr("Firmware upgrade completed."));
btnOk->setEnabled(true);
FreeLibrary(hDll);
}
});
timer->start(300);
/* ================== 完成后退出 ================== */
QObject::connect(btnOk, &QPushButton::clicked, dialog, [=]() {
dialog->accept();
dialog->deleteLater();
});
}