其他语言

位置:IT落伍者 >> 其他语言 >> 浏览文章

C++/CLI中有效使用非托管并列缓存


发布日期:2022年03月31日
 
C++/CLI中有效使用非托管并列缓存
Visual Studio安装程序会把Visual Studio的共享库放在一个称为并列缓存(sidebyside cache)的地方那怎样才能有效地利用它呢?

在文章开头先看一个示例在命令行中创建一个C++源文件输入例中的代码(虽然此处使用的是C++/CLI语法但不管你是用C++/CLI托管C++或本地C++都不影响要讲解的主题

libcpp

using namespace System;

public ref class Test

{

public:

void CallMe()

{

Console::WriteLine(called me);

}

};

将其编译为一个托管库程序集

cl /clr /LD libcpp

在此要多留意我们是使用了混合模式(/clr)来编译此代码当然了如果适当修改也能以旧式托管C++语法(/clr:oldsyntax)来编译

下一步创建一个调用此库的C#程序(例当然也可以使用Visual BasicNET不过C#更好一点再与库一起编译

using System;

class App

{

static void Main()

{

Test test = new Test();

testCallMe();

}

}

csc appcs /r:libdll

运行此程序会抛出一个异常

Unhandled Exception:

SystemIOFileNotFoundException:

The specified module could not be found

(Exception from HRESULT: xE)

at AppMain()

怎么会这样呢?打开程序所在的目录库也在那啊HRESULT的高位字为x其代表FACILITY_WIN也就是说这是一个Win错误低位字以十进制表示为在winerrorh中列明其代表ERROR_MOD_NOT_FOUND如果LoadLibrary不能查找到某个模块才会返回这个错误结果因此现在非常清楚了这个错误表示不能查找到一个非托管的DLL

为找出库所使用的模块列表可在ILDASM中加载它并查看MANIFEST如果库是通过平台调用加载DLL的那这些DLL会作为module条目列出然而对这个库来说你将会发现它只用到了托管程序集mscorlib与MicrosoftVisualC两者都在NET全局程序集缓存(GAC)中另有一种可能性在程序集中还存在着非托管代码由它调用了非托管库(例如那些使用托管C++ It Just Works的代码)

为调查清楚从ILDASM的View菜单中选项Headers这将会列出库中的PE文件头向下滚动直至找到导入表(IAT)会得到一份所有从非托管库引入的方法列表因为库是以混合模式编译的因此库用到了C运行时库(CRT)从导入表中也确认了这点它列出了msvcrdll及msvcmdll前者是CRT的DLL多线程版本后者是一个包含了一些CRT托管版本的混合模式库这下非常清楚了错误产生的原因是Windows找不到这两个库或其一

最后查看%systemroot%\system目录下是否有这些库但它们不会在那的此时你可能会指责Visual Studio安装程序没有把最新版本的CRT安装在自己的电脑上但实际上安装程序已经安装了这些CRT库只是不在你原先期待的地方

并列缓存

Visual Studio安装程序会把Visual Studio的共享库放在一个称为并列缓存(sidebyside cache)的地方目录位于%systemroot%\WinSxS且只有SYSTEM及Administrators组成员有写访问权限其他用户只有读取和运行权限并列缓存中包含了程序集不是托管程序集而是非托管的等价物

在WinSxS目录下每个程序集都会有一个目录另外还有两个目录分别是Manifests和Policies其中包含了版本的相关信息以下两个目录与CRT有关

x_MicrosoftVCCRT_fcbbaeeb__xww_deacd

x_MicrosoftVCDebugCRT_fcbbaeeb__xww_febc

显而易见一个是发布版(Release Build)而另一个是调试版(Debug Build)但重点是版本号与一个公有密钥权标也是目录名的一部分如果你查看前一个目录的内容可看到有msvcmdll msvcpdll及msvcrdll它们是被称为MicrosoftVCCRT非托管程序集版本的内容一个非托管程序集可包含一个或多个文件而这些文件也可为包含本地代码或COM对象的DLL一个非托管程序集通常被作为一个单独的单元部署且其中的所有文件由一个被称为清单(manifest)的XML文件来描述

清单文件存储在Manifest目录中且与程序集同名但是后缀名为 manifest这个文件列出了程序集中的所有文件此外还有一个文件的文件名也与程序集同名但是后缀名为 cat这是一个已签名的安全编目文件其包含了程序集中文件的hash值正是因为它已签名所以可以防止被篡改且Windows也能利用这些hash值来检查程序集的任一部分是否在部署后已被篡改

Visual Studio安装程序会把Visual Studio的共享库放在一个称为并列缓存(sidebyside cache)的地方那怎样才能有效地利用它呢?

在文章开头先看一个示例在命令行中创建一个C++源文件输入例中的代码(虽然此处使用的是C++/CLI语法但不管你是用C++/CLI托管C++或本地C++都不影响要讲解的主题

libcpp

using namespace System;

public ref class Test

{

public:

void CallMe()

{

Console::WriteLine(called me);

}

};

将其编译为一个托管库程序集

cl /clr /LD libcpp

在此要多留意我们是使用了混合模式(/clr)来编译此代码当然了如果适当修改也能以旧式托管C++语法(/clr:oldsyntax)来编译

下一步创建一个调用此库的C#程序(例当然也可以使用Visual BasicNET不过C#更好一点再与库一起编译

using System;

class App

{

static void Main()

{

Test test = new Test();

testCallMe();

}

}

csc appcs /r:libdll

运行此程序会抛出一个异常

Unhandled Exception:

SystemIOFileNotFoundException:

The specified module could not be found

(Exception from HRESULT: xE)

at AppMain()

怎么会这样呢?打开程序所在的目录库也在那啊HRESULT的高位字为x其代表FACILITY_WIN也就是说这是一个Win错误低位字以十进制表示为在winerrorh中列明其代表ERROR_MOD_NOT_FOUND如果LoadLibrary不能查找到某个模块才会返回这个错误结果因此现在非常清楚了这个错误表示不能查找到一个非托管的DLL

为找出库所使用的模块列表可在ILDASM中加载它并查看MANIFEST如果库是通过平台调用加载DLL的那这些DLL会作为module条目列出然而对这个库来说你将会发现它只用到了托管程序集mscorlib与MicrosoftVisualC两者都在NET全局程序集缓存(GAC)中另有一种可能性在程序集中还存在着非托管代码由它调用了非托管库(例如那些使用托管C++ It Just Works的代码)

为调查清楚从ILDASM的View菜单中选项Headers这将会列出库中的PE文件头向下滚动直至找到导入表(IAT)会得到一份所有从非托管库引入的方法列表因为库是以混合模式编译的因此库用到了C运行时库(CRT)从导入表中也确认了这点它列出了msvcrdll及msvcmdll前者是CRT的DLL多线程版本后者是一个包含了一些CRT托管版本的混合模式库这下非常清楚了错误产生的原因是Windows找不到这两个库或其一

最后查看%systemroot%\system目录下是否有这些库但它们不会在那的此时你可能会指责Visual Studio安装程序没有把最新版本的CRT安装在自己的电脑上但实际上安装程序已经安装了这些CRT库只是不在你原先期待的地方

并列缓存

Visual Studio安装程序会把Visual Studio的共享库放在一个称为并列缓存(sidebyside cache)的地方目录位于%systemroot%\WinSxS且只有SYSTEM及Administrators组成员有写访问权限其他用户只有读取和运行权限并列缓存中包含了程序集不是托管程序集而是非托管的等价物

在WinSxS目录下每个程序集都会有一个目录另外还有两个目录分别是Manifests和Policies其中包含了版本的相关信息以下两个目录与CRT有关

x_MicrosoftVCCRT_fcbbaeeb__xww_deacd

x_MicrosoftVCDebugCRT_fcbbaeeb__xww_febc

显而易见一个是发布版(Release Build)而另一个是调试版(Debug Build)但重点是版本号与一个公有密钥权标也是目录名的一部分如果你查看前一个目录的内容可看到有msvcmdll msvcpdll及msvcrdll它们是被称为MicrosoftVCCRT非托管程序集版本的内容一个非托管程序集可包含一个或多个文件而这些文件也可为包含本地代码或COM对象的DLL一个非托管程序集通常被作为一个单独的单元部署且其中的所有文件由一个被称为清单(manifest)的XML文件来描述

清单文件存储在Manifest目录中且与程序集同名但是后缀名为 manifest这个文件列出了程序集中的所有文件此外还有一个文件的文件名也与程序集同名但是后缀名为 cat这是一个已签名的安全编目文件其包含了程序集中文件的hash值正是因为它已签名所以可以防止被篡改且Windows也能利用这些hash值来检查程序集的任一部分是否在部署后已被篡改

版本重定向

回过头来再看一下为库创建的清单文件注意程序集所需的版本号给定为再次提醒Visual Studio 安装的程序集是这个叫策略版本重定向在并列缓存的同级Policies目录中可找到下面这个文件夹

x_policyMicrosoftVCCRT_fcbbaeeb_xww_c

注意除了版本部分它有着程序集的全名此文件夹中分别包含了一个策略及安全编目文件文件名基于将要重定向至的版本号

policy

这是一个XML文件(见例这个策略文件是针对版本其也是Visual Studio安装程序所安装的版本它在<bindingRedirect>中指明了所有将要被重定向至本版本的版本号中表明对版本号程序集的所有请求都会被重定向至这个版本与Fusion(混淆 NET中的程序集加载技术)不同的是对并列共享程序集的版本重定向只能是那些生成或修订的版本值之间的变化不能用于主副版本值的变化

<?xml version= encoding=UTF standalone=yes?>

<! Copyright (r) Microsoft Corporation

<assembly xmlns=urn:schemasmicrosoftcom:asmv manifestVersion=

<assemblyIdentity type=winpolicy name=policyMicrosoftVCCRT

version= processorArchitecture=x

publicKeyToken=fcbbaeeb/>

<dependency>

<dependentAssembly>

<assemblyIdentity type=win name=MicrosoftVCCRT

processorArchitecture=x publicKeyToken=fcbbaeeb/>

<bindingRedirect oldVersion=

newVersion=/>

</dependentAssembly>

</dependency>

</assembly>

那就又带出了一个问题那为什么需要重定向呢?为什么链接器不在清单文件中直接指定由安装程序安装的程序集版本呢?原因在于链接器是从导入静态库中获得所需的程序集版本这又引出了另外一个问题为什么链接器要为DLL的不同版本使用导入库而不是安装的那个?原因是这些安装的都是重要的库

目前为止的讨论都是针对托管C++编译器(C++/CLI及旧式语法)然而即便本地C++开发技巧再高也有可能被这些新特性所影响如果你的代码使用了某个共享的Visual Studio本地库(MFCATL或CRT)那么必须有一个单独的manifest清单文件要么绑定至可执行文件要么只绑定至一个 exe文件

结论

以前Microsoft C++编译器及链接器的各个版本所生成的库都能被Windows加载并运行但Visual Studio 中的版本生成的库却无法运行

此处有两个解决方法第一种方法是运行链接器两次一次是生成清单文件其能编译进非托管资源接着一次是把这个清单绑定至PE文件这也是本文所推荐的方法因为如果在构造一个具有强名称的程序集在第二次调用时就能提供密钥文件或容器的名称

另一个方法是使用mtexe未公开的选项来修改程序集然而如果使用链接器来生成一个强名称的程序集mtexe的动作会使强名称签名无效且程序集也不会加载

上一篇:Visual C++开发数据库基础之ADO篇

下一篇:初学者编程入门:学习C++的最大难度