ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Qt程序打包exe全攻略:从windeployqt到安装包制作与问题排查

Qt程序打包exe全攻略:从windeployqt到安装包制作与问题排查 1. 打包前的准备工作别急着动手先搞清楚这三件事第一次把Qt工程打成exe时我踩过一个大坑编译完直接复制exe到别的电脑双击后弹窗“缺少Qt5Core.dll”当时还以为是自己代码写得有问题。后来才明白Qt的打包本质上不是“把你的代码变成exe”那么简单而是要解决依赖收集和运行环境匹配这两件核心事情。先说清楚一个关键概念Qt生成的exe是一个“药方”而Qt的DLL和插件是“药材”windowsdeployqt这个工具就是“抓药师傅”。你要做的是把药方、药材、抓药师傅三者配合好才能让程序在别人的电脑上跑起来。动手打包之前先检查三件事第一确认编译用的是Release版本不是Debug版本。Debug版本的exe体积大、依赖一堆调试用的DLL比如Qt5Cored.dll而且运行速度慢更不能发布给别人用。切到Release模式重新编译一次这是打包的最低前提。第二确认你的Qt Kit是MinGW还是MSVC。这两者的差异直接决定最终exe跑在别人电脑上需要什么样的运行库。MinGW编译出来的程序相对好办相关库文件往往能一并带上MSVC编译出来的程序除了Qt自身的DLL还得考虑VC运行库vcredist的问题哪怕用静态编译也能省掉这层麻烦。后面会详细说。第三确认工程里用到了哪些Qt模块。这个看起来废话但很多人漏了。比如你用到了Qt Charts、Qt WebEngine或者Qt Multimediawindeployqt会自动扫描exe的依赖去收集相应的DLL但有些资源文件、翻译文件、qml文件它不一定能自动带上就得手动补充。最稳妥的方法是先看一眼.pro文件或者CMakeLists.txt里写入了哪些模块心里有个数。注意如果你用的Qt是5.15以上的版本官方推荐用在线安装器装的时候勾选对应编译套件。不同版本Qt生成的exe对系统的要求也不同比如Qt 6.x要求Windows 10 1809及以上Qt 5.x最低支持Windows 7。如果你的目标用户还有老系统尽量选Qt 5.12或者5.15的版本。2. 一个完整的打包流程三步走稳妥可靠2.1 第一步用Release模式编译出干净的exe打开Qt Creator在左下角的构建套件选择器里从Debug切换到Release。点击构建等编译完成。编译成功后到你的构建目录里找exe文件。它通常在类似这样的路径下build-你的工程名-Desktop_Qt_5_15_2_MinGW_64_bit-Release\release\你的工程名.exe先在本机双击运行一下这个exe确认功能正常。这一步不能省因为后面所有动作都是围绕这个exe展开的如果它本身就起不来后面全是白费功夫。我习惯在桌面新建一个“发布包”文件夹把exe复制进去后续生成的DLL、插件都往这个文件夹里塞保持结构清晰。2.2 第二步windeployqt自动收集依赖windeployqt是Qt官方提供的部署工具路径在Qt安装目录下比如C:\Qt\5.15.2\mingw81_64\bin\windeployqt.exe打开命令行cmd或者PowerShell都行切换到你的发布包目录执行cd /d C:\Users\你的用户名\Desktop\发布包 C:\Qt\5.15.2\mingw81_64\bin\windeployqt.exe 你的工程名.exe这里的路径要根据你的Qt实际安装路径和套件调整。如果用的是MSVC套件路径可能是C:\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe命令执行完后你会看到发布包文件夹里多了几十个DLL文件、一个platforms文件夹、几个子目录imageformats、styles、translations等还有的可能有iconengines、tls看你的工程用到了什么。这一步的原理是windeployqt通过解析exe的PE头部信息读取它依赖的动态链接库清单再从Qt安装目录里把这些库和插件复制过来。它不是把所有Qt的DLL都复制过来而是“按需索取”所以产出的文件夹体积一般在60MB到150MB之间相对可控。但是有个细节必须提windeployqt对自定义插件、第三方库不一定能识别。比如你用到了OpenCV、FFmpeg或者自己写的DLLwindeployqt会置若罔闻。这些需要手动复制到发布包目录而且第三方DLL可能还依赖其他DLL这就麻烦一些。2.3 第三步手动补充缺失的运行库windeployqt自动收集之后还有一个常见的坑MinGW套件编译的程序需要带上libgcc_s_seh-1.dll、libstdc-6.dll、libwinpthread-1.dll这三个运行库。有时候windeployqt会帮你带上有时候不会取决于Qt版本和具体环境。我遇到过一种情况在Qt 5.12的MinGW环境中windeployqt执行完这三个库就自动带上了但到了Qt 5.15.2windeployqt却“漏掉”了libwinpthread-1.dll。这种情况去Qt安装目录下找C:\Qt\Tools\mingw810_64\bin\libwinpthread-1.dll C:\Qt\Tools\mingw810_64\bin\libstdc-6.dll C:\Qt\Tools\mingw810_64\bin\libgcc_s_seh-1.dll复制到发布包即可。认准这几个库都是MinGW编译器的运行时依赖。如果是MSVC编译的需要判断目标电脑上有没有安装VC运行库。最简单的验证方法是把发布包拷到一台裸机没有装开发环境的电脑上运行如果提示缺MSVCP140.dll就得把对应版本的运行库vcredist一起打包进去或者在目标电脑上安装。也可以用微软官方提供的“应用程序本地部署”方式把VC运行库的DLL直接放到exe同目录但这种方式官方不太推荐因为VC库也有更新。判断自己用的是哪个编译套件最直接的方法是看Qt Creator左下角的构建套件名称或者看生成exe的属性——MinGW的exe会依赖libgcc和libstdcMSVC的exe会依赖VCRUNTIME140.dll和MSVCP140.dll。3. 打包工具选型绿色版、安装包、单文件到底怎么选windeployqt把依赖收集完后你手上是一个“绿色版”的文件夹——这个文件夹拷到任意Windows电脑上只要系统版本符合要求exe就能运行。但实际发布的时候一般还要做两件事压缩成一个单文件或者做成安装包。这两件事分别对应不同的工具。3.1 常用工具对比工具类型学习成本产出结果适用场景Enigma Virtual Box绿色单文件工具低单个exe给技术人员发工具追求简单Inno Setup安装包制作中setup.exe给普通用户发布规范友好NSIS安装包制作高setup.exe需要自定义安装逻辑、多语言场景7-Zip SFX自解压压缩包低单exe解压运行快速分发不想引入额外工具Qt Installer Framework官方安装器框架高专业安装包需要在线更新、多组件选择的大型软件个人经验是如果只是给同事或者小范围用户用Enigma Virtual Box最省心如果要公开发布Inno Setup的脚本化打包更可控。3.2 Enigma Virtual Box打包单文件Enigma Virtual Box的用法很简单打开软件在“Enter Input File Name”里选择你的exe然后把发布包文件夹里的所有DLL和子文件夹拖进下方列表实际上它会默认加载exe同目录的所有文件。点击“Process”生成一个虚拟化的单exe。它的原理是在生成的exe内嵌一个虚拟文件系统运行时动态地把依赖文件映射到临时目录然后加载。所以程序启动时会有短暂的解压过程体积大的工程启动会稍慢但胜在“一次打包到处运行”。这里有个独家避坑提示Enigma Virtual Box虚拟化之后某些需要绝对路径访问文件的程序可能会出问题比如程序用QFile直接读同目录下的配置文件或者用QPluginLoader动态加载插件。因为虚拟文件系统是映射出来的QFile读取“同目录”可能失败。解决办法是在代码里用QCoreApplication::applicationDirPath()来拼接路径不要用相对路径。3.3 Inno Setup制作安装包Inno Setup是制作Windows安装包的经典工具脚本控制灵活度高。下面是一个Qt工程打包用的最小脚本模板[Setup] AppName你的软件名 AppVersion1.0.0 DefaultDirName{autopf}\你的软件名 DefaultGroupName你的软件名 OutputDir输出目录 OutputBaseFilename你的软件名_Setup_v1.0.0 Compressionlzma2 SolidCompressionyes [Files] Source: C:\发布包\*; DestDir: {app}; Flags: ignoreversion recursesubdirs createallsubdirs保存为.iss文件用Inno Setup编译即可生成安装包。关键目录是{autopf}它会自动适配32位/64位系统64位程序安装到Program Files32位程序安装到Program Files (x86)这个要注意因为Qt的32位程序装到64位系统上时路径写错了会导致找不到DLL。3.4 为什么我不太推荐每次都用“拷贝文件夹”方式发布有的Qt新手可能觉得既然绿色版文件夹已经能跑了直接压缩发给别人不就行了这个在团队内部试用没问题但正式对外发布时有很多麻烦一是用户解压后不知道运行哪个exe二是杀毒软件对绿色包里的某些DLL经常误报三是更新版本时用户容易搞混旧文件没清理导致版本混乱。所以我一般这样组合使用开发迭代阶段发给测试人员用Enigma打包的单exe方便快速替换正式发版用Inno Setup做成安装包带版本号、卸载入口、开始菜单快捷方式整体看上去专业很多。4. 程序运行不起来常见报错排查实战打包完成后最常见的问题就是在开发机上跑得好好的拿到别的电脑上就各种报错。我挑几个高频问题直接给排查路径。4.1 “qt.qpa.plugin: Could not find the Qt platform plugin “windows””这个报错是Qt打包界的“经典款”网上一搜一大片。原因通常是exe找不到platforms文件夹下的qwindows.dll或者加载qwindows.dll失败。排查步骤确认发布包根目录下有platforms文件夹里面至少有qwindows.dll。确认qwindows.dll和你exe用的是同一个Qt版本和编译套件。混用MinGW和MSVC的qwindows.dll必炸。如果platforms文件夹存在但仍然报错大概率是编译器运行库缺失导致qwindows.dll加载失败。MinGW环境检查那三个运行库MSVC环境检查VC运行库。这类报错还有个变体报错里出现的不是“windows”而是“linuxfb”“offscreen”等说明手动设置了-platform参数指定了错误的平台插件。极端情况下如果代码里写死了QApplication::setPlatformName(linuxfb)Windows上必然起不来。这种问题代码层面解决跟打包无关。4.2 “fatal: cannot mix incompatible Qt library (version 0x50601) with this library”这个报错的意思是程序里的某个DLL是Qt 5.6.1编译的但exe是Qt 5.15.2编译的二进制不兼容。原因很简单——你的发布包里混入了一个“来历不明”的Qt DLL。通常发生在别人给你一个第三方库的时候这个库是拿老版本Qt编的。解决办法是找到不匹配的DLL是哪个把发布包里对应文件替换成和你exe同版本的。如果第三方库本身是固定的比如硬件厂商的SDK那就得考虑把整个工程降级到它对应的Qt版本或者让厂商提供新版本SDK。还有一种情况你自己在构建目录中找到了某个老版本残留的DLL复制到了发布包。这里记住一个原则发布包里的Qt相关DLL必须全部来自你当前使用的那一套Qt环境最好不要从系统目录或者别的工程里随便拷贝。4.3 程序启动后闪退事件查看器报“0xc0000005”0xc0000005是Windows的“访问违例”错误绝大多数情况是内存地址访问出错。Qt程序打包后闪退并报这个我遇到过的场景有三类第一类第三方驱动/硬件SDK的DLL没带上。比如有人用Qt写CAN通讯的上位机软件用了厂商提供的CAN驱动SDK打包时忘了把厂商的DLL和驱动相关文件放进去程序一调用就崩报0xc0000005。这类问题用Dependency Walker或Process Monitor微软的工具Process Explorer也行排查能看加载了哪些DLL、谁失败了。第二类插件版本冲突。比如项目里既用了Qt的串口模块又用了第三方串口库两个库同时操作同一个COM口访问到了无效句柄。打包层面没法解决代码层面加异常保护或者统一用一个库。第三类有些杀毒软件注入导致的冲突。国产杀毒软件有时候会往exe里注入钩子Qt程序某些内存操作被拦截后直接崩。这种问题不好排查只能建议用户加白名单或者你换用静态编译的Qt版本减少DLL数量降低被注入的风险。提示排查闪退类问题时先用windeployqt把依赖补齐然后在命令行里直接运行exe。如果控制台输出里有“Segmentation fault”之类的信息MinGW环境下或者弹出一个崩溃对话框千万别急着关掉看崩溃地址和模块名往往能定位到具体是哪个DLL出了问题。4.4 提示缺少DLL但明明已经复制过去了有一种情况是你把DLL复制到了发布包但exe还是提示找不到。原因可能有两个一是DLL依赖的其他DLL缺失。比如你复制了A.dll但A.dll还依赖B.dllB.dll没带上系统在加载A.dll时失败报错信息却指向A.dll。排查方法是用DependenciesDependency Walker的替代品支持64位打开exe它能列出完整的依赖树哪个环节断了看得一清二楚。二是“DLL搜索顺序”问题。Windows查找DLL的顺序是exe所在目录 → 系统目录 → 环境变量PATH中的目录。如果你把Qt相关DLL放到了系统目录比如C:\Windows\System32而exe所在目录又有一个不同版本的同名DLLWindows会优先加载exe目录下的导致版本冲突。尽量把所有DLL都放在发布包根目录系统目录里不要乱放Qt库。5. 侧边话题QML程序、WebEngine程序、数据库程序的特殊打包需求如果只是用Qt Widgets写的小工具上面的流程完全够用。但工程一复杂就有很多“特殊分子”需要额外处理。5.1 QML程序的打包qml文件夹必须手动检查用QML写界面的Qt程序windeployqt会尝试收集QML相关模块但实测下来经常有遗漏。因为你可能用到了第三方QML模块或者自定义的QML组件这些不在windeployqt的“认知”范围内。解决思路是确认qml目录下有哪些模块被用到把整个相关文件夹复制到发布包。比如用到了Qt Quick Controls 2发布包里要有qml\QtQuick\Controls.2\ qml\QtQuick\Templates.2\ qml\QtQuick\Window.2\一个快速验证方法是把发布包文件夹整个拷到一台干净电脑上运行你的exe用QT_DEBUG_PLUGINS1环境变量启动。命令是set QT_DEBUG_PLUGINS1 你的工程名.exe这时候控制台会打印加载每个插件的具体结果哪个模块没找到一目了然。5.2 Qt WebEngine程序的打包体积大、坑多Qt WebEngine是Chrome内核的封装打包体积动辄几百MB。windeployqt对WebEngine的支持比较弱发布包需要手动检查以下文件和目录QtWebEngineProcess.exe resources/ translations/qtwebengine_locales/ icudtl.dat qtwebengine_resources.pak qtwebengine_resources_100p.pak qtwebengine_resources_200p.pak有个经验Qt WebEngine程序发布时qtwebengine_process.exe必须放在和主exe同级的目录不能改名不能挪进子目录否则它起不来。另外打包WebEngine程序时最好用官方文档里的部署方式windeployqt加一个--qmldir参数指向你的QML目录否则QML加载WebEngine视图时可能白屏。5.3 数据库程序SQLite比较简单其他数据库要带驱动Qt自带SQLite驱动windeployqt默认会收集sqldrivers目录下的qsqlite.dll。但如果你用MySQL或者PostgreSQL就需要额外处理。MySQL的Qt驱动依赖MySQL客户端的库文件libmysql.dll这不在Qt安装目录里得去MySQL安装目录找或者下载对应版本的Connector/C。把libmysql.dll复制到发布包后还可能出现“Driver not loaded”的报错——这是因为Qt编译的MySQL插件是按特定版本的MySQL客户端库链接的版本不匹配就会加载失败。PostgreSQL同理需要libpq.dll以及它依赖的一堆库libintl-9.dll、libiconv-2.dll等。这类数据库驱动的打包问题最麻烦的地方在于第三方库的依赖链不可控排查时建议用Dependencies工具逐步跟踪。5.4 自定义插件和第三方DLL的处理策略如果你自己写了一个插件或者用到了一个不自带DLL的第三方库windeployqt是不知道的。一般做法是把插件的DLL和依赖逐个复制到发布包然后写一个“打包清单”文档记录每个DLL的来源和版本。这个文档很重要因为三个月后你更新版本时完全不记得当初那个DLL是哪儿来的。我自己的习惯是二级子目录放非Qt依赖比如3rdparty文件夹然后把exe目录设为Plugins的搜索路径。代码里用QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() /plugins); QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath() /3rdparty);这样结构清楚排查问题也方便。如果工程里用了大量第三方库用CMAKE_PREFIX_PATH或者pro文件里的QMAKE_LIBDIR控制链接路径比手动拷贝要稳。6. 进一步优化减小exe体积和启动速度发布出去的exe体积动不动200MB用户第一印象就很差。体积优化可以从几个方向入手。6.1 用UPX压缩可执行文件UPX是一个免费的可执行文件压缩工具对Qt程序效果不错压缩率通常在40%到60%。命令很简单upx --best 你的工程名.exe有一个大坑UPX压缩后的exe会触发某些杀毒软件误报而且UPX压缩导致程序启动时需要解压内存占用会上升。如果程序本身对启动速度敏感不建议用UPX。另外如果exe要用Enigma Virtual Box再打包一次先UPX后Enigma的顺序会导致Enigma无法处理两者顺序别搞反。6.2 去掉调试信息和符号文件Release编译时Qt Creator默认会带上调试符号.pdb文件MinGW下是符号表信息。发布时去掉这些能省几MB。在pro文件里加QMAKE_LFLAGS_RELEASE -s不过实测下来这招对Qt Widgets程序帮助不大因为Qt本身的库才是体积大头。真正有效的是下面的方法。6.3 适当裁剪Qt库的内容Qt各大模块的DLL是按模块打包的比如你用到了Qt Widgets、Qt Network、Qt SerialPort发布包就会带对应DLL。如果用不到WebEngine、Multimedia这些大模块它们不会被带进来所以在源码层面减少不必要的依赖是最有效的减体积方式。用Qt 5.15版本有一个特殊技巧可以借助windeployqt --no-translations跳过一堆翻译文件如果你只做中文界面--no-system-d3d-compiler和--no-angle能去掉D3D编译器。这几个参数能把体积从150MB压到90MB左右windeployqt --no-translations --no-system-d3d-compiler --no-angle 你的工程名.exe去掉之后如果你的程序恰好用到了ANGLE做OpenGL ES的转译显示会异常这时就得保留--no-angle别乱加。Qt 5.15以上的版本默认就处理了ANGLEQt 6开始这块干净很多体积优化也更好做。6.4 升级到Qt 6之后体积有惊喜从Qt 5.15切换到Qt 6.x我实测相同功能的Widgets程序最终发布体积从140MB降到了90MB左右。原因是Qt 6重构了图形架构把很多OpenGL相关的代码挪到了核心库之外DLL数量变少了。但是Qt 6的兼容性坑也不小比如Qt 5里常见的QRegExp被移除了得全工程改QRegularExpression串口模块变成独立的Qt SerialBusQTextCodec部分接口变化。如果存量代码很大升级成本可能大于体积收益这个建议结合具体项目判断。7. 关于“不同环境下编译出来的exe差异”再补充几句打包时还有一个不能忽视的维度你是用什么编译器、什么平台、什么架构编译的直接决定exe能跑在哪些机器上。MinGW和MSVC编译出来的exe对目标机器的要求不同32位和64位也不同。这里给一个实际场景如果你的目标用户里有老旧的XP机器、32位Win7机器用Qt 5.12 MinGW 32位编译会更稳。但Qt 5.12已经是官方支持的最后一代支持Win7的版本线Qt 6.x直接不支持Win7了。我以前给工业现场做过一个仪器控制程序现场的工控机是Win7 32位系统内存只有2GB。刚开始用Qt 5.15.2 64位版本编译到现场根本装不上。后来把代码下放到Qt 5.12.12 MinGW 32位重新编译、打包一次过。所以打包之前先问清楚用户是什么系统、什么架构不同的目标环境工具链选择完全不同这比打包技巧本身更影响成败。再说一个容易被忽略的点官方在线安装器里每个Qt版本都带“Sources”组件如果要定制编译Qt别漏了这个。有些场景下比如目标机器缺VC运行库、不想带一堆DLL可以考虑静态编译Qt把Qt库直接编进exe里。但静态编译的工程会遇到插件机制失效的问题比如QT_PLUGIN_PATH必须指向编译时设定的路径SSL证书这类资源也要特殊处理。静态编译的调试过程比较痛苦需要提前规划。另外代码里用了多线程、定时器这类高频特性的程序打包后在用户机器上出现随机闪退时不要只盯着DLL依赖。检查一下你的QTimer是不是在主线程之外创建的或者是不是有跨线程访问UI组件的操作。这类问题跟打包无关但往往在“换了一台机器”后才暴露容易误判成打包的问题。8. 最后再分享几个实际经验做Qt打包这几年我养成了几个固定习惯不一定对所有人适用但确实帮我少踩了不少坑分享出来供参考习惯一搞一个“打包检查清单”。每次发版前逐项确认Release编译、windeployqt执行成功、额外DLL补齐、QML插件目录完整、数据库驱动文件在位、第三方SDK文件齐全、图标和版本信息正确。这个清单看起来简单但能拦住至少一半的低级失误。习惯二发版前必做“裸机测试”。找一台没有装Qt、没有装编译器的干净Windows虚拟机把发布包或者安装包放上去跑一遍能通过才是真的通过。我见过太多“在自己电脑上能跑”的假象——因为开发机上Qt环境齐全很多缺失的依赖被系统目录里的Qt库自动覆盖了。习惯三给exe加上版本信息和数字签名。在pro文件里设置VERSION 1.0.0.0 QMAKE_TARGET_PRODUCT 你的软件名 QMAKE_TARGET_COMPANY 你的公司名 QMAKE_TARGET_DESCRIPTION 软件描述Windows的资源管理器里能看到文件版本排查问题方便很多。如果有条件做一下代码签名哪怕只是一个自签名证书能减少杀毒软件的误报概率。习惯四把“发布包清单”写进README。每个DLL的来源、版本、为什么需要全部记录下来。半年后你重新维护这个项目时会感谢当初的自己。我吃过大亏一个老项目用的第三方串口库DLL找不到来源后来项目重构时怎么都复现不了当时的编译环境最后只能降级功能。Qt打包这件事说难不难流程熟了半小时能搞定说简单也不简单坑隐蔽得很。希望这篇经验能帮你少走点弯路。如果你在打包过程中遇到什么新奇的报错欢迎回来交流。
返回列表