打包我的 Qt/C++ 视觉应用:从依赖部署到单文件 EXE 的踩坑之旅

一、前言

最近完成了一个基于 Qt/C++ 的桌面视觉应用项目(proj_ai_vision_app)。这个项目功能还挺复杂,不仅用了 Qt 做界面,还集成了 OpenCV 进行图像处理,并且通过我们自研的 AIEngine 库(它内部又调用了 MNN 推理框架)来实现 AI 功能。开发完成后,接下来的重要一步就是将它打包,以便在没有安装开发环境的其他 Windows 电脑上也能顺利运行。

通常,Windows 应用打包有两种主流方式:

  1. 部署依赖:创建一个包含 EXE 文件和所有必需的 DLL、资源文件的文件夹。用户直接运行这个文件夹里的 EXE。
  2. 单文件打包:将 EXE 和所有依赖项“塞”进一个单独的可执行文件中。用户只需双击这一个文件即可运行。

这篇文章就记录一下我为 proj_ai_vision_app 这个项目打包的过程,特别是后面尝试创建单个可执行文件时遇到的一些坑以及最终的解决方案。

二、打包 EXE 文件流程(带依赖文件夹)

这是最基础也是最标准的打包方式,主要依赖 Qt 官方提供的 windeployqt 工具。

1. 构建 Release 版本

首先,在 Qt Creator 中,确保你的项目配置是 "Release" 模式。Release 构建会进行代码优化,生成的可执行文件运行效率更高,体积也可能更小。点击构建按钮,生成 proj_ai_vision_app.exe 文件。

2. 创建部署目录

新建一个干净的文件夹,专门用来存放打包后的所有文件。我这里创建了一个名为 exe 的目录 (D:\DeskTop\QT_Program\exe\)。然后,将 Qt Creator 构建生成的 proj_ai_vision_app.exe 文件复制到这个 exe 目录中。

3. 使用 windeployqt 收集 Qt 依赖

windeployqt.exe 是 Qt SDK 自带的一个命令行工具,它的作用是分析你的 EXE 文件,然后自动将运行该 EXE 所需的 Qt 库 DLL、必要的插件(如平台插件 qwindows.dll、图片格式插件 qjpeg.dll 等)、翻译文件(.qm)等复制到 EXE 所在的目录。

搜索windows下的qt终端,记得打开的是带有你编译器环境的终端(我这里使用的是MVSC,所以打开的也是带有MVSC环境的QT终端),切换到你的部署目录 (exe 目录),

然后执行:

# 确保 windeployqt 在你的系统 PATH 中,或者使用 Qt 安装目录下的完整路径
windeployqt .\proj_ai_vision_app.exe

执行后,你会看到类似这样的输出,提示它添加了哪些模块和插件:

D:\DeskTop\QT_Program\exe\proj_ai_vision_app.exe 64 bit, release executable
Adding in plugin type platforms for module: Qt6Gui
... (省略其他插件和依赖)
Updating Qt6Core.dll.
Updating Qt6Gui.dll.
Updating Qt6Widgets.dll.
...
Creating directory D:/DeskTop/QT_Program/exe/platforms.
Updating qwindows.dll.
...
Creating D:\DeskTop\QT_Program\exe\translations...
Creating qt_zh_CN.qm...
...

现在查看 exe 目录,你会发现多了很多 Qt6*.dll 文件,以及 platforms, imageformats, styles, translations 等子文件夹,里面也包含了相应的 .dll.qm 文件。

4. 手动添加非 Qt 依赖

windeployqt 只负责 Qt 自身的依赖。我的项目还依赖了 OpenCV、MNN 和我自己编译的 AIEngine。这些库的 DLL 文件需要我们手动复制到 exe 目录中。

