程序集清单(Assembly Manifest)的应用

来源:互联网 发布:网络中免费提供的商品 编辑:程序博客网 时间:2024/05/29 13:24

最近在工作中碰到一个软件版本发布的问题:在本地开发了一个服务端程序,在自己的计算机上能毫无问题地运行,但是当发布出去在一个纯净的win7环境下运行无法正常加载动态库,报加载库的错误为“14001”,于是在网上查找相关资料,发现相关解决问题的方式,做一下总结。 原来这一切都是Windows 的Assembly Manifest(程序清单文件)有关。这个文件的作用就是为了解决以前windows上的“Dll 地狱” 问题才产生的新的DLL管理解决方案。大家知道,Dll是动态加载共享库,同一个Dll可能被多个程序所使用,而所谓“Dll 地狱”就是当不通程序依赖的Dll相同,但版本不同时,由于系统不能分辨到底哪个是哪个,所以加载错了Dll版本,然后就挂了。于是win平台搞了一个“程序集清单”的概念,每个运行的程序都要有一个清单,这个清单保存在和自己应用程序同名的.manifest文件中,里面列出其所需要的所有依赖,这儿所列出的依赖可不是简单地靠文件明来区分的,而是根据一种叫做“强文件名”的东西区分的,那么什么是强文件名呢?我们来看一下这个.manifest文件(客户端-使用库的可执行文件)便知道了:

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>

<assemblyxmlns='urn:schemas-microsoft-com:asm.v1'manifestVersion='1.0'>

<dependency>

<dependentAssembly><assemblyIdentitytype='win32'name='Microsoft.VC80.CRT'version='8.0.50608.0'processorArchitecture='x86'publicKeyToken='1fc8b3b9a1e18e3b'/>

</dependentAssembly>

</dependency>

</assembly>

通过上面大家发现原来这是一个XML格式的文件,其中<dependency>这一部分指明了其依赖于一个名字叫做Microsoft.VC80.CRT的库。但是我们发现,<assemblyIdentity>属性里面还有其它的东东,分别是type系统类型,version版本号,processorArchitecture平台环境,publicKeyToken公匙(一般用来标示一个公司),只需要把他们加在一起便组成了“强文件名”,有了这种“强文件名”,我们就可以根据组合文件名区分不同的版本、不同的平台……总之,有了这种强文件名,系统中可以有多个不同版本的相同的库共存而不会发生冲突。 那么现在,我们就来具体了解一下这一套机制。 首先是强弱文件名的问题。正如上面提到的那样,为了区分不同版本或不同厂商生成的相同的程序集,必须用一个Assembly Manifest程序清单来列出我这个程序集的强文件名--慢着,到这里你可能会问:刚才不是说Assembly Manifest程序清单是列出其所依赖的程序集的强文件名呢,怎么这里变成了当前文件的强文件明了呢?其实,Assembly Manifest程序清单有两部分功能,上面这个实例之所以标注了其所依赖的文件的强文件名是因为其是客户端的Assembly Manifest,在服务端有另外一个Manifest 来标注。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<assemblyxmlns="urn:schemas-microsoft-com:asm.v1"manifestVersion="1.0">

<noInheritable>

</noInheritable><assemblyIdentitytype="win32"name="Microsoft.VC80.CRT"version="8.0.50727.42"processorArchitecture="x86"publicKeyToken="1fc8b3b9a1e18e3b">

</assemblyIdentity>

<filename="msvcr80.dll"hash="2a0d797a8c5eac76e54e98db9682e0938c614b45"hashalg="SHA1">

<asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">

<dsig:Transforms>

<dsig:TransformAlgorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform>

</dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1">

</dsig:DigestMethod>

<dsig:DigestValue>phRUExlAeZ8BwmlD8VlO5udAnRE=</dsig:DigestValue>

</asmv2:hash></file><filename="msvcp80.dll"hash="cc4ca55fb6aa6b7bb8577ab4b649ab77e42f8f91"hashalg="SHA1">

<asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">

<dsig:Transforms>

<dsig:TransformAlgorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform>

</dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>7AY1JqoUvK3u/6bYWbOagGgAFbc=</dsig:DigestValue></asmv2:hash></file><filename="msvcm80.dll"hash="55e8e87bbde00d1d96cc119ccd94e0c02c9a2768"hashalg="SHA1">

<asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">

<dsig:Transforms><dsig:TransformAlgorithm="urn:schemas-microsoft-com:HashTransforms.Identity">

</dsig:Transform>

</dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1">

</dsig:DigestMethod><dsig:DigestValue>hWq8zazTsMeKVxWFBa6bnv4hEOw=</dsig:DigestValue>

</asmv2:hash>

</file>

</assembly>

