用強名稱程序集避免“DLL地獄”

爲Microsoft .NET框架創建應用程序時,你獲得的最大的一個承諾就是能避免所謂的DLL地獄。它是指當一個組件更新後,可能會中斷依賴於它的其他應用程序。然而,爲了理解這個承諾,開發者需要熟悉“強名稱”(Strong Names)的概念與實現。本文將引導你理解強名稱在託管代碼中的應用。
爲什麼要使用強名稱

在討論強名稱的好處之前,先來看看它的定義。強名稱由用於標識一個程序集的信息構成,其中包括程序集的文本名稱、分爲4部分的版本號、區域性信息(如果有的話)、一個公鑰以及一個數字簽名。這些信息存儲在程序集的清單(manifest)中。清單包含了程序集的元數據,並嵌入程序集的某個文件中。


注意
大多數程序集(比如使用Visual Studio .NET創建的那些)都是單文件程序集,也就是隻有一個.exe或者.dll文件。在這種情況下,清單直接嵌入單文件程序集中。但是,你可用“程序集生成工具”(Al.exe)來創建多文件程序集。



在程序集中包括一個強名稱後,公共語言運行庫(CLR)可保證具有相同強名稱的兩個程序集在任何方面都是完全一致的。換言之,強名稱爲CLR提供了一個程序集的惟一性標識。除此之外,添加一個強名稱還可確保二進制完整性,因爲CLR可在程序集加載時執行驗證,判斷它自從編譯以來是否被篡改過。

在兩種主要的情況下,開發者應爲一個程序集包括強名稱:

  • 共享程序集。通過包括強名稱,程序集在安裝到“全局程序集緩存”(GAC)之後,就由同一臺機器上運行的多個應用程序共享。這種代碼共享模型與非託管世界中使用的模型剛好相反。在非託管世界中,COM組件一旦編譯並在系統註冊表中註冊,就自動共享。
  • Serviced Component。一個.NET類要想使用企業服務(COM+服務),比如分佈式事務處理和對象池等等,那麼包含類(稱爲Serviced Component,因爲它從EnterpriseServices.ServicedComponent類繼承)的程序集必須有一個強名稱。有了強名稱後,企業服務才能保證加載正確的程序集。企業服務(DLLHost.exe)容納的進程中所運行的Serviced Component應放到GAC中;相反,作爲庫應用程序在調用者的進程中運行的那些ServicedComponent則不必這樣做。

在第一種情況下(共享程序集放到GAC中),主要的優點包括:

單一部署
由於同一臺機器上的所有應用程序都從GAC加載共享程序集,所以不需要爲每個應用程序都部署程序集。這有別於.NET框架默認的“私有程序集”,它必須隨同每個應用程序來部署。

繞過驗證
如前所述,強名稱允許CLR的類加載器驗證程序集自編譯後便沒有被篡改過。將程序集放到GAC中,只有在程序集首次放入GAC時纔會執行驗證,而不是應用程序每次加載它時都執行驗證,這有助於提升性能。

減少工作量
如果多個應用程序引用同一個共享程序集,所有應用程序都從相同的位置加載程序集。因此,操作系統在所有應用程序中共享程序集的代碼頁,這減少了內存佔用。

集中更新
程序集部署到GAC後,就可集中部署修正內容。雖然應用程序默認使用程序當初編譯時的版本,而且GAC允許相同程序集的多個版本並存,但只需在machine.config文件中添加一個版本策略,即可強迫機器上的所有應用程序使用新版本,而不是使用舊版本。除此之外,一旦程序集包含強名稱,其他代碼就可指定:只有來自具有特定強名稱的一個程序集中的代碼才能調用自己。例如,爲一個類添加StrongNameIdentityPermissionAttribute後,就可確保只有具有指定強名稱的調用者才能創建這個類的實例,如清單A所示。這個例子指定了PublicKey屬性,所以凡是使用與這個公鑰配對的私鑰簽署的任何程序集,都允許實例化Products類。

要注意的問題

雖然強名稱提供了許多好處,包括允許代碼共享,並允許託管代碼使用企業服務等,但開發者需要注意以下問題:

調用私有程序集
假如一個共享程序集試圖從私有程序集加載一個類型,CLR會引發一個異常,因爲共享程序集只能引用其他共享程序集。正是因爲存在這個限制,所以避免了DLL衝突。