根据我的项目配置 (CMakeLists.txt),这些 DLL 位于:

  • AIEngine.dll: lib/AIEngine/proj_ai_engine/Arch/x86/Windows/build/bin/Release/AIEngine.dll
  • MNN.dll: lib/MNN/bin/MNN.dll
  • OpenCV DLLs: lib/opencv/x64/vc17/bin/ 目录下的所有 opencv_*.dll 文件。

将这些 DLL 文件全部复制到 exe 目录下,与 proj_ai_vision_app.exeQt6*.dll 放在一起。

至此,这个 exe 文件夹就包含了运行 proj_ai_vision_app 所需的所有基本文件。理论上,将这个文件夹拷贝到其他 Windows 电脑上(前提是目标电脑安装了对应版本的 Microsoft Visual C++ Redistributable,因为 AIEngine 是用 MSVC 编译的),应该就能运行了。

三、创建单个可执行文件(使用 Enigma Virtual Box)

虽然带依赖文件夹的方式很标准,但有时分发一个单独的 EXE 文件会更方便。我选择了 Enigma Virtual Box 这款免费工具来尝试实现这个目标。它的原理是将所有依赖文件虚拟化地打包进主 EXE 中。

1. 下载与安装

前往 Enigma Virtual Box 官网 下载并安装。

2. Enigma Virtual Box 基本配置

  • 打开 Enigma Virtual Box。
  • Enter Input File Name: 选择我们之前准备好的、位于 部署目录 (D:\DeskTop\QT_Program\exe\) 中的主程序 proj_ai_vision_app.exe
  • Enter Output File Name: 指定打包后生成的单文件名,例如 D:\DeskTop\QT_Program\exe\proj_ai_vision_app_boxed.exe

3. 添加虚拟文件(关键步骤与踩坑记录)

这是最关键也是我踩坑最多的地方。目标是把所有依赖项都“告诉”Enigma Virtual Box,让它打包进去。

  • 错误尝试 1:直接添加整个部署目录 (exe) 作为子文件夹

    我最初的想法是,既然所有东西都在 exe 目录里,那我就把这个 exe 文件夹整个添加到 Enigma 的虚拟文件系统 %DEFAULT FOLDER% 下。

    结果: 运行打包后的程序,直接报错 “Cannot load library AIEngine.dll”。

    原因: Enigma 创建的虚拟文件系统 %DEFAULT FOLDER% 对于运行在其中的 proj_ai_vision_app.exe 来说,就是它认为的“当前目录”。Windows 加载器默认只在 EXE 所在的当前目录查找直接依赖的 DLL。我把 DLL 都放到了虚拟的 exe 子文件夹里,程序自然在它认为的当前目录(虚拟根目录)找不到它们。

  • 错误尝试 2:添加了错误的源文件(Qt 构建目录)

    我还尝试过把 Qt Creator 的构建目录 (Desktop_Qt_6_7_3_MSVC2022_64bit-Release) 添加进去。

    结果: 同样失败。

    原因: 构建目录里包含的是编译中间产物,而不是最终部署需要的文件,结构完全不对。

  • 关键依赖:VC++ 运行时库

    反复失败后,我们排查到 AIEngine.dll 是我自己用 Visual Studio 2022 (MSVC) 编译的,它运行时依赖特定版本的 Microsoft Visual C++ Redistributable 运行时库。windeployqt 不会处理这个依赖。这些运行时库(主要是 vcruntime140.dll, msvcp140.dll, vcruntime140_1.dll 等)也必须被打包进去!

    查找路径: 这些 DLL 可以在 VS 安装目录下的 VC\Redist\MSVC\<version_number>\x64\Microsoft.VC143.CRT 找到。

    操作: 将这个 Microsoft.VC143.CRT 文件夹下的所有 DLL 文件复制出来。

  • 正确的做法:重建部署目录结构于虚拟根目录

    最终的解决方案是,模拟程序运行时所需的真实文件结构,将其“平铺”到 Enigma Virtual Box 的虚拟根目录 (%DEFAULT FOLDER%) 下:

    1. 移除之前错误添加的 exe 文件夹或构建目录。
    2. 添加 DLL 文件:将物理部署目录 (D:\DeskTop\QT_Program\exe) 下的所有 .dll 文件(包括 AIEngine.dll, MNN.dll, 所有 opencv_*.dll, 所有 Qt6*.dll, opengl32sw.dll 等)直接添加到 %DEFAULT FOLDER% 下。
    3. 添加 VC++ 运行时 DLL:将从 VS Redist 目录找到的 Microsoft.VC143.CRT 文件夹下的所有 .dll 文件也添加到 %DEFAULT FOLDER% 下。
    4. 添加 Qt 插件文件夹:将物理部署目录 (D:\DeskTop\QT_Program\exe) 下的 platforms, imageformats, styles, translations子文件夹(使用 "Add Folder Recursively" 或类似选项)添加到 %DEFAULT FOLDER% 下,保持它们的子文件夹结构。

