恩,為了大家都能很方便的理解,我將盡量簡(jiǎn)單通俗地進(jìn)行描述。
[現(xiàn)象]
對(duì)這個(gè)問(wèn)題的研究是起源于這么一個(gè)現(xiàn)象:當(dāng)你用VC++2005(或者其它.NET)寫程序后,在自己的計(jì)算機(jī)上能毫無(wú)問(wèn)題地運(yùn)行,但是當(dāng)把此exe文件拷貝到別人電腦上時(shí),便不能運(yùn)行了,大致的錯(cuò)誤提示如下:應(yīng)用程序配置不正確,請(qǐng)重新安裝程序……或者是MSVCR80D.dll 沒(méi)有找到什么的(我記得不是很清楚,不過(guò)大致是這樣的)
[分析]
看到這樣的提示,當(dāng)然不會(huì)傻到重裝咯。第一反應(yīng)應(yīng)該是什么配置有問(wèn)題、或者是缺少了什么依賴的庫(kù)文件;于是我就根據(jù)以前Windows缺少庫(kù)文件的經(jīng)驗(yàn),把所有庫(kù)文件(××.DLL)統(tǒng)統(tǒng)一股腦地復(fù)制到當(dāng)前文件夾下來(lái),滿心歡喜以為可以運(yùn)行了,以運(yùn)行……@#¥@#%¥……還是掛了。
[探索]
于是開始網(wǎng)上搜索,我Google,我擺渡;漸漸我發(fā)現(xiàn),這一切都和一個(gè)叫做***.manifest 類型的文件發(fā)生關(guān)系,那么到底什么是 .manifest 文件呢?他有什么用,以前為什么沒(méi)有?
后來(lái),經(jīng)過(guò)艱苦努力,終于得知,原來(lái)這一切都是Windows 的Assembly Manifest搞的鬼。這個(gè)東東的作用就是為了解決 以前windows上的“Dll 地獄” 問(wèn)題才產(chǎn)生的新的DLL管理解決方案。大家知道,Dll是動(dòng)態(tài)加載共享庫(kù),同一個(gè)Dll可能被多個(gè)程序所使用,而所謂“Dll 地獄”就是當(dāng)不通程序依賴的Dll相同,但版本不同時(shí),由于系統(tǒng)不能分辨到底哪個(gè)是哪個(gè),所以加載錯(cuò)了Dll版本,然后就掛了。于是蓋茨就吸取了教訓(xùn),搞了一個(gè)程序集清單的東東,每個(gè)程序都要有一個(gè)清單,這個(gè)清單存再和自己應(yīng)用程序同名的.manifest文件中,里面列出其所需要的所有依賴,這兒所列出的依賴可不是簡(jiǎn)單地靠文件明來(lái)區(qū)分的,而是根據(jù)一種叫做“強(qiáng)文件名”的東西區(qū)分的,那么什么是強(qiáng)文件明呢?我們來(lái)看一下這個(gè).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>
我們發(fā)現(xiàn)原來(lái)這是一個(gè)XML格式的文件,其中<dependency>這一部分指明了其依賴于一個(gè)名字叫做Microsoft.VC80.CRT的庫(kù)。但是我們發(fā)現(xiàn),<assemblyIdentity>屬性里面還有其它的東東,分別是
type系統(tǒng)類型,version版本號(hào),processorArchitecture平臺(tái)環(huán)境,publicKeyToken公匙(一般用來(lái)標(biāo)示一個(gè)公司)……把他們加在一起便成了“強(qiáng)文件名”了,有了這種“強(qiáng)文件名”,我們就可以根據(jù)其區(qū)分不同的版本、不同的平臺(tái)……總之,有了這種強(qiáng)文件名,系統(tǒng)中可以有多個(gè)不同版本的相同的庫(kù)共存而不會(huì)發(fā)生沖突。
[深入]
恩,那么現(xiàn)在,我們就來(lái)具體了解一下這一套機(jī)制。
首先是強(qiáng)弱文件名的問(wèn)題。正如上面提到的那樣,為了區(qū)分不同版本或不同廠商生成的相同的程序集,必須用一個(gè)Assembly Manifest程序清單來(lái)列出我這個(gè)程序集的強(qiáng)文件名--慢著,到這里你可能會(huì)問(wèn):剛才不是說(shuō)Assembly Manifest程序清單是列出其所依賴的程序集的強(qiáng)文件名呢,怎么這里變成了當(dāng)前文件的強(qiáng)文件明了呢?其實(shí),Assembly Manifest程序清單有兩部分功能,上面這個(gè)實(shí)例之所以標(biāo)注了其所依賴的文件的強(qiáng)文件名是因?yàn)槠涫强蛻舳说腁ssembly Manifest,在服務(wù)端有另外一個(gè)Manifest 來(lái)標(biāo)注。
<?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>
這個(gè)便是從WINDOWS\WinSxS\Manifests目錄下取出來(lái)的一個(gè)manifest文件,再這個(gè)文件夾下有一陀子這種XML格式的manifest文件,其是服務(wù)端的程序清單。WinSxs是windows XP以上版本提供的[blue]非托管并行緩存(side-by-side catche)[/blue]里面安裝了各種版本的經(jīng)過(guò)強(qiáng)文件名簽名的系統(tǒng)庫(kù),而上面這個(gè)文件<assemblyIdentity>正是標(biāo)注了系統(tǒng)中Microsoft.VC80.CRT的一個(gè)版本的強(qiáng)文件名簽名,如果其和客戶端。.manifest 清單里面<dependentAssembly>所列出的依賴項(xiàng)對(duì)上的話,就會(huì)被加載。剛才說(shuō)的side-by-side 是指各種不同的版本并行運(yùn)行。
上面這個(gè)服務(wù)端manifest文件中<file>標(biāo)簽具體指明了當(dāng)前強(qiáng)文件名簽名的到底是哪一個(gè)文件,其中還有這個(gè)文件的Hash簽名,以確保文件的完整性。
好了,有了這一套機(jī)制,就可以非常非常安全地進(jìn)行庫(kù)文件關(guān)聯(lián)了,但是、但是貌似還有一個(gè)一直困擾我們的問(wèn)題:這套機(jī)制安全是安全了,但是卻失去了以前良好的前后版本兼容性,即如果你的系統(tǒng)庫(kù)發(fā)生了升級(jí),那么服務(wù)端的版本號(hào)發(fā)生了變化,那豈不是所有服務(wù)端程序都不能使用了嗎?其實(shí),windows還使用一個(gè)policy的策略文件來(lái)確認(rèn)映射關(guān)系。
<?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目錄下的一個(gè)Policy文件,其中<bindingRedirect>標(biāo)簽便指定了所有8.0.41204.256-8.0.50608.0變本的客戶需求映射到8.0.50727.42這個(gè)我現(xiàn)在系統(tǒng)中安裝的比較新的版本的庫(kù)。當(dāng)然我們也能對(duì)別的字段進(jìn)行映射,這樣便能很好解決系統(tǒng)升級(jí)帶來(lái)的問(wèn)題。
[應(yīng)用]
經(jīng)過(guò)以上的講解,大家對(duì)整個(gè)依賴查找過(guò)程都有了一個(gè)整體的認(rèn)識(shí),那么在實(shí)際中問(wèn)題就好解決了。
讓我們回到實(shí)際問(wèn)題中,我之前說(shuō)了,把一個(gè)程序編譯連接成可執(zhí)行程序后,在別人的電腦上發(fā)現(xiàn)找不到其所依賴的庫(kù)了,那么怎么辦呢?聰明的你自然想到把其所依賴的庫(kù)相應(yīng)的版本拷貝到目標(biāo)計(jì)算機(jī)上面,可是……當(dāng)你在拼命尋找那個(gè)可執(zhí)行文件的assembly manifests文件的時(shí)候,卻突然發(fā)現(xiàn)找不到了,在執(zhí)行目錄下面明明只有一個(gè)exe文件嘛。是不是沒(méi)有生成呢?顯然不會(huì),原來(lái)是資源連接器把那個(gè)assembly manifests文件連接到了可執(zhí)行文件里面了;不信,你可以用你的vc++打開一個(gè)可執(zhí)行文件看看,在其資源項(xiàng)里面就有一個(gè)叫做RT_MANIFEST的項(xiàng)目。這個(gè)里面就是二進(jìn)制標(biāo)示的manifests文件。那么根據(jù)這里面提供的要求,將相應(yīng)版本的依賴文件(一般就是CRT運(yùn)行庫(kù))拷貝到系統(tǒng)目錄Windows\WinSxS\,記住一般會(huì)是連帶著一個(gè)特殊命名的目錄一起拷貝到那個(gè)文件夾下,比如CRT的運(yùn)行庫(kù)就是WinSxS\x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50608.0_x-ww_b7acac55有這樣一個(gè)目錄,其標(biāo)注了此庫(kù)的版本號(hào)以及簽名等信息,以防止多個(gè)版本重名時(shí)不能復(fù)制到同一WinSxS目錄下。
這樣就搞定了么?如果是以前,那么一切都解決了,系統(tǒng)會(huì)在這個(gè)目錄下面找到這個(gè)運(yùn)行庫(kù),可是現(xiàn)在單單這樣可不行,系統(tǒng)可是要找到這個(gè)運(yùn)行庫(kù)的assembly manifests文件,并且對(duì)比強(qiáng)文件名之后才能加載,所以所以千萬(wàn)別忘了把相應(yīng)的manifests文件拷貝到\WinSxS\Manifests目錄下面。
當(dāng)然,這樣在目標(biāo)的系統(tǒng)文件夾下面打動(dòng)干戈,自然有些過(guò)于暴動(dòng)了,還好,Windows還為我們提供了一種私有查找方式。這種方式會(huì)在前面的位置找不到合適庫(kù)的時(shí)候在本地文件夾下面找。所以你只要把之前的庫(kù)以及那個(gè)manifests文件一起拷貝到你的應(yīng)用程序的路徑下面,就可以使用啦。
根據(jù)MSDN的說(shuō)明,在本地查找并加載遵循一下規(guī)則:
在應(yīng)用程序本地文件夾中查找名為 <assemblyName>.manifest 的清單文件。在此示例中,加載程序試圖在 appl.exe 所在的文件夾中查找 Microsoft.VC80.CRT.manifest。如果找到該清單,加載程序?qū)膽?yīng)用程序文件夾中加載 CRT DLL。如果未找到 CRT DLL,加載將失敗。
嘗試在 appl.exe 本地文件夾中打開文件夾 <assemblyName>,如果存在此文件夾,則從中加載清單文件 <assemblyName>.manifest。如果找到該清單,加載程序?qū)?<assemblyName> 文件夾中加載 CRT DLL。如果未找到 CRT DLL,加載將失敗。
最后,我想補(bǔ)充的一點(diǎn)是,在你的VC++安裝目錄下面的“Microsoft Visual Studio 8\VC\redist”目錄下,有著所有的提供發(fā)布的已經(jīng)配備相應(yīng).manifest的庫(kù)文件。所以你想要發(fā)布一個(gè)程序最簡(jiǎn)單最安全的做法(不用擔(dān)心用戶電腦是否包含你所需要的庫(kù))就是把這個(gè)目錄下面的相應(yīng)的庫(kù)的文件夾和你的可執(zhí)行文件放在一起發(fā)布。
比如在X86平臺(tái)下如果你的可執(zhí)行文件用到了CRT庫(kù)(廢話么),那么就拷貝Microsoft Visual Studio 8\VC\redist\x86\Microsoft.VC80.CRT這個(gè)文件夾到你的程序所在的目錄,一起發(fā)布,就萬(wàn)事大吉啦!