為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之后,編譯時和部署時必須進行更多的工作。開發者應仔細考慮對于自己創建的程序集來說,創建一個強名稱是否劃算。