最终,%DEFAULT FOLDER% 下应该直接包含所有必需的 DLL、.mnn 文件,以及顶级的 Qt 插件文件夹和 VC++ 运行时 DLLs。

4. 打包与测试

确认 Enigma Virtual Box 的文件列表结构正确后,点击界面右下角的 "Process" 按钮。等待打包完成。然后,将生成的 proj_ai_vision_app_boxed.exe 文件复制到一台没有安装 Qt、VS 开发环境和相关库的干净 Windows 电脑上,双击运行。

经过上面正确的步骤处理后,这次终于成功运行了!

总结

创建单文件 EXE 对于分发确实更简洁,但需要对程序的运行时依赖有更清晰的认识。使用 Enigma Virtual Box 这类工具的关键在于,在它的虚拟文件系统中正确地重建程序运行时所期望的文件和目录结构。特别是对于自己编译的库,千万不要忘记它依赖的 VC++ 运行时库。同时,程序运行时需要加载的数据文件(如 AI 模型)也必须一并打包进去。这次踩坑经历虽然曲折,但也加深了对 Windows 程序部署和依赖管理的理解。

Windows下将QT打包为可执行文件(exe)的完整流程,包含第三方库。的更多相关文章

  1. Windows下的Qt Creator的安装

    采用Qt和Qt creator分别下载和安装的方式:(需要手动设置关联Qt和Qt Creator)   一.软件下载 从http://qt-project.org/downloads分别下载Qt和Qt ...

  2. windows下安装Qt

    1.Linux下安装Qt与MySQL相对来说比较容易,在这里我就不多加介绍. 接下来主要介绍windows下安装Qt与MySQL. 2.在windows,我安装QtCreator, 使用的是qt-wi ...

  3. Python程序打包为可执行文件exe

    Python程序打包为可执行文件exe,pyinstaller应用 山重水复疑无路,柳暗花明又一村. 本来是向老师提交一个python程序,因为第一次所以就很尴尬只把源码给老师了,应该是打包成一个可执 ...

  4. 在windows下的QT编程中的_TCHAR与QString之间的转换

    由于在windows下的QT编程中,如果涉及到使用微软的API,那么不可避免使用_TCHAR这些类型,因此在网上查了一下,其中一个老外的论坛有人给出了这个转换,因此在这里做一下笔记 : )#ifdef ...

  5. windows下编译qt的mysql驱动

    windows下编译qt的mysql驱动cd %QTDIR%\src\plugins\sqldrivers\mysqlqmake –o Makefile INCLUDEPATH+="C:\M ...

  6. windows下Inno Setup打包

    基于inno setup的windos打包,主要脚本语言inno script.下载地址:https://jrsoftware.org/isdl.php相关打包教程:https://blog.csdn ...

  7. 让 windows 下的命令行程序 cmd.exe 用起来更顺手

    在 Windows 下使用 Larave 框架做开发,从 Composer 到 artisan 总是避免不了和 cmd.exe 打交道,系统默认的命令行界面却是不怎么好看,且每行显示的字符数是做了限制 ...

  8. golang 在 windows 下编译出 linux 二进制可执行文件的软件套装合集 [go 1.7.3环境]

    golang 很好用,不过要把工具链弄完整. 要不你会发现怎么不能编译跨平台的呀? 怎么写代码没提示啊? ... 这一整套弄下来并不容易. 所以精心准备了一套工具方便大家使用. 软件列表如图. 安装顺 ...

  9. 将Python打包成可执行文件exe的心路历程

    导言: 我们有时候需要将做好的Python程序打包成为一个exe , 方便我们使用,查找了资料发现 pyinstaller .py2exe,最后还是选择的pyinstaller,用的时候踩过了挺多的坑 ...

  10. windows下用py2exe打包脚本为可双击运行程序

    文件夹结构: ├── readme.txt ├── settings.py #程序参数 ├── settings.pyc ├── setup.py    #安装文件 ├── spider.ico   ...