受信任的程序集
強名稱雖然引入了“身份”的概念,但沒有包括“信任”機制。例如,使用強名稱簽署的一個程序集雖然能保證版本兼容性,但不能保證要加載的程序集來自Quilogy。爲了用Authenticode數字簽名來簽署程序集,開發者要使用.NET框架配套提供的命令行實用程序Signcode.exe。程序集使用Authenticode簽名進行簽署之後,管理員就可創建相應的策略,利用代碼訪問安全性(CAS)機制,允許它下載到用戶的機器上並進行加載。簽名將成爲CLR的類加載器所使用的身份憑證的一部分,用於判斷程序集是否應該加載。

安裝問題
具有強名稱的程序集通常放在GAC中,假如應用程序必須將程序集安裝到GAC中,安裝過程就必然會複雜一些。幸好,用Visual Studio .NET創建的Windows Installer項目可將程序集自動安裝到GAC中。此外,開發者還可使用命令行實用程序Gacutil.exe。

部署問題
與安裝密切相關的就是部署問題。.NET框架應用程序最大的一個好處在於,它們可採取一種XCOPY方式來部署。也就是說,應用程序的目錄可直接移動到另一臺機器,而不必註冊組件。然而,如果使用了共享程序集,這一過程就沒那麼簡單了,因爲當應用程序部署到另一臺機器時,共享程序集也必須安裝到GAC中。

強名稱的用法

要爲程序集創建一個強名稱,開發者可使用程序集創建工具(Al.exe),或將System.Reflection命名空間中的屬性包括到自己的代碼中。但是,首先必須準備好一對公鑰和私鑰,以便用它們來簽署程序集。在準備生成強名稱的那臺機器上,可用一個文件來包含這個密鑰對,或者將其放到“加密服務提供程序”(CSP)內的某個密鑰容器中(最終放到註冊表中)。

要想在文件中創建密鑰對,可使用.NET框架提供的“強名稱”實用程序(Sn.exe)。例如,要創建名爲keyfile.dat的一個文件,並在其中包括新的密鑰對,可以像下面這樣運行實用程序:

Sn.exe –k keyfile.dat

隨後,程序集生成工具可利用這個密鑰文件來生成強名稱:

Al.exe /out:AtomicData.dll /keyfile:keyfile.dat

利用程序集生成工具的開關選項,你可指定使用一個特定的CSP及密鑰容器。更常見的情況是,開發者可從AssemblyInfo.vb(或.cs)文件中選用某個屬性,包括AssemblyKeyFileAttribute、AssemblyKeyNameAttribute或者AssemblyDelaySignAttribute:
<Assembly: AssemblyKeyFile("keyfile.dat")>
<Assembly: AssemblyVersion("1.0.0.*")>


在本例中,包含密鑰對的文件將在編譯時訪問,以創建強名稱並將其放到程序集清單中。另外,開發者可使用AssemblyKeyNameAttribute來指定用於存儲密鑰對的容器的名稱。

這裏展示的技術適用於小公司或者個人開發。在大型企業中,用於簽署代碼的密鑰對往往會被嚴密看守。在這種情況下,開發者一般只能訪問公鑰,私鑰只掌握在少數幾個受信任的人的手中。但是,開發期間對公鑰的訪問仍是至關重要的,因爲引用“強名稱程序集”的任何程序集都必須在自己的清單(manifest)裏包含公鑰。爲了確保開發過程的順利進行,.NET框架也支持推遲簽署或部分簽署。

“部分簽署”是指在編譯時,在程序集清單中爲完整的強名稱簽名保留空間。簽名可在以後的某個時間添加。同時,其他程序集仍可引用強名稱程序集。爲了實現“推遲簽署”,AssemblyInfo文件可包括AssemblyDelaySign屬性,並向構造函數傳遞True。這意味着AssemblyKeyFile中引用的密鑰文件只包含公鑰(可使用Sn.exe工具的一個開關來完成)。

如果程序集是部分簽署的,它的驗證功能也必須關閉。這是由於部分構造的強名稱是無效的,不能通過前面說過的二進制完整性檢查。要繞過驗證,請使用Sn.exe工具的–Vr開關:

Sn.exe –Vr AtomicData.dll

在以後某個時候,可將程序集拿給掌握着私鑰的人,由其使用–R開關來簽署強名稱:

sn.exe –R AtomicData.dll keyfile.dat

小結

爲程序集創建一個強名稱,並利用全局程序集緩存(GAC),開發者就可與機器上的其他應用程序共享程序集。另外,還可訪問包括分佈式事務處理和對象池在內的“企業服務”。但是,創建強名稱和使用GAC之後,編譯時和部署時必須進行更多的工作。開發者應仔細考慮對於自己創建的程序集來說,創建一個強名稱是否划算。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章