C++/CLI中有效利用非托管并列缓存
副标题#e#
Visual Studio安装措施会把Visual Studio的共享库放在一个称为"并列缓存(side-by-side cache)"的处所,那奈何才气有效地操作它呢?
在文章开头,先看一个示例。在呼吁行中,建设一个C++源文件,输入例1中的代码。(固然此处利用的是C++/CLI语法,但不管你是用C++/CLI、托管C++、或当地C++,都不影响要讲授的主题。)
例1:lib.cpp
using namespace System;
public ref class Test
{
public:
void CallMe()
{
Console::WriteLine("called me");
}
};
将其编译为一个托管库措施集:
cl /clr /LD lib.cpp
在此要多寄望,我们是利用了殽杂模式(/clr)来编译此代码,虽然了,假如适当修改,也能以旧式托管C++语法(/clr:oldsyntax)来编译。
下一步,建设一个挪用此库的C#措施(例2),虽然也可以利用Visual Basic.NET,不外C#更好一点。再与库一起编译:
例2:
using System;
class App
{
static void Main()
{
Test test = new Test();
test.CallMe();
}
}
csc app.cs /r:lib.dll
运行此措施,会抛出一个异常:
Unhandled Exception:
System.IO.FileNotFoundException:
The specified module could not be found.
(Exception from HRESULT: 0x8007007E)
at App.Main()
怎么会这样呢?打开措施地址的目次,库也在那啊。HRESULT的高位字为0x8007,其代表FACILITY_WIN32,也就是说,这是一个Win32错误;低位字以十进制暗示为126,在winerror.h中列明其代表ERROR_MOD_NOT_FOUND。假如LoadLibrary不能查找到某个模块,才会返回这个错误功效,因此,此刻很是清楚了,这个错误暗示不能查找到一个非托管的DLL。
#p#副标题#e#
为找出库所利用的模块列表,可在ILDASM中加载它,并查察MANIFEST。假如库是通过平台挪用加载DLL的,那这些DLL会作为.module条目列出,然而,对这个库来说,你将会发明,它只用到了托管措施集mscorlib与Microsoft.VisualC,两者都在.NET全局措施集缓存(GAC)中。还有一种大概性,在措施会合,还存在着非托管代码,由它挪用了非托管库(譬喻,那些利用托管C++ It Just Works的代码)。
为观测清楚,从ILDASM的View菜单中选项Headers,这将会列出库中的PE文件头。向下转动直至找到导入表(IAT),会获得一份所有从非托管库引入的要领列表。因为库是以殽杂模式编译的,因此库用到了C运行时库(CRT),从导入表中也确认了这点–它列出了msvcr80.dll及msvcm80.dll,前者是CRT的DLL多线程版本,后者是一个包括了一些CRT托管版本的殽杂模式库。这下很是清楚了,错误发生的原因是Windows找不到这两个库、或其一。
最后,查察%systemroot%\system32目次下是否有这些库–但它们不会在那的,此时,你大概会指责Visual Studio安装措施没有把最新版本的CRT安装在本身的电脑上,但实际上,安装措施已经安装了这些CRT库–只是不在你原先等候的处所。
并列缓存
Visual Studio安装措施会把Visual Studio的共享库放在一个称为"并列缓存(side-by-side cache)"的处所,目次位于%systemroot%\WinSxS,且只有SYSTEM及Administrators构成员有写会见权限,其他用户只有读取和运行权限。并列缓存中包括了"措施集"–不是托管措施集,而长短托管的等价物。
在WinSxS目次下,每个措施集城市有一个目次,别的,尚有两个目次别离是Manifests和Policies,个中包括了版本的相关信息。以下两个目次与CRT有关:
x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.42_x-ww_0de06acd
x86_Microsoft.VC80.DebugCRT_1fc8b3b9a1e18e3b_8.0.50727.42_x-ww_f75eb16c
显而易见,一个是宣布版(Release Build),而另一个是调试版(Debug Build),但重点是,版本号与一个公有密钥权标也是目次名的一部门。假如你查察前一个目次的内容,可看到有msvcm80.dll、 msvcp80.dll、及msvcr80.dll,它们是被称为"Microsoft.VC80.CRT"非托管措施集8.0.50727.42版本的内容。一个非托管措施集可包括一个或多个文件,而这些文件也可为包括当地代码或COM工具的DLL。一个非托管措施集凡是被作为一个单独的单位陈设,且个中的所有文件由一个被称为"清单(manifest)"的XML文件来描写。
清单文件存储在Manifest目次中,且与措施集同名,可是后缀名为 .manifest。这个文件列出了措施会合的所有文件;另外,尚有一个文件的文件名也与措施集同名,可是后缀名为 .cat,这是一个已签名的安详编目文件,其包括了措施会合文件的hash值,正是因为它已签名,所以可以防备被改动,且Windows也能操作这些hash值来查抄措施集的任一部门是否在陈设后已被改动。
客户清单文件
#p#分页标题#e#
一个客户文件(措施或库)能依赖于措施会合的某个文件来构建,但客户文件只会依赖于措施集的某个特定版原来构建,Windows也只会加载所需的特定版本。为标出所需共享措施集的版本,一个可执行文件(措施或库)也必需有一个清单文件(manifest)。链接器在可执行文件生成时,会为其建设一个包括清单信息的文件,因此,假如回过甚来看一下前面生成的库的目次,会找到一个名为"lib.dll.manifest"的文件,例3是其的内容。
例3:
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<dependency>
<dependentAssembly>
<assemblyIdentity type='win32' name='Microsoft.VC80.CRT'
version='8.0.50608.0' processorArchitecture='x86'
publicKeyToken='1fc8b3b9a1e18e3b' />
</dependentAssembly>
</dependency>
</assembly>
正如各人所瞥见的,它说明托管措施集lib.dll依赖于Microsoft.VC80.CRT共享并列措施会合的某些文件。尽量这个文件位于库lib.dll的同一目次中,但Windows明明不会用到它,而这与MSDN中写明的有点南辕北辙:
(MSDN):你可在应用措施二进制可执行头文件中包括应用措施清单文件……,作为备选方案,也可把一个单独的清单文件放在应用措施可执行文件的同一目次中,操纵系统会首先从文件系统中加载此清单文件,并查抄可执行文件的资源节。文件系统的版本具有优先权。
然而,完全不是这么回事。对库而言,Windows会忽略清单文件,尽量如此,文档照旧给出了奈何办理这个问题的一个线索,清单文件必然要以资源ID为2的非托管资源RT_MANIFEST形式绑定到可执行文件。
在此有两种要领:第一种要领是建设一个包括对清单文件引用的资源剧本文件:
#include <winuser.h>
2 RT_MANIFEST lib.dll.manifest
它会在今后通过Windows资源编译器rc.exe编译为一个资源,并通过链接器限定为一个非托管资源。这种要领的问题之处在于,是链接器建设了这个清单文件,因此必需运行两次链接器:一次是为生成清单文件,一次是把资源链接到最终生成文件。例4是一个典型makefile,演示了如何举办操纵:
例4:
# The main target
all: app.exe
# A C# process that depends upon a Managed C++ library
app.exe : app.cs lib.dll
csc app.cs /r:lib.dll
# This is the second invocation of the linker, so the object file and
# manifest will already exist, so they do not need to be rebuilt.
lib.dll : lib.cpp lib.res lib.obj
link /DLL /manifest:no /machine:x86 lib.res lib.obj
lib.res : lib.rc
rc lib.rc
# Create a temporary resource script that binds the manifest file to the DLL
lib.rc : lib.dll.manifest
type <<[email protected]
#include <winuser.h>
2 RT_MANIFEST lib.dll.manifest
<<KEEP
# Create the object file, and invoke the linker to create the manifest file
lib.dll.manifest lib.obj : lib.cpp
cl /LD /clr lib.cpp
另一个要领是利用mt.exe未果真的埋没选项把资源绑定到最终生成文件上,这也是Visual Studio 2005建设加载的C++库(托管殽杂模式或非托管模式)时所利用的要领。两个埋没选项别离为/manifest和/outputresource:前者用于指定清单文件名,尔后者用于指出将要修改的PE文件及清单资源的资源ID。一般而言,对库来说,资源ID应为2;对措施来说,应为1。请看以下示例:
mt /manifest lib.dll.manifest
/outputresource:lib.dll;#2
留意此处的区别:/manifest选项后跟的参数是用空格脱离的;而/outputresource选项后跟的参数是用冒号脱离的。明明看得出,这两个选项是由差异的措施员开拓的。
一旦你把清单文章绑定到库,Windows就可以判定需加载措施集的正确版本。假如在作出这些修改之后,运行前面的C#措施,也会发明措施运行正常。
假如你生成了一个殽杂模式(/clr)或纯前言语言模式(/clr:pure)的托管C++措施,来利用这个殽杂模式的库,链接器也建设了一个相应的措施清单文件,当此措施运行时,Windows会查找资源ID为1的清单文件,或查找名为manifest的相应文件。因为殽杂模式或纯前言语言模式措施都用到了CRT,意味着将会在清单文件中提及CRT措施集,所以,在这个特例中,库不需要清单文件。然而,你不该该依赖这个机制,因为在本例中,措施利用同一个非托管措施集作为库是一个偶尔环境。
版本重定向
#p#分页标题#e#
回过甚来再看一下为库建设的清单文件,留意措施集所需的版本号给定为8.0.50608.0,再次提醒,Visual Studio 2005安装的措施集是8.0.50727.42,这个叫计策版本重定向。在并列缓存的同级Policies目次中,可找到下面这个文件夹:
x86_policy.8.0.Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_x-ww_77c24773
留意,除了版本部门,它有着措施集的全名。此文件夹中别离包括了一个计策及安详编目文件,文件名基于将要重定向至的版本号:
8.0.50727.42.policy
这是一个XML文件(见例5)。这个计策文件是针对版本8.0.50727.42的,其也是Visual Studio安装措施所安装的版本。它在<bindingRedirect>中指明白所有将要被重定向至本版本的版本号,例5中表白,对版本号8.0.41204.256至8.0.50608.0措施集的所有请求,城市被重定向至8.0.50727.42这个版本。与Fusion(夹杂: .NET中的措施集加载技能)差异的是,对并列共享措施集的版本重定向只能是那些生成或修订的版本值之间的变革,不能用于主、副版本值的变革。
例5:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Copyright (r) 1981-2001 Microsoft Corporation -->
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32-policy" name="policy.8.0.Microsoft.VC80.CRT"
version="8.0.50727.42" processorArchitecture="x86"
publicKeyToken="1fc8b3b9a1e18e3b"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.VC80.CRT"
processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"/>
<bindingRedirect oldVersion="8.0.41204.256-8.0.50608.0"
newVersion="8.0.50727.42"/>
</dependentAssembly>
</dependency>
</assembly>
那就又带出了一个问题:那为什么需要重定向呢?为什么链接器不在清单文件中直接指定由安装措施安装的措施集版本呢?原因在于,链接器是从导入静态库中得到所需的措施集版本。这又引出了别的一个问题:为什么链接器要为DLL的差异版本利用导入库,而不是安装的谁人?原因是,这些安装的都是重要的库。
今朝为止的接头都是针对托管C++编译器(C++/CLI及旧式语法),然而,即便当地C++开拓能力再高,也有大概被这些新"特性"所影响。假如你的代码利用了某个共享的Visual Studio当地库(MFC、ATL或CRT),那么,必需有一个单独的.manifest清单文件,要么绑定至可执行文件,要么只绑定至一个 .exe文件。
结论
以前Microsoft C++编译器及链接器的各个版本所生成的库,都能被Windows加载并运行,但Visual Studio 2005中的版本14,生成的库却无法运行。
此处有两个办理要领:第一种要领是运行链接器两次,一次是生成清单文件,其能编译进非托管资源,接着一次是把这个清单绑定至PE文件。这也是本文所推荐的要领,因为假如在结构一个具有"强名称"的措施集,在第二次挪用时,就能提供密钥文件或容器的名称。
另一个要领是,利用mt.exe未果真的选项来修改措施集,然而,假如利用链接器来生成一个"强名称"的措施集,mt.exe的行动会使强名称签名无效,且措施集也不会加载。