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();
        });
}
相关推荐
Scott9999HH2 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社4 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海4 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖4 小时前
JDK 26 新特性详解
java·开发语言
马优晨5 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区7 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大7 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai8 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜8 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa8 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio