為Microsoft .NET框架創(chuàng)建應(yīng)用程序時(shí),你獲得的最大的一個(gè)承諾就是能避免所謂的
DLL地獄。它是指當(dāng)一個(gè)組件更新后,可能會(huì)中斷依賴于它的其他應(yīng)用程序。然而,為了理解這個(gè)承諾,開發(fā)者需要熟悉“強(qiáng)名稱”(Strong Names)的概念與實(shí)現(xiàn)。本文將引導(dǎo)你理解強(qiáng)名稱在托管代碼中的應(yīng)用。
為什么要使用強(qiáng)名稱
在討論強(qiáng)名稱的好處之前,先來看看它的定義。強(qiáng)名稱由用于標(biāo)識(shí)一個(gè)程序集的信息構(gòu)成,其中包括程序集的文本名稱、分為4部分的版本號(hào)、區(qū)域性信息(如果有的話)、一個(gè)公鑰以及一個(gè)數(shù)字簽名。這些信息存儲(chǔ)在程序集的清單(manifest)中。清單包含了程序集的元數(shù)據(jù),并嵌入程序集的某個(gè)文件中。
注意
大多數(shù)程序集(比如使用Visual Studio .NET創(chuàng)建的那些)都是單文件程序集,也就是只有一個(gè).exe或者.dll文件。在這種情況下,清單直接嵌入單文件程序集中。但是,你可用“程序集生成工具”(Al.exe)來創(chuàng)建多文件程序集。
在程序集中包括一個(gè)強(qiáng)名稱后,公共語言運(yùn)行庫(CLR)可保證具有相同強(qiáng)名稱的兩個(gè)程序集在任何方面都是完全一致的。換言之,強(qiáng)名稱為CLR提供了一個(gè)程序集的惟一性標(biāo)識(shí)。除此之外,添加一個(gè)強(qiáng)名稱還可確保二進(jìn)制完整性,因?yàn)镃LR可在程序集加載時(shí)執(zhí)行驗(yàn)證,判斷它自從編譯以來是否被篡改過。
在兩種主要的情況下,開發(fā)者應(yīng)為一個(gè)程序集包括強(qiáng)名稱:
- 共享程序集。通過包括強(qiáng)名稱,程序集在安裝到“全局程序集緩存”(GAC)之后,就由同一臺(tái)機(jī)器上運(yùn)行的多個(gè)應(yīng)用程序共享。這種代碼共享模型與非托管世界中使用的模型剛好相反。在非托管世界中,COM組件一旦編譯并在系統(tǒng)注冊(cè)表中注冊(cè),就自動(dòng)共享。
- Serviced Component。一個(gè).NET類要想使用企業(yè)服務(wù)(COM+服務(wù)),比如分布式事務(wù)處理和對(duì)象池等等,那么包含類(稱為Serviced Component,因?yàn)樗鼜腅nterpriseServices.ServicedComponent類繼承)的程序集必須有一個(gè)強(qiáng)名稱。有了強(qiáng)名稱后,企業(yè)服務(wù)才能保證加載正確的程序集。企業(yè)服務(wù)(DLLHost.exe)容納的進(jìn)程中所運(yùn)行的Serviced Component應(yīng)放到GAC中;相反,作為庫應(yīng)用程序在調(diào)用者的進(jìn)程中運(yùn)行的那些ServicedComponent則不必這樣做。
在第一種情況下(共享程序集放到GAC中),主要的優(yōu)點(diǎn)包括:
單一部署
由于同一臺(tái)機(jī)器上的所有應(yīng)用程序都從GAC加載共享程序集,所以不需要為每個(gè)應(yīng)用程序都部署程序集。這有別于.NET框架默認(rèn)的“私有程序集”,它必須隨同每個(gè)應(yīng)用程序來部署。
繞過驗(yàn)證
如前所述,強(qiáng)名稱允許CLR的類加載器驗(yàn)證程序集自編譯后便沒有被篡改過。將程序集放到GAC中,只有在程序集首次放入GAC時(shí)才會(huì)執(zhí)行驗(yàn)證,而不是應(yīng)用程序每次加載它時(shí)都執(zhí)行驗(yàn)證,這有助于提升性能。
減少工作量
如果多個(gè)應(yīng)用程序引用同一個(gè)共享程序集,所有應(yīng)用程序都從相同的位置加載程序集。因此,操作系統(tǒng)在所有應(yīng)用程序中共享程序集的代碼頁,這減少了內(nèi)存占用。
集中更新
程序集部署到GAC后,就可集中部署修正內(nèi)容。雖然應(yīng)用程序默認(rèn)使用程序當(dāng)初編譯時(shí)的版本,而且GAC允許相同程序集的多個(gè)版本并存,但只需在machine.config文件中添加一個(gè)版本策略,即可強(qiáng)迫機(jī)器上的所有應(yīng)用程序使用新版本,而不是使用舊版本。除此之外,一旦程序集包含強(qiáng)名稱,其他代碼就可指定:只有來自具有特定強(qiáng)名稱的一個(gè)程序集中的代碼才能調(diào)用自己。例如,為一個(gè)類添加StrongNameIdentityPermissionAttribute后,就可確保只有具有指定強(qiáng)名稱的調(diào)用者才能創(chuàng)建這個(gè)類的實(shí)例,如清單A所示。這個(gè)例子指定了PublicKey屬性,所以凡是使用與這個(gè)公鑰配對(duì)的私鑰簽署的任何程序集,都允許實(shí)例化Products類。
要注意的問題
雖然強(qiáng)名稱提供了許多好處,包括允許代碼共享,并允許托管代碼使用企業(yè)服務(wù)等,但開發(fā)者需要注意以下問題:
調(diào)用私有程序集
假如一個(gè)共享程序集試圖從私有程序集加載一個(gè)類型,CLR會(huì)引發(fā)一個(gè)異常,因?yàn)楣蚕沓绦蚣荒芤闷渌蚕沓绦蚣?。正是因?yàn)榇嬖谶@個(gè)限制,所以避免了DLL沖突。
受信任的程序集
強(qiáng)名稱雖然引入了“身份”的概念,但沒有包括“信任”機(jī)制。例如,使用強(qiáng)名稱簽署的一個(gè)程序集雖然能保證版本兼容性,但不能保證要加載的程序集來自Quilogy。為了用Authenticode數(shù)字簽名來簽署程序集,開發(fā)者要使用.NET框架配套提供的命令行實(shí)用程序Signcode.exe。程序集使用Authenticode簽名進(jìn)行簽署之后,管理員就可創(chuàng)建相應(yīng)的策略,利用代碼訪問安全性(CAS)機(jī)制,允許它下載到用戶的機(jī)器上并進(jìn)行加載。簽名將成為CLR的類加載器所使用的身份憑證的一部分,用于判斷程序集是否應(yīng)該加載。
安裝問題
具有強(qiáng)名稱的程序集通常放在GAC中,假如應(yīng)用程序必須將程序集安裝到GAC中,安裝過程就必然會(huì)復(fù)雜一些。幸好,用Visual Studio .NET創(chuàng)建的Windows Installer項(xiàng)目可將程序集自動(dòng)安裝到GAC中。此外,開發(fā)者還可使用命令行實(shí)用程序Gacutil.exe。
部署問題
與安裝密切相關(guān)的就是部署問題。.NET框架應(yīng)用程序最大的一個(gè)好處在于,它們可采取一種XCOPY方式來部署。也就是說,應(yīng)用程序的目錄可直接移動(dòng)到另一臺(tái)機(jī)器,而不必注冊(cè)組件。然而,如果使用了共享程序集,這一過程就沒那么簡單了,因?yàn)楫?dāng)應(yīng)用程序部署到另一臺(tái)機(jī)器時(shí),共享程序集也必須安裝到GAC中。
強(qiáng)名稱的用法
要為程序集創(chuàng)建一個(gè)強(qiáng)名稱,開發(fā)者可使用程序集創(chuàng)建工具(Al.exe),或?qū)ystem.Reflection命名空間中的屬性包括到自己的代碼中。但是,首先必須準(zhǔn)備好一對(duì)公鑰和私鑰,以便用它們來簽署程序集。在準(zhǔn)備生成強(qiáng)名稱的那臺(tái)機(jī)器上,可用一個(gè)文件來包含這個(gè)密鑰對(duì),或者將其放到“加密服務(wù)提供程序”(CSP)內(nèi)的某個(gè)密鑰容器中(最終放到注冊(cè)表中)。
要想在文件中創(chuàng)建密鑰對(duì),可使用.NET框架提供的“強(qiáng)名稱”實(shí)用程序(Sn.exe)。例如,要?jiǎng)?chuàng)建名為keyfile.dat的一個(gè)文件,并在其中包括新的密鑰對(duì),可以像下面這樣運(yùn)行實(shí)用程序:
Sn.exe –k keyfile.dat
隨后,程序集生成工具可利用這個(gè)密鑰文件來生成強(qiáng)名稱:
Al.exe /out:AtomicData.dll /keyfile:keyfile.dat
利用程序集生成工具的開關(guān)選項(xiàng),你可指定使用一個(gè)特定的CSP及密鑰容器。更常見的情況是,開發(fā)者可從AssemblyInfo.vb(或.cs)文件中選用某個(gè)屬性,包括AssemblyKeyFileAttribute、AssemblyKeyNameAttribute或者AssemblyDelaySignAttribute:
<Assembly: AssemblyKeyFile("keyfile.dat")>
<Assembly: AssemblyVersion("1.0.0.*")>
在本例中,包含密鑰對(duì)的文件將在編譯時(shí)訪問,以創(chuàng)建強(qiáng)名稱并將其放到程序集清單中。另外,開發(fā)者可使用AssemblyKeyNameAttribute來指定用于存儲(chǔ)密鑰對(duì)的容器的名稱。
這里展示的技術(shù)適用于小公司或者個(gè)人開發(fā)。在大型企業(yè)中,用于簽署代碼的密鑰對(duì)往往會(huì)被嚴(yán)密看守。在這種情況下,開發(fā)者一般只能訪問公鑰,私鑰只掌握在少數(shù)幾個(gè)受信任的人的手中。但是,開發(fā)期間對(duì)公鑰的訪問仍是至關(guān)重要的,因?yàn)橐?#8220;強(qiáng)名稱程序集”的任何程序集都必須在自己的清單(manifest)里包含公鑰。為了確保開發(fā)過程的順利進(jìn)行,.NET框架也支持推遲簽署或部分簽署。
“部分簽署”是指在編譯時(shí),在程序集清單中為完整的強(qiáng)名稱簽名保留空間。簽名可在以后的某個(gè)時(shí)間添加。同時(shí),其他程序集仍可引用強(qiáng)名稱程序集。為了實(shí)現(xiàn)“推遲簽署”,AssemblyInfo文件可包括AssemblyDelaySign屬性,并向構(gòu)造函數(shù)傳遞True。這意味著AssemblyKeyFile中引用的密鑰文件只包含公鑰(可使用Sn.exe工具的一個(gè)開關(guān)來完成)。
如果程序集是部分簽署的,它的驗(yàn)證功能也必須關(guān)閉。這是由于部分構(gòu)造的強(qiáng)名稱是無效的,不能通過前面說過的二進(jìn)制完整性檢查。要繞過驗(yàn)證,請(qǐng)使用Sn.exe工具的–Vr開關(guān):
Sn.exe –Vr AtomicData.dll
在以后某個(gè)時(shí)候,可將程序集拿給掌握著私鑰的人,由其使用–R開關(guān)來簽署強(qiáng)名稱:
sn.exe –R AtomicData.dll keyfile.dat
小結(jié)
為程序集創(chuàng)建一個(gè)強(qiáng)名稱,并利用全局程序集緩存(GAC),開發(fā)者就可與機(jī)器上的其他應(yīng)用程序共享程序集。另外,還可訪問包括分布式事務(wù)處理和對(duì)象池在內(nèi)的“企業(yè)服務(wù)”。但是,創(chuàng)建強(qiáng)名稱和使用GAC之后,編譯時(shí)和部署時(shí)必須進(jìn)行更多的工作。開發(fā)者應(yīng)仔細(xì)考慮對(duì)于自己創(chuàng)建的程序集來說,創(chuàng)建一個(gè)強(qiáng)名稱是否劃算。