直播中
DLL Hell 一個主要目的就是共享當(dāng)前在基于組件的系統(tǒng)中使用的模型。默認(rèn)情況下,單獨(dú)的軟件組件由機(jī)器上的多個應(yīng)用程序共享。例如,每次一個安裝程序復(fù)制一個 DLL 到系統(tǒng)目錄或在 COM 注冊表中注冊一個類,該代碼將潛在地影響其他運(yùn)行在機(jī)器上的應(yīng)用程序。實(shí)際上,如果一個已存在的應(yīng)用程序使用共享組件的前一個版本,那么該應(yīng)用程序?qū)⒆詣邮褂眯掳姹?。如果共享組件是嚴(yán)格向后兼容的這當(dāng)然更好,但如果不可能,在許多情況下維護(hù)向后兼容是很困難的。如果沒有維持向后兼容或不能維持,作為其他應(yīng)用程序安裝時的側(cè)面影響經(jīng)常導(dǎo)致應(yīng)用程序中斷。
.NET 設(shè)計(jì)方針的一個原則就是隔離組件(或匯編)。隔離一個匯編的意思是一個匯編只能由一個應(yīng)用程序訪問—不是由機(jī)器上的多個應(yīng)用程序共享并且不可能因其他應(yīng)用程序?qū)ο到y(tǒng)的改變而影響。隔離賦予開發(fā)者對應(yīng)用程序所用代碼的絕對控制。隔離,或應(yīng)用程序?qū)S脜R編期望在 .NET 應(yīng)用程序中是默認(rèn)的。隔離組件的趨勢在 Microsoft Windows 2000 中隨著 .local 文件的引入已經(jīng)開始。該文件用于努力定位所需組件時使 OS Loader 和 COM 首先從應(yīng)用程序目錄查找。(請參閱 MSDN Library 中的相關(guān)文檔,Implementing Side-by-Side Component Sharing in Applications(英文)。)
然而,有些情況下在應(yīng)用程序之間共享匯編是必要的。很明顯每個應(yīng)用程序都有自己的 System.Winforms、System.ASP 或公用的 Web 表格控件的副本是沒有意義的。
在 .NET 中,在應(yīng)用程序之間共享代碼是明確的決定。共享匯編需要一些附加的需求。特別是,共享匯編應(yīng)該支持相同的匯編并排多個版本安裝和運(yùn)行在相同的機(jī)器上,或者甚至在相同的進(jìn)程中,在相同的時間。另外,共享匯編有更嚴(yán)格的命名需要。例如,一個共享的匯編必須有一個全局唯一的名稱。
隔離和共享的需要導(dǎo)致我們考慮兩種匯編。這是個相當(dāng)松散的集合,在這兩種匯編之間沒有實(shí)際的結(jié)構(gòu),但它們?nèi)绾问褂檬遣煌模簩S糜谀硞€應(yīng)用程序或與許多應(yīng)用程序共享。
應(yīng)用程序?qū)S脜R編
應(yīng)用程序?qū)S脜R編是只對某個應(yīng)用程序可視的匯編。我們期望這是 .NET 應(yīng)用程序最普通的情況,因?yàn)?.NET 框架幫助建立從其它應(yīng)用程序引起的系統(tǒng)變化中隔離的應(yīng)用程序。
專用匯編的命名需求很簡單:匯編名稱必須在應(yīng)用程序中是唯一的。沒必要起全局唯一的名稱。保持名稱唯一不是問題因?yàn)閼?yīng)用程序開發(fā)者完全控制哪個匯編與應(yīng)用程序隔離。
應(yīng)用程序?qū)S脜R編部署在使用它們的應(yīng)用程序目錄結(jié)構(gòu)中。專用匯編可以直接放在應(yīng)用程序的目錄或它的子目錄中。通用語言運(yùn)行時間通過稱為 probing 的進(jìn)程查找這些匯編。"Probing" 是匯編名稱到包含清單的文件名稱之間的簡單映射。
特別地,通用語言運(yùn)行時間把匯編的名稱記錄在匯編引用中,追加“.dll” 并在應(yīng)用程序的目錄中查找該文件。該方案中有一些變量,在那里運(yùn)行時間會訪問匯編命名的子目錄中或匯編的風(fēng)格命名的子目錄。例如,某個開發(fā)者會選擇將包含定位于德國的資源的匯編部署在稱為“de”的子目錄中,并將西班牙的資源部署在稱為“es”的子目錄中。
如前所述,每個匯編清單包括有關(guān)其關(guān)系的版本信息。該版本信息沒有為專用匯編而加強(qiáng),因?yàn)殚_發(fā)人員完全控制了部署到應(yīng)用程序目錄的匯編。
共享匯編
.NET 框架還支持共享匯編的概念。共享匯編是在機(jī)器上由多個應(yīng)用程序使用的。使用 .NET,共享應(yīng)用程序之間的代碼是明確的決定。共享匯編有些額外的需求用于解決現(xiàn)在我們經(jīng)歷的共享問題。除了支持早先描述的并列之外,共享匯編還有許多嚴(yán)格的命名需求。例如,共享匯編必須有一個全局唯一的名稱。而且系統(tǒng)必須提供“名稱保護(hù)”—更確切的說,防止有人再使用編寫者的匯編名稱。例如,假設(shè)您是一個網(wǎng)格控件的廠家,并且發(fā)布了您的匯編版本 1。做為編寫您需要確信沒有其他人能發(fā)布聲稱為版本 2 的匯編或您的網(wǎng)格控件。.NET 框架支持通過稱為共享名的技術(shù)支持支持這些命名需求。(在下一節(jié)詳細(xì)說明)。
通常,應(yīng)用程序編寫者不對應(yīng)用程序使用的共享匯編有同等程度的控制。結(jié)果,在每次引用共享匯編時都檢查版本信息。另外,.NET 框架允許應(yīng)用程序和管理員通過指定版本策略重載應(yīng)用程序使用的共享匯編版本。
共享匯編通常部署到全局匯編庫。全局匯編庫是供多個應(yīng)用程序使用的機(jī)器范圍的匯編庫。使用該庫不是必要條件,但這樣做有很多好處。例如,自動提供多個版本的匯編并行存儲。而且,管理員能使用該庫部署他們需要的每個機(jī)器上的應(yīng)用程序要使用的缺陷修復(fù)或安全補(bǔ)丁。在該方案中,配置匯編到全局匯編存儲能影響機(jī)器上的多個應(yīng)用程序。.NET 框架利用版本政策(稍候描述)的概念解決了現(xiàn)在出現(xiàn)在系統(tǒng)中共享區(qū)域的問題,例如 %windir%\system32。
在庫中添加匯編需要明確的管理員操作—實(shí)際上,安裝過程必須有“管理員權(quán)限”。匯編從不在存儲結(jié)束作為運(yùn)行一個應(yīng)用程序的側(cè)面影響,也不是當(dāng)前工作的共享匯編的任何存儲。在 Visual Studo .NET 時間框架中,Windows 安裝程序?qū)⒏聻槔斫鈪R編和匯編庫。這意味著可以使用 Windows 安裝程序的所有功能,例如使用 .NET 應(yīng)用程序選擇安裝和應(yīng)用程序恢復(fù)。
.NET SDK 包括兩個用于匯編庫的工具。第一個是稱為 AL 的工具,它允許在庫中添加匯編。AL 使開發(fā)和測試方案變得方便,它不需要創(chuàng)建整個 Windows 安裝程序包在庫中添加一個匯編。使用 /install 開關(guān)在庫中添加匯編:
Al /install:myassembly.dll
第二個工具是 Windows Shell Extension,它允許您使用 Windows Explorer 操作庫。圖 4 表示全局匯編庫的視圖。
<img src=http://www.microsoft.com/china/msdn/images/dplywithnet04.gif>
圖 4. 全局匯編庫
共享名
共享名用于使嚴(yán)格的命名需求與共享匯編結(jié)合起來。共享名有三個目標(biāo):
名稱唯一:共享匯編必須具有全局唯一的名稱。
防止名稱冒充:作為開發(fā)人員,不希望有人發(fā)布您的匯編的后繼版本并假稱它是您發(fā)布的,無論是意外還是故意的。
提供引用標(biāo)識:當(dāng)涉及引用一個匯編時,共享名用于保證載入的匯編來自所期望的發(fā)行者。
共享名使用公共密鑰加密實(shí)現(xiàn)。通常,過程如下所示:匯編的編寫者產(chǎn)生一對密鑰(或使用已有的),標(biāo)記包含專有密鑰清單的文件,并給調(diào)用者提供公共密鑰。當(dāng)引用匯編時,調(diào)用者記錄生成強(qiáng)名稱專有密鑰相應(yīng)的公共密鑰。圖 5 略述了該過程在開發(fā)期間如何工作,包括密鑰如何存儲在元數(shù)據(jù)中及如何生成簽名。
該方案是稱為“Main”的匯編,它引用一個稱為“MyLib”的匯編。MyLib 具有共享名。重要的步驟如下所述。
<img src=http://www.microsoft.com/china/msdn/images/dplywithnet05.gif>
圖 5. 實(shí)現(xiàn)共享名的過程
開發(fā)者調(diào)用在密鑰對中傳遞的編譯器和一組匯編的源文件。密鑰對通常由稱為 SN 的 SDK 工具生成的。例如,下面的命令生成一個新的密鑰對并保存的文件中:
Sn 杒 MyKey.snk
多數(shù)的編譯器將匯編作為編輯步驟的一部分。下面是一個 C# 命令的例子,它接受密鑰對并給匯編簽名:
Csc /t:library math.cs /a.keyfile:MyKey.snk /a.version:1.0.0.0
當(dāng)編譯器生成匯編時,公共密鑰作為匯編標(biāo)識的一部分保存在清單中。包括公共密鑰作為標(biāo)識的一部分給匯編提供了全局唯一的名稱。
匯編生成后,包含清單的文件由專有密鑰標(biāo)記。結(jié)果簽名保存在文件中。
當(dāng)編譯器生成 Main 匯編時,MyLib 匯編的公共密鑰就作為引用 MyLib 的一部分保存在 Main 的清單中。
在運(yùn)行時,.NET 框架中有兩個步驟保證共享名給予開發(fā)人員所需的利益。首先,在匯編安裝到全局匯編庫時,驗(yàn)證 MyLib 的共享名簽名。(沒有配置到庫中的驗(yàn)證簽名的選項(xiàng)也是可用的。)驗(yàn)證簽名保證 MyLib 的內(nèi)容從匯編建立以來沒有改變。第二步是驗(yàn)證作為 Main 引用 MyLib 的一部分保存的公共密鑰與 MyLib 身份的一部分的公共密鑰相匹配。如果這些密鑰相同,Main 的作者就能保證載入的 MyLib 版本來自同一個的發(fā)布者,該發(fā)布者編寫了建立 Main 的 MyLib 版本。當(dāng)涉及 Main 引用 MyLib 時,該密鑰等效檢查在運(yùn)行時完成。
術(shù)語“簽名”常常聯(lián)想到 Microsoft Authenticode。理解共享名和 Authenticode 沒有任何關(guān)系是非常重要的。這兩個技術(shù)有不同的目標(biāo)。實(shí)際上,Authenticode 意味著對發(fā)行者信任的水平,而共享名不是。沒有與強(qiáng)名稱相關(guān)聯(lián)的授權(quán)許可或第三方簽名授權(quán)。另外,共享名簽名通常由編譯器本身作為編譯過程的一部分進(jìn)行。但是,也有實(shí)用程序用于在 SDK 中簽名。
另一個值得考慮的是“測試簽名”工程。匯編的編寫者經(jīng)常不能訪問需要完全簽名的專有密鑰。大多數(shù)公司很好的保護(hù)這些密鑰的存儲它只能被少數(shù)人訪問。結(jié)果,.NET 框架提供少量在開發(fā)中的“測試簽名”技術(shù),然后再“真實(shí)簽名”。
版本策略
如剛剛所描述的,每個匯編清單記錄它創(chuàng)建所依賴的每個關(guān)系的版本信息。但是,有一些方案應(yīng)用程序的編寫者或管理員需要在運(yùn)行時以關(guān)系的不同版本運(yùn)行。例如,管理員應(yīng)該能發(fā)布故障排除版本而不需要重新編譯每個應(yīng)用程序以便得到該修改。而且,管理員必須能列出因發(fā)現(xiàn)安全漏洞或服務(wù)故障從沒有使用的匯編的詳細(xì)版本。.NET 框架通過版本策略在版本綁定中啟用了該靈活性。
匯編版本號
每個匯編都有四個部分組成的版本號作為它標(biāo)識的一部分(就是說,一些匯編的版本 1.0.0.0 和 版本 2.1.0.2 是完全不同的與類裝載器有關(guān)的標(biāo)識)。包括作為標(biāo)識的一部分的版本主要用來區(qū)分用于并行目的的匯編的版本。
開發(fā)人員和管理員必須理解版本號的結(jié)構(gòu),因?yàn)樗峭ㄓ谜Z言運(yùn)行時間如何加強(qiáng)匯編之間的版本關(guān)系的關(guān)鍵。
<img src=http://www.microsoft.com/china/msdn/images/dplywithnet06.gif>
圖 6. 匯編版本號的四個部分
版本號的幾個部分是主要版本、次要版本、內(nèi)部版本和修訂版本。主要版本或次要版本的改變可認(rèn)為是不可兼容的改變。例如,開發(fā)人員改變了一些方法參數(shù)的類型或徹底刪除了一些類型。類裝載器使用該信息使所依賴匯編的不兼容版本不默認(rèn)載入。
另一方面,僅改變版本號中的建立和版本部分可認(rèn)為是兼容的。這些改變一般是故障排除或安全補(bǔ)丁,在這種情況下類型定義沒有在某種程度上中斷調(diào)用者的改變,這是兼容的。這些兼容的變化經(jīng)常稱為 Quick Fix Engineering (QFE) 修復(fù)或動態(tài)修復(fù)。
默認(rèn)的版本策略
當(dāng)通用語言運(yùn)行時間遇到在代碼中引用匯編時,它決定載入所依賴匯編的版本。默認(rèn)情況下,載入解決引用的匯編必須有與引用中的記錄相同的主版本號和次版本號。如果這些號碼不同,該匯編認(rèn)為是不兼容的,并且不會默認(rèn)載入。相反,通用語言運(yùn)行時間會用最高的號碼得到 QFE (或動態(tài)修復(fù))。
例如,如果 Main 以 MyLib 的 1.0.0.0 版本編譯,當(dāng)運(yùn)行發(fā)現(xiàn) MyLib 的 1.0.1.1 版本時,將載入 1.0.1.1。
總是取最新的編譯和版本的策略稱為“自動 QFE 策略”。該原則的主要目的是允許管理員在不重新創(chuàng)建所有應(yīng)用程序的情況下發(fā)布故障排除版本。
定制版本策略
有些時候前面描述的默認(rèn)策略并不是您所要的。例如,也許應(yīng)用程序被所安裝的 QFE 不經(jīng)意地中斷了。
默認(rèn)的版本策略能通過使用 XML 配置文件修改。關(guān)于版本,有兩個文件:一個應(yīng)用程序說明文件和一個機(jī)器范圍或管理員文件。應(yīng)用程序說明文件保存在與應(yīng)用程序相同的目錄中。該文件中的策略描述只影響該應(yīng)用程序。機(jī)器范圍策略文件當(dāng)前保存在 Windows 目錄。該文件被管理員用來描述影響本機(jī)器上所有應(yīng)用程序的策略。例如,也許某個管理員確定了某個匯編的詳細(xì)的版本具有一些安全漏洞,并且他確保該匯編將不再被使用。
自定義策略的示例包括:
綁定指定的版本
有時需要綁定一個與清單記錄的版本完全不同的匯編。通過在配置文件中的<BindingPolicy>標(biāo)簽提供對該方案的支持。該策略用于映射某個關(guān)系指定版本的引用或某個關(guān)系所有版本的引用。
下面例子映射某個稱為 Calcr 的匯編 6.0.0.0 版本的引用:
<BindingPolicy>
<BindingRedir Name="Calcr"
Originator="32ab4ba45e0a69a1"
Version="*" VersionNew="6.0.0.0"
UseLatestBuildRevision="yes"/>
</BindingPolicy>
關(guān)閉自動 QFE 策略
該策略允許停止“自動 QFE 策略”引用已提供的匯編。 <BindingPolicy> 標(biāo)簽也用在這里,但 UseLatestBuildRevision 屬性設(shè)置為 No,如下面例子所示:
<BindingPolicy>
<BindingRedir Name="Calcr"
Originator="32ab4ba45e0a69a1"
Version="*" VersionNew="6.1.1212.14"
UseLatestBuildRevision="no"/>
</BindingPolicy>
安全模式
安全模式(或編譯運(yùn)行)策略用于恢復(fù)編譯配置。啟用該策略將使通用語言運(yùn)行時間載入記錄在清單中的關(guān)系的一個精確版本。大概應(yīng)用程序在它創(chuàng)建、測試并第一次發(fā)布時工作。安全模式是用于恢復(fù)到該狀態(tài)安全的網(wǎng)絡(luò)。下面的 XML 代碼為具體的應(yīng)用程序打開安全模式:
<BindingMode>
<AppBindingMode Mode="safe"/>
</BindingMode>
如果某個具體的關(guān)系不符合版本規(guī)則或不經(jīng)意引入一個錯誤,“安全模式”和“關(guān)閉自動 QFE 原則”可用于恢復(fù)應(yīng)用程序到某個工作的狀態(tài)。
策略解決方案中的階段
本文已經(jīng)介紹了幾個版本和發(fā)布的概念,包括應(yīng)用程序?qū)S脜R編、共享的匯編、全局匯編存儲和用于指定版本策略的 XML 文件。本節(jié)通過描述通用語言運(yùn)行時間查找匯編和應(yīng)用版本策略經(jīng)歷的階段將這些概念連接起來。
當(dāng)通用語言運(yùn)行時間遇到引用存儲在元數(shù)據(jù)中的另一個匯編時,開始載入?yún)R編的過程。根據(jù)引用,下面的步驟決定載入那個匯編的哪個版本:
參考應(yīng)用程序說明文件查看是否指定了策略。如果是,以策略的信息修改原始文件。例如,如果引用指定版本 1.0.0.0 而應(yīng)用程序說明策略文件指定版本 2.0.0.0,通用語言運(yùn)行時間將按照如同初始指定版本 2.0.0.0 一樣處理。
在應(yīng)用程序目錄(和子目錄)中查找匹配的匯編?!捌ヅ洹倍x為精確的主版本號和次版本號(除非 QFE 策略被禁止)。
不管通過查找是否發(fā)現(xiàn)一個匹配項(xiàng),全局匯編庫都將由 QFE 引用。這使管理員發(fā)布每個人都應(yīng)該得到的故障排除。
最后,參考管理員策略文件。最后參考該文件是因?yàn)楣芾韱T最后決定載入哪個版本。
--------------------------------------------------------------------------------
發(fā)布
發(fā)布包含至少兩個不同的方面:包裝代碼和將這些包裝分布給運(yùn)行該應(yīng)用程序的客戶端和服務(wù)器。.NET 框架主要的目標(biāo)是通過毫無影響的安裝簡化發(fā)布和復(fù)制發(fā)布的可行性。匯編自描述的天性使我們擺脫對注冊表的依賴,因此使安裝、卸載和復(fù)制變得相當(dāng)簡單。但是,有幾種場合下復(fù)制作為發(fā)布機(jī)制并不充分。在這些情況下,.NET 框架提供擴(kuò)展代碼下載服務(wù)并集成在 Windows 安裝程序中。
包裝
在 .NET 框架的第一個版本中有三個包裝選項(xiàng)可用:
As-built(DLL 和 EXE)。在許多場合,不需要特別的包裝。應(yīng)用程序以發(fā)布工具制造的格式發(fā)布,即 DLL 和 EXE 的集合。
Cab 文件。為了更有效地下載,Cab 文件可用于壓縮應(yīng)用程序。
Windows 安裝程序包。 Microsoft Visual Studio.NET 和其他安裝工具允許建立 Windows 安裝程序包(.msi 文件)。Windows 安裝程序允許利用應(yīng)用程序修復(fù)、選擇安裝以及其他 Microsoft Windows 2000 應(yīng)用程序管理功能。
分布方案
.NET 應(yīng)用程序能以多種方式發(fā)布,包括復(fù)制、代碼下載以及通過 Windows 安裝程序。
對于許多應(yīng)用程序,包括 Web 應(yīng)用程序和 Web 服務(wù),發(fā)布和復(fù)制一組文件到磁盤并運(yùn)行一樣簡單。卸載和復(fù)制就像刪除這些文件或復(fù)制它們一樣容易。
.NET 框架提供使用 Web 瀏覽器下載代碼的支持。該部分有幾個重要問題,包括:
零影響:沒有注冊表項(xiàng)添加到機(jī)器上。
增量下載:匯編的許多分塊只有在引用時才下載。
下載與應(yīng)用程序隔離:代表某個應(yīng)用程序的下載代碼不影響機(jī)器上其它的應(yīng)用程序。支持代碼下載的主要目的是:防止用戶下載新版本的某個共享的組件影響瀏覽具體的 Web 站點(diǎn)和影響其他應(yīng)用程序。
No Authenticode 對話框:訪問安全系統(tǒng)的代碼用于允許可移動代碼以部分信任程度運(yùn)行。將不再出現(xiàn)對話框詢問用戶選擇是否信任該代碼。
最后,.NET 完全集成到 Windows 安裝程序和 Windows 2000 應(yīng)用程序管理功能中。