这个便是从WINDOWS/WinSxS/Manifests目录下取出来的一个manifest文件,在这个文件夹下有许多这种XML格式的manifest文件,其是服务端的程序清单,此外,还有一个文件的文件名也与程序集同名,但是后缀名为 .cat,这是一个已签名的安全编目文件,其包含了程序集中文件的hash值,正是因为它已签名,所以可以防止被篡改。WinSxs是windows XP以上版本提供的非托管并行缓存(side-by-side catche)里面安装了各种版本的经过强文件名签名的系统库,而上面这个文件<assemblyIdentity>正是标注了系统中Microsoft.VC80.CRT的一个版本的强文件名签名,如果其和客户端的.manifest 清单里面<dependentAssembly>所列出的依赖项对上的话,就会被加载。刚才说的side-by-side 是指各种不同的版本并行运行。上面这个服务端manifest文件中<file>标签具体指明了当前强文件名签名的到底是哪一个文件,其中还有这个文件的Hash签名,以确保文件的完整性。

把一个程序编译连接成可执行程序后,在别人的电脑上发现找不到其所依赖的库了,那么怎么办呢?首先我们自然想到把其所依赖的库相应的版本拷贝到目标计算机上面,可是……当你在拼命寻找那个可执行文件的assembly manifests文件的时候,却突然发现找不到了,在执行目录下面明明只有一个exe文件(或者是动态库等)。是不是没有生成呢?显然不会,原来是资源连接器把那个assembly manifests文件连接到了可执行文件里面了;不信,你可以用你的vc++打开一个可执行文件看看,在其资源项里面就有一个叫做RT_MANIFEST的项目。这个里面就是二进制标示的manifests文件。那么根据这里面提供的要求,将相应版本的依赖文件(一般就是CRT运行库)拷贝到系统目录Windows/WinSxS/,记住一般会是连带着一个特殊命名的目录一起拷贝到那个文件夹下,比如CRT的运行库就是WinSxS/x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50608.0_x-ww_b7acac55有这样一个目录,其标注了此库的版本号以及签名等信息,以防止多个版本重名时不能复制到同一WinSxS目录下。

这样就搞定了么?如果是以前,那么一切都解决了,系统会在这个目录下面找到这个运行库,可是现在单单这样可不行,系统可是要找到这个运行库的assembly manifests文件,并且对比强文件名之后才能加载,所以所以千万别忘了把相应的manifests文件拷贝到/WinSxS/Manifests目录下面。 当然,这样在目标的系统文件夹下面打动干戈,自然有些过于暴动了,还好,Windows还为我们提供了一种私有查找方式。这种方式会在前面的位置找不到合适库的时候在本地文件夹下面找。所以你只要把之前的库以及那个manifests文件一起拷贝到你的应用程序的路径下面,就可以使用啦。

根据MSDN的说明,在本地查找并加载遵循一下规则: 在应用程序本地文件夹中查找名为 <assemblyName>.manifest 的清单文件。在此示例中,加载程序试图在 appl.exe 所在的文件夹中查找 Microsoft.VC80.CRT.manifest。如果找到该清单,加载程序将从应用程序文件夹中加载 CRT DLL。如果未找到 CRT DLL,加载将失败。 尝试在 appl.exe 本地文件夹中打开文件夹 <assemblyName>,如果存在此文件夹,则从中加载清单文件 <assemblyName>.manifest。如果找到该清单,加载程序将从 <assemblyName> 文件夹中加载 CRT DLL。如果未找到 CRT DLL,加载将失败。 最后,我想补充的一点是,在你的VC++安装目录下面的“Microsoft Visual Studio 8/VC/redist”目录下,有着所有的提供发布的已经配备相应.manifest的库文件。所以你想要发布一个程序最简单最安全的做法(不用担心用户电脑是否包含你所需要的库)就是把这个目录下面的相应的库的文件夹和你的可执行文件放在一起发布。比如在X86平台下如果你的可执行文件用到了CRT库(废话么),那么就拷贝Microsoft Visual Studio 8/VC/redist/x86/Microsoft.VC80.CRT这个文件夹到你的程序所在的目录,一起发布,就万事大吉啦!


vc2008开发的程序的发布方式可以有5种方式:

1.采用静态链接到crt和MFC. 只要你拥有组成程序的所有源代码,你就可以采用这种方式, 这种方式除了程序变大一点,好处多多: 1) 不必重新发布vc2008基础库vcredist_x86.exe(安装到WinSxS).  

2) 不必产生,嵌入manifest.  

3) 也不把vc2008基础库放在程序所在目录.

2.exe(嵌入manifest) + vcredist_x86.exe确保程序正确产生并嵌入manifest文件,然后把程序和vcredist_x86.exe一起发布.用户先安装 vcredist_x86.exe(安装到WinSxS),然后程序就能正常运行了. 

3.exe(嵌入manifest) + 用到的基础库文件放到程序目录(包括库文件本身的manifest文件)确保程序正确产生并嵌入manifest文件,然后把程序用到的vc2008基础库相关文件复制到程序 所在目录,这种方式适用于用户没有安装过vcredist_x86.exe,一旦用户安装过vcredist_x86.exe, 若WinSxS中的相关文件遭到破坏,那么即使在程序目录放上所有用到的vc2008基础库,程序也跑 不起来;若WinSxS中的相关文件正常,那么程序目录下的相关文件就是多余的了,删掉它们程序也能 正常运行.

4.exe(自行编写manifest) + vcredist_x86.exe 

5.exe(自行编写manifest) + 用到的基础库文件放到程序目录(包括库文件本身的manifest文件)**

0 0