Qt 调用 DLL 实现固件升级进度弹窗(完整实战案例)

在工业软件、设备配置工具或上位机系统中,**固件升级(Firmware Upgrade)**是一个非常常见但又容易出问题的功能点。

典型需求包括:

  • 调用厂商提供的 Windows DLL 执行升级
  • 升级过程不能阻塞 UI
  • 实时显示升级进度
  • 升级过程中禁止用户误操作
  • 升级完成后给出明确反馈

本文通过一个真实工程级示例 ,完整讲解如何使用 Qt + Windows DLL 实现一个可靠的固件升级进度弹窗


一、应用场景说明(为什么要这样做)

在实际项目中,固件升级逻辑通常由硬件厂商提供的 DLL完成,例如:

  • LVDS / V-by-One 显示控制器
  • FPGA / MCU 配置工具
  • 工控采集设备
  • 显示驱动板卡升级程序

Qt 作为上位机 UI 层,只负责:

  • 加载 DLL
  • 调用升级接口
  • 显示升级状态
  • 控制用户交互

升级过程本身必须在后台线程执行,否则 UI 会卡死。


二、设计思路概览

整体架构如下:

  1. 使用 LoadLibrary / GetProcAddress 动态加载 DLL
  2. 在后台线程中调用升级函数
  3. 通过定时器轮询 DLL 中的升级进度接口
  4. 使用 QDialog + QProgressBar 显示进度
  5. 升级完成后释放 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();
        });
}
相关推荐
胖咕噜的稞达鸭2 小时前
【C语言进阶】死磕指针:从内存原理到指针数组的深度解析
c语言·开发语言·网络
lly2024062 小时前
Pandas 相关性分析
开发语言
潘达斯奈基~2 小时前
spark性能优化3:小文件问题
大数据·性能优化·spark
CHINAHEAO2 小时前
Bagisto修复php弃用警告,看着难受
开发语言·php
博大世界2 小时前
Python打包成exe文件方法
开发语言·python
yongui478342 小时前
双线性四边形等参单元程序(MATLAB实现)
开发语言·matlab
TT哇2 小时前
@AllArgsConstructor
java·开发语言
lkbhua莱克瓦242 小时前
TCP通信练习1——多发多收
java·开发语言·网络·网络协议·tcp/ip·tcp练习
Filotimo_2 小时前
在java后端开发中,docker虚拟化容器用处
java·开发语言·docker