Visual Studio安裝程序會把Visual Studio的共享庫放在一個稱為
並列緩存(side
by
side cache)
的地方
那怎樣才能有效地利用它呢?
在文章開頭
先看一個示例
在命令行中
創建一個C++源文件
輸入例
中的代碼
(雖然此處使用的是C++/CLI語法
但不管你是用C++/CLI
托管C++
或本地C++
都不影響要講解的主題
)
例
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#程序(例
)
當然也可以使用Visual Basic
NET
不過C#更好一點
再與庫一起編譯
例
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:
x
E)
at App
Main()
怎麼會這樣呢?打開程序所在的目錄
庫也在那啊
HRESULT的高位字為
x
其代表FACILITY_WIN
也就是說
這是一個Win
錯誤
低位字以十進制表示為
在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)
從導入表中也確認了這點
它列出了msvcr
dll及msvcm
dll
前者是CRT的DLL多線程版本
後者是一個包含了一些CRT托管版本的混合模式庫
這下非常清楚了
錯誤產生的原因是Windows找不到這兩個庫
或其一
最後
查看%systemroot%\system
目錄下是否有這些庫
但它們不會在那的
此時
你可能會指責Visual Studio安裝程序沒有把最新版本的CRT安裝在自己的電腦上
但實際上
安裝程序已經安裝了這些CRT庫
只是不在你原先期待的地方
並列緩存 Visual Studio安裝程序會把Visual Studio的共享庫放在一個稱為
並列緩存(side
by
side cache)
的地方
目錄位於%systemroot%\WinSxS
且只有SYSTEM及Administrators組成員有寫訪問權限
其他用戶只有讀取和運行權限
並列緩存中包含了
程序集
不是托管程序集
而是非托管的等價物
在WinSxS目錄下
每個程序集都會有一個目錄
另外
還有兩個目錄分別是Manifests和Policies
其中包含了版本的相關信息
以下兩個目錄與CRT有關
x
_Microsoft
VC
CRT_
fc
b
b
a
e
e
b_
_x
ww_
de
acd
x
_Microsoft
VC
DebugCRT_
fc
b
b
a
e
e
b_
_x
ww_f
eb
c
顯而易見一個是發布版(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