贊助商連結


manifest-windows解決DLL地獄的妙方!


manifest原理和用途


dll是被動態調用的,所以會被若干個程序共享使用的 但是如果dll在應用程序不知道的情況下升級了、或是被另一個程序更改了,就可能會出現問題,即『DLL Hell』!

如果你的軟體今天原本運作順暢,當你安裝某個新軟體之後,突然間電腦就無法運作了。這絕對不是你的硬體有問題,也不是應用程式的問題,而是作業系統設計上的缺失,這樣的問題層出不窮,這通常是因為新的應用程式版本覆蓋掉共享的程式庫(DLL),而且往往修改了一些現存應用程式所必需的「bug」,這個缺失有了一個名字叫做DLL Hell (DLL地獄)。開發人員與系統管理者(以及使用者)面臨最大的挑戰就是版本更新的問題 ,他們花很多時間在 Windows 登錄檔 (Regedit) 上試著解決其問題而吃盡苦頭 。[2]


隨著系統資源越來越豐富,硬盤不那麼緊張,所以在XP以後的操作系統中,用新的機制來管理DLL
(這種機制,這不僅僅是對於.NET而言,對於普通的Native程序也是一樣的)
Madifest是個XML的描述文件,對於每個DLL有DLL的Manifest文件,對於每個應用程序Application也有自己的Manifest 對於應用程序而言,Manifest可以是一個和exe文件同一目錄下的.manifest文件,也可以是作為一個資源嵌入在exe文件內部的(Embed Manifest) 。
XP以前版本的windows,會像以前那樣執行這個exe文件,尋找相應的dll,沒有分別
Manifest只是個多餘的文件或資源,dll文件會直接到system32的目錄下查找,並且調用 。
而XP以後的操作系統,則會首先讀取Manifest,獲得exe文件需要調用的DLL列表
(此時獲得的,並不直接是DLL文件的本身的位置,而是DLL的manifest)
操作系統再根據DLL的Manifest去尋找對應的DLL
<因此就可能區別不同版本的同一個DLL文件,或是指定一個程序本身Isolated的DLL>
不過使用Visual Studio 2005以後的一個新問題是,
VS2005帶的8.0新版的C運行庫(VC 8.0 CRT)文件在XP以後支持manifest的Windows版本中被調用時,
將會check一下Application自身的Manifest,否則將會拒絕被調用
這也就是說,使用Visual Studio開發的Application,Manifest將是必不可少的
(搞不懂MS為啥要這樣設置,所以與VS2003.NET不同了)
(後來想想,除了MS自己說的哪些冠冕堂皇的原因,至少這樣一來Linux的Wine模擬要麻煩多了)[1]


[現象]
對這個問題的研究是起源於這麼一個現象:當你用VC++2005(或者其它.NET)寫程序後,在自己的計算機上能毫無問題地運行,但是當把此exe文件拷貝到別人電腦上時,便不能運行了,大致的錯誤提示如下:應用程序配置不正確,請重新安裝程序……或者是MSVCR80D.dll 沒有找到什麼的(我記得不是很清楚,不過大致是這樣的)

[分析]
看到這樣的提示,當然不會傻到重裝咯。第一反應應該是什麼配置有問題、或者是缺少了什麼依賴的庫文件;於是我就根據以前Windows缺少庫文件的經驗,把所有庫文件(××.DLL)統統一股腦地複製到當前文件夾下來,滿心歡喜以為可以運行了,以運行……@#¥@#%¥……還是掛了。
[探索]
於是開始網上搜索,我Google,我擺渡;漸漸我發現,這一切都和一個叫做***.manifest 類型的文件發生關係,那麼到底什麼是 .manifest 文件呢?他有什麼用,以前為什麼沒有?

後來,經過艱苦努力,終於得知,原來這一切都是Windows 的Assembly Manifest搞的鬼。這個東東的作用就是為瞭解決 以前windows上的「Dll 地獄」 問題才產生的新的DLL管理解決方案。大家知道,Dll是動態加載共享庫,同一個Dll可能被多個程序所使用,而所謂「Dll 地獄」就是當不通程序依賴的Dll相同,但版本不同時,由於系統不能分辨到底哪個是哪個,所以加載錯了Dll版本,然後就掛了。於是蓋茨就吸取了教訓,搞了一個程序集清單的東東,每個程序都要有一個清單,這個清單存再和自己應用程序同名的.manifest文件中,裡面列出其所需要的所有依賴,這兒所列出的依賴可不是簡單地靠文件明來區分的,而是根據一種叫做「強文件名」的東西區分的,那麼什麼是強文件明呢?我們來看一下這個.manifest文件便知道了。


