科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航



ZDNet>软件频道>基础软件>C++/CLI中有效使用非托管并列缓存

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

    Visual Studio安装程序会把Visual Studio的共享库放在一个称为"并列缓存(side-by-side cache)"的地方,那怎样才能有效地利用它呢?   在文章开头,先看一个示例。在命令行中,创建一个C++源文件。

来源:中国IT实验室 2007年10月02日

关键字:编程 C CLI

    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。

  为找出库所使用的模块列表,可在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值来检查程序集的任一部分是否在部署后已被篡改。

  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。

  为找出库所使用的模块列表,可在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值来检查程序集的任一部分是否在部署后已被篡改。

  版本重定向

  回过头来再看一下为库创建的清单文件,注意程序集所需的版本号给定为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的动作会使强名称签名无效,且程序集也不会加载。

查看本文来源

推广二维码
邮件订阅

如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

重磅专题