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"); } }; |
将其编译为一个托管库程序集:
在此要多留意,我们是使用了混合模式(/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。
为找出库所使用的模块列表,可在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值来检查程序集的任一部分是否在部署后已被篡改。
客户清单文件
一个客户文件(程序或库)能依赖于程序集中的某个文件来构建,但客户文件只会依赖于程序集的某个特定版本来构建,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 <<$@ #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.d
阅读(163)
(责任编辑:城市网)