小结vc编译的程序打包发布
今天败给Microsoft了!
面对的问题
- 由于应用程序的配置不正确,应用程序未能启动,重新安装应用程序可能会纠正这个问题。
- 应用程序正常初始化
0xc0150002
失败
综述
大多数程序需要依赖各种各样的库,静态库是程序编译的时候需要依赖的,而动态库是程序运行时需要依赖的。
缺失静态库编译肯定是无法通过的,而缺少动态库是程序运行的时候才能体现出来问题。
无论是使用qt库还是使用microsoft提供的各种库进行开发,连接的时候一般是动态连接,这意味着在我们生成的exe程序会更小。
在程序打包发布的时候就要考虑到同时给客户提供qt库的动态链接库或者microsoft的动态链接库。
注意,本文只讨论动态链接,因为静态链接不存在这样的问题。
VC2003、VC2005、VC2008及其后续版本,对底层最基本的CRT、MFC、ATL库都进行了重构。
为了避免不同版本的库引起冲突,重构后的库文件一般放在C://windows/WinSxS
文件夹中,并用特定的文件夹/文件名称进行标识。
与VC6不同, VC2003、VC2005、VC2008及其后续版本,引入了manifest清单的概念。
应用程序编译后会同时生成对应的.manifest文件,并将该.manifest文件作为资源编译到dll或者exe中去。
.manifest文件实际上是一个XML格式的文本文件,里面记录了dll或exe中要引用的CRT、MFC、ATL库的版本和名称。
注意,.manifest文件中只记录要程序依赖的CRT、MFC、ATL库的版本和名称。
下面我们来看看从编译到操作系统运行程序的整个过程:
编译&连接
根据不同的编译环境,决定程序依赖哪些动态链接库。
例如,使用Mingw编译代码(这个例子不是很恰当,因为本文涉及的问题是由于使用msvc编译所致。Mingw编译不会有这些问题。)
那么程序依赖
mingwm10.dll
和libgcc_s_dw2-1.dll
。例如,使用msvc2005编译则程序依赖
<Visual Studio Install Path>\VC\redist\<Architecture>\Microsoft.VC80.CRT
文件夹下的所有文件。根据你使用的其他外部库,决定程序依赖哪些动态链接库。
例如你使用了
ATL
或者MFC
则还依赖<Visual Studio Install Path>\VC\redist\<Architecture>\Microsoft.VC80.ATL
或者<Visual Studio Install Path>\VC\redist\<Architecture>\Microsoft.VC80.MFC
。例如你使用了
libxml2
则还依赖libxml2.dll
注意:有可能一些外部库依赖不同版本的VC库,这个时候会出现程序依赖两个不同版本的VC运行库,即编译环境依赖一个,外部库依赖一个。
可以使用VC自带的dumpbin判断依赖库的版本,最好只依赖一个版本的VC运行库。
dumpbin /directives <name>.lib
打包程序
除了.manifest文件中所记录依赖的动态链接库之外,其他的动态链接库和程序exe文件放在同一个目录下就可以了。
.manifest文件中记录的依赖必须像这样组织:
------myexe.exe ------libxml2.dll ------Microsoft.VC90.CRT文件夹 ---------Microsoft.VC90.CRT.manifest ---------MSVCM90.DLL ---------MSVCR90.DLL ---------MSVCP90.DLL
运行程序
就以上面的打包例子模拟运行。
假设.manifest文件如下:
<?xml version='1.0' encoding='UTF-8' standalone='yes'?> <assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> <security> <requestedPrivileges> <requestedExecutionLevel level='asInvoker' uiAccess='false'/> </requestedPrivileges> </security> </trustInfo> <dependency> <dependentAssembly> <assemblyIdentity type='win32' name='Microsoft.VC90.CRT' version='9.0.21022.8' processorArchitecture='x86' publicKeyToken='1fc8b3b9a1e18e3b'/> </dependentAssembly> </dependency> </assembly>
系统根据.manifest文件中记录的依赖查找
WinSxS\Policies\Microsoft.VC90.CRT\
下的策略文件。假设有个策略文件如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!-- Copyright (c) Microsoft Corporation. All rights reserved. --> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity type="win32-policy" name="policy.9.0.Microsoft.VC90.CRT" version="9.0.30729.1" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"/> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.VC90.CRT" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"/> <bindingRedirect oldVersion="9.0.20718.0-9.0.21022.8" newVersion="9.0.30729.1"/> <bindingRedirect oldVersion="9.0.30201.0-9.0.30729.1" newVersion="9.0.30729.1"/> </dependentAssembly> </dependency> </assembly>
这个策略文件说明
oldVersion="9.0.20718.0-9.0.21022.8" newVersion="9.0.30729.1"
,即将依赖9.0.21022.8
重定向到9.0.30729.1
的动态链接库。系统根据得到的版本号查找
WinSxS\Manifests\Microsoft.VC90.CRT_9.0.30729.1
的manifests文件。系统根据找到的manifests文件,去加载
WinSxS\Microsoft.VC90.CRT_9.0.30729.1
下的DLL文件。系统加载程序依赖的其他动态链接库。
成功运行程序。
注意:
- 系统搜索
WinSxS
未找到则会搜索程序当前目录下,程序集搜索顺序问题不在讨论范围内。 - 在当前目录下也没有找到
Microsoft.VC90.CRT
文件夹,则错误一出现“应用程序配置不正确,程序无法启动” - 找到对应的文件夹但是版本号不对等则错误二出现“应用程序正常初始化
0xc0150002
失败”
总结
我们编译的时候默认.manifest文件嵌入exe。使用CONFIG-=EMBED_MANIFEST_EXE
确保文件外置。注意这个文件要放在和exe同一个目录下打包发布。
使用外置的.manifest文件使得我们可以手动编辑他,配置程序依赖的库。解决一些突出的问题。
例如上面提到的程序依赖两个不同版本的库,我们可以手动删除一个,确保剩下的库兼容删除的库即可。
所有程序运行问题在“我的电脑”-“管理”-“事件查看器”-“系统”
,双击查看其中的记录,可以看到出错的原因。
工具
- 一定要记住: Dependency Walker 是你的好帮手,它会告诉你你的 exe 和 dll需要哪些库,以及它加载的动态库都在哪个文件夹内 等
- 最好准备一个进程查看的工具,比如微软的 Process Explorer等,来查看你的程序到底加载了哪些动态库(加载了哪些插件等)
注意:
- dependency walker没有process explorer靠谱。
- 由于系统存在多个版本的dll,dependency walker找到的依赖不是很准确。
- 例如系统既有msvcr80.dll也有msvcr90.dll。dependency walker可能会说两个都依赖,但是实际运行时只依赖一个。