随机推荐

  1. Java01-基础入门(准备工作)

    从零开始开发Java的第一个程序: [ 任务列表 ] 最新的程序开发手段 Java是什么 Java开发工具 JDK的卸载,下载及安装 JDK配置环境变量 命令行运行第一个Java程序 Java开发工具 ...

  2. 天翼云加速落地紫金DPU实践应用,让算力供给更高效!

    近日,以"智驱创新·芯动未来"为主题的第三届DPU峰会在北京成功举办.会上,天翼云凭借紫金DPU在架构革新.算力释放.场景落地等多方面的成果,荣膺"2023芯星品牌奖&q ...

  3. NOIP 游记

    前情提要:color \(100\to 0\),arena \(92/100\to 36\). 最后一场模拟赛喜提 0+0+100+0,挺乐的. Day 0 晚上九点睡,然而还是很早就醒了,但是时间体 ...

  4. Luogu P10997 Partition 题解 [ 蓝 ] [ 分割线 dp ]

    Partition:一道 dp 神题,用到了以轮廓线的轨迹来做 dp 的技巧,和敲砖块这题的状态设计有点相似. 观察 首先观察样例,发现整张图可以看作是被两条线分隔开的.同时每个颜色的四个方向上又存在 ...

  5. 如何在Spring Boot项目中添加国密SM4加密支持?——基于过滤器的实现

    如何在Spring Boot项目中添加国密SM4加密支持呢?--基于过滤器的实现 引言 ​ 在数字化时代,数据安全至关重要,尤其是在API交互过程中,确保传输数据的安全性是保护隐私和机密信息的关键.中 ...

  6. 用 C# 插值字符串处理器写一个 sscanf

    插值字符串处理器 C# 有一个特性叫做插值字符串,使用插值字符串,你可以自然地往字符串里面插入变量的值,比如:$"abc{x}def",这一改以往通过 string.Format ...

  7. 【效能提升】上线前漏了SQL脚本,漏加上某个配置项了?

    背景 一个版本从开始开发到上线,可能经历10多天,甚至更久. 由于这个过程的时间较长,难免出现某些需要执行的SQL脚本.需要配置的配置项,到了上线前,却被遗漏了,最后导致出现线上问题才发现. 我们团队 ...

  8. QT5笔记: 14. SpinBox的常用功能

    例子: #include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : ...

  9. wikidata介绍和查询

      Wikidata是一个大型结构化开源知识图,为维基百科等项目提供支持.我们可使用SPARQL(Wikidata官方Tutorial)对其进行查询.SPARQL是一种专为 RDF(Resource ...

  10. 【由技及道】量子构建交响曲:Jenkinsfile流水线的十一维编程艺术【人工智障AI2077的开发日志008】

    摘要:当代码提交触发时空涟漪,当构建流水线穿越量子维度--欢迎来到自动化构建的终极形态.本文将揭示如何用Jenkinsfile编写量子构建乐章,让每次代码提交都成为跨维度交响乐的音符. 动机:构建系统 ...