<?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>


我們發現原來這是一個XML格式的文件,其中<dependency>這一部分指明了其依賴於一個名字叫做 Microsoft.VC80.CRT的庫。但是我們發現,<assemblyIdentity>屬性裡面還有其它的東東,分別是
type系統類型,version版本號,processorArchitecture平台環境,publicKeyToken公匙(一般用來標示一個公司)……把他們加在一起便成了「強文件名」了,有了這種「強文件名」,我們就可以根據其區分不同的版本、不同的平台……總之,有了這種強文件名,系統中可以有多個不同版本的相同的庫共存而不會發生衝突。



[深入]
恩,那麼現在,我們就來具體瞭解一下這一套機制。
首先是強弱文件名的問題。正如上面提到的那樣,為了區分不同版本或不同廠商生成的相同的程序集,必須用一個Assembly Manifest程序清單來列出我這個程序集的強文件名;慢著,到這裡你可能會問:剛才不是說Assembly Manifest程序清單是列出其所依賴的程序集的強文件名呢,怎麼這裡變成了當前文件的強文件明了呢?其實,Assembly Manifest程序清單有兩部分功能,上面這個實例之所以標註了其所依賴的文件的強文件名是因為其是客戶端的Assembly Manifest,在服務端有另外一個Manifest 來標註。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<noInheritable></noInheritable>
<assemblyIdentity
type="win32" name="Microsoft.VC80.CRT" version="8.0.50727.42" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
<file
name="msvcr80.dll" hash="2a0d797a8c5eac76e54e98db9682e0938c614b45" hashalg="SHA1"><asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:Transforms><dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>phRUExlAeZ8BwmlD8VlO5udAnRE=</dsig:DigestValue></asmv2:hash></file>
<file
name="msvcp80.dll" hash="cc4ca55fb6aa6b7bb8577ab4b649ab77e42f8f91" hashalg="SHA1"><asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:Transforms><dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>7AY1JqoUvK3u/6bYWbOagGgAFbc=</dsig:DigestValue></asmv2:hash></file>
<file
name="msvcm80.dll" hash="55e8e87bbde00d1d96cc119ccd94e0c02c9a2768" hashalg="SHA1"><asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"><dsig:Transforms><dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity"></dsig:Transform></dsig:Transforms><dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></dsig:DigestMethod><dsig:DigestValue>hWq8zazTsMeKVxWFBa6bnv4hEOw=</dsig:DigestValue></asmv2:hash></file>
</assembly>


這個便是從WINDOWS\WinSxS\Manifests目錄下取出來的一個manifest文件,再這個文件夾下有一陀子這種XML格式的 manifest文件,其是服務端的程序清單。WinSxs是windows XP以上版本提供的[blue]非託管並行緩存(side-by-side catche)[/blue]裡面安裝了各種版本的經過強文件名簽名的系統庫,而上面這個文件<assemblyIdentity>正是標註了系統中Microsoft.VC80.CRT的一個版本的強文件名簽名,如果其和客戶端。.manifest 清單裡面<dependentAssembly>所列出的依賴項對上的話,就會被加載。剛才說的side-by-side 是指各種不同的版本並行運行。
上面這個服務端manifest文件中<file>標籤具體指明了當前強文件名簽名的到底是哪一個文件,其中還有這個文件的Hash簽名,以確保文件的完整性。

好了,有了這一套機制,就可以非常非常安全地進行庫文件關聯了,但是、但是貌似還有一個一直困擾我們的問題:這套機制安全是安全了,但是卻失去了以前良好的前後版本兼容性,即如果你的系統庫發生了升級,那麼服務端的版本號發生了變化,那豈不是所有服務端程序都不能使用了嗎?其實,windows還使用一個 policy的策略文件來確認映射關係。


<?xml version="1.0" encoding="UTF-8" standalone="yes"?><!-- Copyright ? 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>


