Microsoft Visual C++ 2008 发布程序的部署问题
这个问题有好多Blog和Forum已经讨论过了,但都不详尽,在具体的操作过程中还是有许多疑问。我摘录并整合了许多网络文章,希望能够做个最终了结。
一、VC2005和VC2008编译出来的程序如何发布
http://www.cppblog.com/lf426/archive/2008/04/12/46885.aspx
VC2005和VC2008编译出来的程序放到别人的电脑上为什么有可能无法运行呢?
1:Microsoft Visual C++ 2008 Express Edition可以发布软件吗?
能!
很多人说,因为是Express版,不是Studio,所以只是用来练习语言的,不能发布软件——错!
除了没有MFC和ATL,基本上跟 .net 版本是一样的。发布出来的,是完整的可执行文件。
2:VC 2008 (2005) 发布出来的程序必须附带上他们特定的dll文件吗?
不一定。
如果目标系统是个经常升级的系统,微软已经为其打上了所需要的dll文件补丁了,不需要在软件包里面附加特定的dll文件。特别在Vista系统中,你更是不需要VC8和VC9的dll文件。但是在一些老版本的系统中,这些文件就是必须的。
3:VC2008和VC2005特定的dll文件是哪些?
VC8: msvcm80.dll, msvcp80.dll, msvcr80.dll
VC9: msvcm90.dll, msvcp90.dll, msvcr90.dll
4:如何部署文件?
首先,请选择release版本;在生成可执行文件(exe文件)的时候,会得到相应的部署文件(manifest文件)。比如,得到a.exe文件,就会同时生成a.exe.intermediate.manifest文件。请将这2个文件放在同一文件夹下。然后,你需要VC8和VC9的部署文件:Microsoft.VC80.CRT.manifest和Microsoft.VC90.CRT.manifest。请到你的VC安装目录下寻找,比如:
C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\x86\Microsoft.VC90.CRT
我这里也把2个部署文件直接贴出来,没装的直接用就是了:
Microsoft.VC80.CRT.manifest
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<noInheritable></noInheritable>
<assemblyIdentity type="win32" name="Microsoft.VC80.CRT" version="8.0.50727.762" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
<file name="msvcr80.dll" /> <file name="msvcp80.dll" /> <file name="msvcm80.dll" />
</assembly>
Microsoft.VC90.CRT.manifest
<?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">
<noInheritable/>
<assemblyIdentity
type="win32"
name="Microsoft.VC90.CRT"
version="9.0.21022.8"
processorArchitecture="x86"
publicKeyToken="1fc8b3b9a1e18e3b"
/>
<file name="msvcr90.dll" /> <file name="msvcp90.dll" /> <file name="msvcm90.dll" />
</assembly>
然后将VC8的3个dll以及这个manifest装到一个文件夹里,并将文件夹命名为Microsoft.VC80.CRT。
同样将VC9的3个dll以及这个manifest装到一个文件夹里,并将文件夹命名为Microsoft.VC90.CRT。
将这2个文件夹放到与exe文件(及其部署文件)所在目录下就OK了。
二、“应用程序配置不正确,程序无法启动”的原因
http://hi.baidu.com/fairysky/blog/item/130dda13db7b050a5aaf53be.html
vc2005/vc2008采用了新的程序部署技术(manifest清单文件),manifest清单文件实际上类似于我们常用的makefile文件,它定义了程序运行的依赖关系(程序运行所需要的dll库的名称、版本等)。
程序运行,首先根据manifest清单文件(这个文件可以嵌入到exe或dll中,或者单独生成外部文件,可以通过vc2005/vc2008的编译选项控制:工程“属性”->“配置属性”->“清单工具”->“输入输出”->“嵌入清单文件”,选择“是”或“否”来控制)来查找程序运行需要的dll库的名称、版本等,如果所在的系统中没有程序运行所需要的dll库和相应的manifest清单文件,则弹出“应用程序配置不正确,程序无法启动”对话框。
另 外要注意,由于vc2005/vc2008与.net集成,导致出现一个新的概念:在.net中,将exe、dll都看成“程序集 (assemble)”,每个程序集(assemble)都附带有一个manifest清单文件,因此使得vc2005/vc2008的CRT(C 运行时库)、MFC、ATL等dll库都附带有一个manifest清单文件。
归 根结底是由于老版本的系统没有我们开发的程序运行所需要的基本运行时库(2k、xp系统只有vc6的一些dll库,而没有vc2005、vc2008所需 要的dll库以及相应的manifest清单文件,而在vista系统或者即将到来的windows 7系统上则包含有vc2005、vc2008的dll库和manifest清单文件)
举个例子:(在XP SP3系统下)
使用vc2008 express sp1版(没有mfc、atl),新建一个“HelloWorld”的“win32控制台应用程序”工程,在release下编译,此时默认的编译选项:(在这里我们只关注与我们的问题相关的几个选项)
1、工程“属性”->“配置属性”->“c/c++”->“代码生成”->“运行库”
默认选项为/MD(release)、/MDd(debug),对这几个编译选项不清楚的可以参见: VC运行库版本不同导致链接.LIB静态库时发生重复定义问题的一个案例分析和总结
2、工程“属性”->“配置属性”->“清单工具”->“输入输出”->“嵌入清单文件”
默认选项为“是”(表示将manifest清单文件嵌入到程序中);当然,我们也可以选择“否”,从而单独生成一个manifest清单文件,不过这会增加不必要的依赖项,因此不建议选择“否”。
编译->链接之后在“ HelloWorld ”工程的release或debug目录下,我们能够看到一个HelloWorld.exe.intermediate.manifest清单文件(根据编译选项,见上,vc2008将manifest清单文件嵌入到了exe程序中,HelloWorld.exe.intermediate.manifest清单文件是一个临时文件,但它的内容与嵌入到exe程序的manifest文件是一样的),用文本编辑器打开该文件(用“记事本”也行,不过格式太乱,看不清楚内容,推荐使用vim或其它的文本编辑器查看),大致内容如下:
<?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>
我们重点查看红色部分,这说明编译后的exe程序依赖于vc90(也即vc2008)的CRT(C运行时库),版本9.0.210022.8(这是由于使用/MD选项,程序动态的依赖于CRT,如果使用/MT选项,则会将CRT静态链接到程序中,当然,这会使程序的尺寸急剧的增长,大概有10倍的大小差距)
当exe程序执行时,它会根据嵌入的manifest文件查找相应的依赖项,在这个HelloWorld.exe程序中,它依赖于vc90 CRT,因此它会在“C:\WINDOWS\WinSxS”和“当前目录”下查找相应的dll库以及manifest文件,(这里指的是xp系统,不考虑vista系统,具体的参见:程序集搜索顺序)
在我的机器上有2个版本的vc90 CRT(由于安装了vc2008 express sp1)
vc90 CRT的dll库位于(9.0.21022.8版本)“C:\WINDOWS\WinSxS\x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_x-ww_d08d0375”
相应的manifest文件则位于“C:\WINDOWS\WinSxS\Manifests\x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_x-ww_d08d0375.manifest”
vc90 CRT的dll库位于(9.0.30729版本)“C:\WINDOWS\WinSxS\x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.30729.1_x-ww_6f74963e”
相应的manifest文件则位于“C:\WINDOWS\WinSxS\Manifests\x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.30729.1_x-ww_6f74963e.manifest”
在这里我们就有一个疑问了,我们的开发环境是vc2008 express sp1,那么我们的程序链接的CRT版本应该是9.0.30729版本的啊?(这个不是我瞎说的,大家可以用dependency walker来查看程序实际链接的DLL版本),为什么在manifest文件中依赖的CRT却是9.0.21022.8版本的? 这里就涉及到一个新的名词“policy ",操作系统会根据C:\WINDOWS\WinSxS\Policies \x86_policy.9.0.Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_x-ww_b7353f75 \9.0.30729.1.policy文件的内容,进行dll版本的跳转(重点看深蓝斜体字部分)从而选择了9.0.30729版本的vc90 CRT (这个所谓的“policy跳转”是道听途说来的,具体的英文资料藏在microsoft的什么地方我就不得而知了。里面夹带了一些我自己的主观猜测,不然的话,没有办法解释manifest版本号9.0.21022.8是,而实际链接的dll的版本号却是9.0.30729)
<?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>
如果我们将这个HelloWorld.exe拷贝到其它的机器上(没有安装vc2008 sp1或Microsoft Visual C++ 2008 SP1 Redistributable Package (x86)),则程序因为没能找到vc90 CRT,而不能运行,弹出“应用程序配置不正确,程序无法启动”对话框。
根据参考资料的文章中的内容,对于release版程序,有一个简单的办法就是安装“vcredist_x86.exe”,文件大小4M左右,自动安装在“C:\WINDOWS\WinSxS”目录下,包含了CRT、MFC、ATL等库的dll和manifest清单文件;整个安装时间不到1分钟。
如果机器上安装了vc2005/vc2008,则会自动的安装vcredist_x86.exe程序,安装后在“控制面板”->“添加删除程序”中有一项“Microsoft Visual c++ 2008 Redistributable - x86 9.0.3.729”(我安装的是Microsoft Visual C++ 2008 SP1 Redistributable Package (x86) 版本)
注意:要根据编译器版本以及vc2005/vc2008是否安装了sp1补丁进行选择对应的vcredist.exe版本
上述的解决办法我称之为共享程序集部署方法,同样的我们也可以采用私有程序集的部署方式来发布程序。
Helloworld例子的私有程序集的部署方法:(针对release版本,仍然是采用上面的例子
,采用参考资料中提到的第2中私有程序集部署方法:将Microsoft.VC90.CRT目录下的manifest文件的版本号修改为9.0.21022.8)
1、将编译后的程序拷贝到一个目录下,假定为d:\helloworld
2、将vc安装目录下的redist\x86目录下的Microsoft.VC90.CRT目录拷贝到d:\helloworld(假定vs安装在C:\Program Files\Microsoft Visual Studio 9.0,则vc安装目录为C:\Program Files\Microsoft Visual Studio 9.0\VC)
3、将Microsoft.VC90.CRT目录下的manifest文件的版本号修改为9.0.21022.8(用记事本打开修改)
最终发布程序的目录结构
D:\helloworld
|--helloworld.exe
|--Microsoft.VC90.CRT
|--Microsoft.VC90.CRT.manifest
|--msvcm90.dll
|--msvcp90.dll
|--msvcr90.dll
这个时候,程序的manifest文件(已经内嵌到exe中了)依赖的vc90 CRT的版本号和Microsoft.VC90.CRT.manifest文件的版本号对应一致,都是9.0.21022.8(但是要注意的是,我们的helloworld程序实际上依赖的vc90 CRT版本是9.0.30729版本,这里只是采用了一种欺骗的方法,因为我们编译时链接的CRT的版本是9.0.30729版本)
三、发生二个DLL版本错乱的原因
http://blogs.msdn.com/vcblog/archive/2008/05/15/vc-runtime-binding.aspx
Virtual C++ 2008提供一个选项,可以选择使用那个版本的DLL,是9.0.21022.8还是9.0.30729.1
1. 9.0.21022.8也称RTM版本
2. 9.0.30729.1也称Current当前版本
VC提供ploicy DLL版本策略转发功能,可以将老版本的DLL请求转发到新版本上,这意味着
1. 使用RTM版本的库可以被指向RTM版本或更高版本
2. 使用当前版本的库可以被指向当前版本或以后新出的版本
对于下面情况,一般使用RTM版本的库
alan使用RTM版本的产品,并发布给客户
MS释放出VS SP1, 客户安装并使用了新版本的库
alan发现他的产品有问题,修复并将其再次发给客户
在这种情况下,alan不知道客户使用的是那个版本的库,他发布的产品使用RTM版本的库,如果有策略转发,则使用新版本的库
另外一种情况
jim使用RTM版本的库开发
jim发现RTM版本库中有bug,并将其提交给MS
MS修复,并发布新版本库
jim使用新版本库,工作正常。
jim的产品是否正常依赖于是否使用新版本的库,如果不能正常使用,还不如不让程序运行
因此我们提供多个版本库,并向后兼容。
四、人工指定使用那个版本的库
在编译时,使用编译选项
1. MFC使用 _BIND_TO_CURRENT_MFC_VERSION=1
见afx.h
2. CRT使用 _BIND_TO_CURRENT_CRT_VERSION=1
见 crtdefs.h 和 crtassem.h
二者同时使用 _BIND_TO_CURRENT_VCLIBS_VERSION=1,但这个参数有问题最好不要用。
转自:http://blog.chinaunix.net/u2/82843/showart.php?id=2032758