ARTICLE DETAIL

资讯详情

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

VS2015+QT5程序发布实战:windeployqt部署与DLL问题排查

VS2015+QT5程序发布实战:windeployqt部署与DLL问题排查 1. 项目概述与发布思路1.1 核心需求解析为什么VS2015QT5发布总让人头疼先说个项目背景。我手头有个串口调试工具用的是VS2015写C逻辑界面层挂QT5来做。开发机上跑得欢拷贝到别的电脑上双击却直接弹出程序无法启动因为计算机中丢失Qt5Core.dll或者更狠一点压根没反应连报错都没有。这种问题做C/QT开发的基本都遇到过尤其是用VS2015搭配QT5这套组合发布环节的坑比想象中多得多。很多人会问程序发布到底是什么意思简单说就是把你写的代码编译出来的exe连同所有依赖的动态库、插件、资源文件打包成一个完整的目录或安装包让它能在没有安装开发环境的电脑上正常运行。QT程序在这件事上比纯Win32程序麻烦因为QT本身是一大坨DLL还有平台插件、样式插件、图片格式插件这些运行时组件缺一个就崩。VS2015编译的程序又依赖VC运行库比如MSVCP140.dll、VCRUNTIME140.dll所以还得把这一层也照顾到。这篇文章适合谁看就是那些正在用VS2015 QT5做开发需要把程序拿给同事或客户用的朋友。不管是做串口工具、TCP调试器还是内部业务系统只要你要做Windows桌面程序分发这篇内容都能帮你少踩几十个坑。核心是把怎么发布讲透包括自动部署工具、手动补漏、常见报错排查以及最后怎么做一个正经的安装包。1.2 发布方案选型三种常见方案对比程序发布不是只有一条路。我做了这么多年大概接触过三种主流方案各有优缺点先放在一起对比一下方案工具优点缺点适用场景方案一自动部署脚本windeployqt一条命令拉全依赖省时省力偶尔漏掉第三方库需要手动补绝大多数情况首选方案二手工拷贝DLL文件管理器/Dependency Walker能精确控制每个文件依赖分析费时容易漏维护成本高程序极小或依赖非常固定方案三静态编译QT静态库 /MT单个exe不依赖DLL配置繁琐体积大LGPL合规问题对体积和分发要求极高的特殊场景我之前在方案一和方案二之间来回折腾过几次。早期不会用windeployqt就手动一个个拷DLL结果换一台机器就报缺这个缺那个后来才明白QT官方早就给了自动部署工具只是很多人不知道。方案三静态编译也试过但QT5的静态库需要重新编译整个QT源码耗时几个钟头不说如果你用到了某些模块比如ICU、OpenSSL还得自己编译依赖折腾成本极高不建议新手碰。所以这篇文章的主体内容基于方案一来展开方案二作为补充思路方案三只简单提一下即可。2. 环境准备与发布前的配置检查2.1 确认编译环境与QT版本信息正式开始之前先把自己的环境摸清楚。这点特别重要因为VS2015配合QT5的组合其实有两个大分支一个是安装QT官方提供的Visual Studio Tools插件在VS2015里直接加载QT工程另一个是使用QT Creator MSVC2015编译器工具链。两种方式最后的产物差不多但发布细节略有不同。我自己用的是VS2015 qt5.9.2msvc2015_64这也是当时比较稳的一套组合。你可以在QT安装目录下看到类似这样的文件夹结构D:\Qt\Qt5.9.2\5.9.2\msvc2015_64\ ├─ bin\ # QT的DLL和工具 ├─ lib\ # 导入库(.lib) ├─ plugins\ # 平台插件、样式插件、图片插件 ├─ qml\ # QML相关如果你的程序不用QML可忽略 ├─ include\ # 头文件 └─ translations\ # QT自带的翻译文件这里有一个很容易踩的坑如果你在VS2015里面用的是32位编译x86那部署时必须去msvc2015不带_64的目录找对应的DLL和windeployqt。之前有个朋友把64位的Qt5Core.dll拷到32位程序目录里结果报应用程序无法正常启动0xc000007b这种问题非常迷惑人。所以第一步就确认好你的程序是Win32还是x64QT目录是不是对应的。还有一个检查点确认编译器版本是MSVC2015而不是MinGW。因为VS2015的工程走的是MSVC工具链QT插件的编译器和运行库都必须和VS2015匹配。你用MinGW的QT去尝试配VS2015大概率是配不起来的。2.2 理解windeployqt工具的原理windeployqt是QT官方提供的一个命令行部署工具它做的事情本质上是一个依赖扫描器读入你的exe文件分析导入表找出它依赖了哪些QT模块然后把对应的DLL、插件、翻译文件递归地复制到exe所在目录。它的原理和Python的pip freeze不一样不用配置文件而是直接二进制分析所以用起来比较偷懒但确实好用。windeployqt工具的路径在QT安装目录的bin下面比如D:\Qt\Qt5.9.2\5.9.2\msvc2015_64\bin\windeployqt.exe基本用法是windeployqt.exe --release --compiler-runtime D:\MyProject\release\MyApp.exe其中--release告诉它这是Release版本--compiler-runtime表示顺带把VC运行库msvcp140.dll、vcruntime140.dll等也拷贝过来。这个参数很多人会漏漏了之后换一台没装过VS的电脑就会报缺少MSVCP140.dll。有一点需要说明windeployqt不是万能的。它只管QT自身模块和编译器的运行库如果你程序里用了第三方库比如OpenSSL、FFmpeg、自己造的DLLwindeployqt不会帮你拷贝得自己在部署完后手动补进去。这是它最大的一个边界。2.3 VS2015下QT插件的运行环境检查在发布之前先确保开发机上程序能正常跑起来。这句话听起来像废话但确实有开发者把没编译成功的半成品拿去做发布测试白白浪费时间。具体检查项包括VS2015的扩展和更新里能看到Qt Visual Studio Tools已安装工程属性里VC目录的包含目录和库目录都指向了正确的QT路径工程属性 - 链接器 - 输入 - 附加依赖项里至少包含Qt5Widgets.lib;Qt5Gui.lib;Qt5Core.lib如果你用了网络、串口还得加上Qt5Network.lib;Qt5SerialPort.lib注意Release模式下的运行库设置工程属性 - C/C - 代码生成 - 运行库Release通常是多线程 (/MT)或多线程 DLL (/MD)。如果选了/MT那就把VC运行库静态编进去了发布时可以少带几个DLL如果选/MD就得依赖msvcp140.dll等运行库。windeployqt默认会处理/MD的情况。顺带提一个和开发体验相关的小问题很多人问QT5无法拖拽文件比如QWidget的setAcceptDrops和dragEnterEvent都写了但拖拽就是没反应。这通常不是因为发布导致的问题而是你的程序以管理员权限运行时UAC的权限隔离会导致资源管理器无法把文件拖进高权限窗口。如果你发现发布后的程序拖拽失效看看是不是用了以管理员身份运行。这不是发布主题的核心但经常在群里面被连着一块问所以先记一笔。3. 实操过程完整发布流程详解3.1 第一步切换Release模式并编译发布前最重要的一步是确认你编译的是Release版本不是Debug版。Debug版的exe体积大、速度慢而且依赖一堆调试用的DLL比如Qt5Cored.dll、qtpcre2d.dll这些在下游机器上根本没有。操作路径VS2015的工具栏上把解决方案配置从Debug切换成Release。然后右键解决方案选重新生成。生成完之后在工程目录下会多出一个Release文件夹里面放着编译好的exe文件。为了整洁我习惯单独建一个发布目录比如D:\release\MyApp把exe复制过去后面所有部署操作都在这个目录里进行。这样避免把一堆中间文件.obj、.pdb也打包进去。有个细节如果你的工程用到了QMLQt QuickRelease编译完之后记得检查qml文件夹是否被正常输出。QML的部署比Widgets负责一些因为QML模块是动态加载的windeployqt只能识别你在代码里import过的模块如果你在运行时动态拼接QML路径它可能识别不全。不过本文以C Widgets程序为主QML就不展开了。3.2 第二步用windeployqt自动部署依赖这是整个发布流程的核心操作。打开命令提示符cmd切换到你的QT bin目录然后执行cd /d D:\Qt\Qt5.9.2\5.9.2\msvc2015_64\bin windeployqt.exe --release --compiler-runtime D:\release\MyApp\MyApp.exe执行之后你会看到类似这样的输出Adding Qt5Svg to dependencies... Adding Qt5Widgets to dependencies... Adding Qt5Gui to dependencies... Adding Qt5Core to dependencies... Directory: D:\release\MyApp\ Added 12 dlls. Adding 8 plugin directories... Adding platform plugin qwindows.dll... Adding styles plugin qwindowsvistastyle.dll... Adding imageformats plugin ... ...如果你看到Unable to find platform plugin之类的提示大概率是路径有问题或者在64位命令窗口里对32位exe执行了64位的windeployqt。注意windeployqt的位数必须和exe一致。这个问题在4.3小节会详细讲。执行完windeployqt后目录结构大概长这样D:\release\MyApp\ ├─ MyApp.exe ├─ Qt5Core.dll ├─ Qt5Gui.dll ├─ Qt5Widgets.dll ├─ Qt5SerialPort.dll # 视你用的模块而定 ├─ msvcp140.dll ├─ vcruntime140.dll ├─ platforms\ │ └─ qwindows.dll ├─ styles\ │ └─ qwindowsvistastyle.dll └─ imageformats\ ├─ qjpeg.dll ├─ qgif.dll └─ qico.dll这里的platforms\qwindows.dll特别关键没有它程序启动时会报could not find or load the Qt platform plugin windows错误。windeployqt默认会生成它但如果你手工拷贝DLL时漏了就会出现这个报错。3.3 第三步手工补充缺失DLL与第三方依赖windeployqt执行完成后还需要人工检查几类东西。第一类是第三方依赖。以我的串口工具为例程序里用到了一个自己封装的MyComm.dll是之前用C#或者C编译的本地库windeployqt不会认识它需要手动复制到exe目录。如果你的程序用了OpenSSL的libcrypto和libssl同样要手动拷贝。第二类是某些QT模块的运行时补充。有一个比较隐蔽的点如果你的程序用到了QSS样式表里外部的图片或者运行时动态加载了某个字体文件这些资源文件windeployqt也不会管。发布前把用到的资源放在exe目录下的相应子文件夹里并确保程序用相对路径读取。我见过有人用绝对路径D:\MyProject\Resources\style.qss换台机器直接白屏这就是没有做资源打包。第三类是中文路径的兼容性。发布目录和生产机器上的安装路径尽量不要带中文字符或空格。虽然现代Windows对中文路径支持得不错但某些QT版本在加载插件时遇到中文路径会出现莫名其妙的异常。为了省事我发布时一律用类似C:\Program Files\MyApp或D:\Tools\MyApp这种纯英文路径。检查完这些最好再做一个干净机器模拟测试找一台没装过QT、没装过VS的Windows机器或者虚拟机把整个发布目录复制过去直接双击exe看能不能跑。这一步能提前暴露大部分依赖缺失问题。3.4 第四步验证发布结果验证分两步。第一步是运行验证在干净机器上运行exe确认主界面正常、功能模块可用尤其是那些你在代码里用到的非默认功能比如串口打开关闭、TCP连接、数据库操作都要挨个点一遍。很多时候依赖库是延迟加载的主界面能弹出来但一用某个功能就崩多半就是那个模块对应的DLL没带全。第二步是依赖验证。用Dependencies工具32位时代叫Dependency Walker新版叫Dependencies.exe打开exe查看导入表确认没有红色的未解析依赖项。也可以直接用命令行工具dumpbin需要VS2015的开发命令提示符dumpbin /dependents MyApp.exe输出会列出exe依赖的所有DLL你再逐一确认这些DLL都在发布目录里。这个方法虽然原始但非常可靠能发现一些windeployqt漏掉的间接依赖。我遇到的典型场景是程序依赖了Qt5Network.dllwindeployqt把它拷过来了但Qt5Network.dll又依赖了Qt5Core.dll和libssl-1_1-x64.dll后者不在目录里结果程序在初始化网络模块时崩溃。这种间接依赖问题不靠dumpbin或实际功能测试光看目录很难发现。4. 常见问题与排查技巧实录4.1 经典报错找不到Qt5Core.dll或Qt5Widgets.dll这个报错是最常见的几乎每个初试发布的人都会遇到。报错弹窗内容大致是由于找不到Qt5Core.dll无法继续执行代码。重新安装程序可能会解决此问题。排查思路其实很简单打开发布目录确认Qt5Core.dll等QT核心DLL是否存在如果不存在说明windeployqt执行失败或者根本还没执行如果存在那就检查DLL的位数和架构是否匹配32位程序不能配64位的DLL反之亦然。判断方法很简单用记事本打开DLL文件搜索PE这个字符其后面的值如果是d就是64位L就是32位当然更准确的做法是用dumpbin看。另外还要注意系统里可能存在多个版本的Qt5Core.dll。如果你在PATH环境变量里加了某个QT的bin目录程序加载DLL时可能先命中了错误的路径造成冲突。我一般建议发布目录里的DLL和exe保持在同一目录Windows会优先加载exe所在目录的DLL这样能减少很大一部分混乱。4.2 目标机器上运行提示缺少MSVCP140.dll这个报错说明VC运行库没有带上。两种解决方式第一种是在部署时让windeployqt带上--compiler-runtime参数它会自动把msvcp140.dll、vcruntime140.dll、concrt140.dll等运行库文件复制到发布目录。缺点是把运行库和exe放在一起了每个程序目录都要带一份比较占空间。第二种是在目标机器上安装Visual C Redistributable for Visual Studio 2015这个.exe安装包。微软官方有提供下载安装后系统全局就有了这些运行库。好处是安装包体积小且一次安装多个程序都能用坏处是你得额外跑一次安装流程。两种方式我都在用。给内部同事分发时我倾向于让windeployqt直接带运行库给外部客户做安装包时我会在安装包构建脚本里加入VC运行库的静默安装命令确保他们点击安装后一次到位。这里有一个容易被忽略的点VS2015的VC运行库实际上可以覆盖VS2013、VS2012等旧版本因为微软从VS2015开始做了统一的运行库版本号14.x所以你只需要分发一个最新版即可不用按VS版本分别装好几个。4.3 启动时报could not find or load the Qt platform plugin windows这个报错的原因几乎都是platforms插件目录缺失或位置不对。QT程序在启动时需要加载平台插件默认查找exe同级目录下的platforms\qwindows.dll找不到就报这个错。具体排查步骤确认platforms\qwindows.dll文件存在确认这个DLL的位数与exe一致。windeployqt如果是64位的复制出来的qwindows.dll也是64位搭配32位exe会报0xc000007b错误如果你的程序在代码里手动指定了QApplication::addLibraryPath或QCoreApplication::setLibraryPathsQT会优先去你指定的路径找插件而不是默认目录。这时要检查路径字符串是否正确。我有个老项目为了优化插件加载写死了addLibraryPath(D:/MyApp/plugins)结果换机器就报错改成相对路径./plugins以后就好了。顺带说一个和QT5无法拖拽文件相关的小排查技巧如果拖拽功能在开发机上正常发布后不行先确认发布目录里platforms\qwindows.dll是否存在且版本正确。平台插件不对不仅会导致窗口样式异常也会影响拖拽输入的接收。另外如果程序以管理员权限运行拖拽也会失效这是UIPI用户界面特权隔离导致的不是DLL问题。4.4 程序运行字体异常或QSS样式不生效很多人发布完程序功能跑通了但界面很难看。最常见的原因是styles插件缺失或QSS里引用的资源没有带上。QT的样式插件比如qwindowsvistastyle.dll存在于发布目录的styles子文件夹里。windeployqt默认会复制但如果你手工精简目录把它删掉了程序会退回基础样式看起来很朴素。如果你的程序使用了QSS文件比如QFile styleFile(:/qss/main.qss);那么资源已经被编译进二进制里了发布时不用额外处理。但如果你改成这样QFile styleFile(config/style.qss);那就要确保config目录和style.qss文件也出现在发布目录里。我建议把QSS资源编译进程序用qrc文件这样能少一个运行时依赖。另外如果你在代码里使用了一些特殊字体比如微软雅黑、思源黑体目标机器上没装这些字体程序会自动退回到默认字体界面可能走样。解决办法是发布时带上字体文件并在程序启动时用QFontDatabase::addApplicationFont动态加载字体。4.5 发布目录体积过大如何精简默认情况下windeployqt会把所有QT插件都复制进来体积往往超过100MB看着很吓人。精简思路有两个方向。第一个方向是删掉没用的插件。比如你的程序只做串口通信那imageformats里的qsvg.dll、qwebp.dll可能都用不上可以只保留qjpeg.dll和qpng.dll通常也叫qico.dll看需要。bearer目录网络承载插件如果你的程序不用网络也可以删掉。translations目录里只需要保留你实际用到的语言翻译文件比如中文的qt_zh_CN.qm其他全部删掉。我见过有的程序发布目录600MB精简后只剩80MB差别非常大。第二个方向是使用压缩安装包。上面的精简方式只是减少文件数量但DLL和插件本身还是有体积。这时可以用Inno Setup或NSIS打一个压缩安装包安装包体积能再缩小30%到50%。压缩安装包还有一个好处是文件在安装时自动解压到目标目录不容易被误删。我的个人习惯是发布到测试环境用精简目录方式直接压缩成zip发过去发布到生产环境用Inno Setup做一个安装包一步到位带卸载功能给客户的感觉也更专业。5. 进阶优化与实战心得5.1 用Inno Setup做一个正经的安装包程序发布到最后一步通常要做一个安装包。我用Inno Setup比较多原因很简单免费、脚本化、支持静默安装、还能把VC运行库和QT依赖一起打包进安装流程。一个最小可用的Inno Setup脚本大致长这样[Setup] AppNameMyApp AppVersion1.0.0 DefaultDirName{pf}\MyApp OutputDirinstaller OutputBaseFilenameMyApp_Setup Compressionlzma2 SolidCompressionyes [Files] Source: D:\release\MyApp\*; DestDir: {app}; Flags: recursesubdirs [Run] Filename: {app}\MyApp.exe; Description: 立即运行 MyApp; Flags: nowait postinstall skipifsilent编写脚本时注意几个点Source路径里的*包括子目录recursesubdirs标志这样platforms、styles这些子目录都能被正确打包如果你希望安装包自动装上VC运行库可以额外写一个静默安装命令Filename: {tmp}\vcredist_x64.exe; Parameters: /install /quiet /norestart; Flags: runascurrentuser但前提是你把vcredist_x64.exe放在安装包资源里用[Files]先解压到{tmp}目录建议在[Icons]段加上桌面快捷方式否则用户安装完还要去安装目录里翻exe体验稍差。5.2 跨平台与ARM环境发布的一些发散思考虽然标题是VS2015QT5但实际工程里可能会遇到我要发布到Windows下不同架构或者我打算移植到Linux的情况。我对ARM平台的QT编译有过一些研究简单分享一下思路如果你的目标平台是ARM Windows比如某些工控平板那么VS2015的编译器版本可能不支持ARM64你需要换用更新的VS或者交叉工具链QT源码本身支持交叉编译但需要配置CMake工具链文件指定ARM编译器、sysroot等这个比x86/x64复杂不少发布到ARM平台时QT模块、插件架构和x86/x64完全不一样不能直接把DLL拷过去用。windeployqt虽然也有ARM版本但需要确保目标机器有对应的运行环境。这个方向水比较深如果读者确实需要做ARM QT编译和发布我建议先拿官方交叉编译文档逐行过一遍再用最小化的hello world验证工具链最后再移植完整程序。别一上来就编译大型工程不然你会被一连串依赖错误折磨得怀疑人生。另外如果你的程序有计划移植到Linux或者macOS那么VS2015的工程就没法直接用了。QT的好处是跨平台框架但你在Windows上用的很多MSVC特性比如__declspec(dllexport)需要改成跨平台写法。这个扩展内容可以先收藏真到那天再做专项研究。5.3 发布检查清单与个人踩坑总结最后我把这些年做发布时总结出来的检查清单交给各位。每次发布前照着这个清单过一遍能避免大部分低级问题[ ] 确认编译模式是Release不是Debug[ ] 确认exe是32位还是64位QT DLL和插件的位数与之匹配[ ] 用windeployqt部署后第三方DLL已手动补齐[ ]platforms\qwindows.dll存在且位数正确[ ] 程序用到的资源文件QSS、图片、字体、配置文件已放入发布目录且是相对路径读取[ ] VC运行库已带上windeployqt的--compiler-runtime或安装包静默安装[ ] 在干净机器上完整跑一遍功能测试不只是打开主界面[ ] 发布路径和安装路径使用纯英文[ ] 如果程序需要管理员权限确认平台插件和拖拽功能不受影响。我个人实际踩过最深的一个坑是在开发机上换了QT版本但没重新编译。当时程序用的是QT5.9.2后来系统里装了QT5.12PATH环境变量里新版本的bin排在前面。我在开发机上运行觉得一切正常但发布时windeployqt用了新版工具拷过去的运行库是QT5.12的而exe实际引用的是QT5.9.2的符号表结果在客户机器上启动后界面随机闪退。排查了很久才发现是版本混用的问题。从那以后我每次发布前都会先打开命令提示符执行where windeployqt确认自己用的到底是哪个版本的部署工具。发布这件事本质上是对程序运行到底需要什么的一次彻底的盘问。windeployqt能帮你解决80%的常规问题但剩下的20%需要你对程序依赖、插件机制、运行库加载顺序有足够的理解。希望这篇从实际项目里总结出来的过程记录能帮你少走一些我已经走过的弯路。如果后续你在发布时遇到了这篇没覆盖到的怪问题欢迎带着报错信息来交流我们下一轮再一起拆解。
返回列表