這便是在WINDOWS\WinSxS\Policies目錄下的一個Policy文件,其中<bindingRedirect>標籤便指定了所有8.0.41204.256-8.0.50608.0變本的客戶需求映射到8.0.50727.42這個我現在系統中安裝的比較新的版本的庫。當然我們也能對別的字段進行映射,這樣便能很好解決系統升級帶來的問題。



[應用]
經過以上的講解,大家對整個依賴查找過程都有了一個整體的認識,那麼在實際中問題就好解決了。
讓我們回到實際問題中,我之前說了,把一個程序編譯連接成可執行程序後,在別人的電腦上發現找不到其所依賴的庫了,那麼怎麼辦呢?聰明的你自然想到把其所依賴的庫相應的版本拷貝到目標計算機上面,可是……當你在拚命尋找那個可執行文件的assembly manifests文件的時候,卻突然發現找不到了,在執行目錄下面明明只有一個exe文件嘛。是不是沒有生成呢?顯然不會,原來是資源連接器把那個 assembly manifests文件連接到了可執行文件裡面了;不信,你可以用你的vc++打開一個可執行文件看看,在其資源項裡面就有一個叫做 RT_MANIFEST的項目。這個裡面就是二進制標示的manifests文件。那麼根據這裡面提供的要求,將相應版本的依賴文件(一般就是CRT運行庫)拷貝到系統目錄Windows\WinSxS\,記住一般會是連帶著一個特殊命名的目錄一起拷貝到那個文件夾下,比如CRT的運行庫就是WinSxS \x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50608.0_x-ww_b7acac55有這樣一個目錄,其標註了此庫的版本號以及簽名等信息,以防止多個版本重名時不能複製到同一WinSxS目錄下。

這樣就搞定了麼?如果是以前,那麼一切都解決了,系統會在這個目錄下面找到這個運行庫,可是現在單單這樣可不行,系統可是要找到這個運行庫的 assembly manifests文件,並且對比強文件名之後才能加載,所以所以千萬別忘了把相應的manifests文件拷貝到\WinSxS\Manifests目錄下面。

當然,這樣在目標的系統文件夾下面打動干戈,自然有些過於暴動了,還好,Windows還為我們提供了一種私有查找方式。這種方式會在前面的位置找不到合適庫的時候在本地文件夾下面找。所以你只要把之前的庫以及那個manifests文件一起拷貝到你的應用程序的路徑下面,就可以使用啦。

根據MSDN的說明,在本地查找並加載遵循一下規則:

在應用程序本地文件夾中查找名為 <assemblyName>.manifest 的清單文件。在此示例中,加載程序試圖在 appl.exe 所在的文件夾中查找 Microsoft.VC80.CRT.manifest。如果找到該清單,加載程序將從應用程序文件夾中加載 CRT DLL。如果未找到 CRT DLL,加載將失敗。
嘗試在 appl.exe 本地文件夾中打開文件夾 <assemblyName>,如果存在此文件夾,則從中加載清單文件 <assemblyName>.manifest。如果找到該清單,加載程序將從 <assemblyName> 文件夾中加載 CRT DLL。如果未找到 CRT DLL,加載將失敗。



[最後]
我想補充的一點是,在你的VC++安裝目錄下面的「Microsoft Visual Studio 8\VC\redist」目錄下,有著所有的提供發佈的已經配備相應.manifest的庫文件。所以你想要發佈一個程序最簡單最安全的做法(不用擔心用戶電腦是否包含你所需要的庫)就是把這個目錄下面的相應的庫的文件夾和你的可執行文件放在一起發佈。
比如在X86平台下如果你的可執行文件用到了CRT庫(廢話麼),那麼就拷貝Microsoft Visual Studio 8\VC\redist\x86\Microsoft.VC80.CRT這個文件夾到你的程序所在的目錄,一起發佈,就萬事大吉啦![3]


參考資料:
[1]、http://blog.mcuol.com/User/iwillbeback008/Article/12390_1.htm
[2]、http://www.iiiedu.org.tw/knowledge/knowledge20021130_1.htm
[3]、http://hi.baidu.com/y97523/blog/item/ef5a73f0339050a5a40f5281.html

文章標籤
創作者介紹

宇若彎彎

周宇若 發表在 痞客邦 PIXNET 留言(1) 人氣()


留言列表 (1)

發表留言
  • Keith Tsao
  • 這麼辛苦整理的資料,一定要推。

    雖還沒機會用上,但是很珍貴的經驗...

    感謝
找更多相關文章與討論