熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

C++/CLI中有效使用非托管並列緩存

2022-06-13   來源: .NET編程 
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的動作會使強名稱簽名無效且程序集也不會加載


From:http://tw.wingwit.com/Article/program/net/201311/12469.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.