標籤:style blog http color 使用 strong
本文章我們將來重點介紹強命名程式集,強命名程式集的出現其實是為解決版本控制問題,比如說,在新版程式集發布後,我們希望在系統中對舊程式集的引用繼續保留,而有些地方又可以引用新的程式集,再比如說不同的公司提供了不同功能的程式集,這些類庫存放在一個公用目錄,有時候可能會出現名稱相同的情況。使用強命名程式集可以解決這些問題,一個強命名的程式集是靠公開金鑰標示、程式集版本號碼、地區屬性、程式集名稱這四個屬性來唯一標識的,這樣一來,新發布的庫檔案版本與前面發布的不同,不同的版本引用可以在中繼資料裡面標識,相互不會受到影響,而且在99.99%的情況下不同公司產生的程式集這四個屬性不會全都一樣,可以大大降低應用程式部署的風險。
一 強命名程式集的各個部分
版本號碼
[assembly: AssemblyVersion("1.0.0.2")]
眾所周知,程式集的版本是通過程式集層級的定製Attribute來標識的,一個版本號碼總是包含4個部分,分別是 Major Version(主要版本號),MinorVersion(次版本號碼),Build(構建),Revision(修訂),可以按照自己的版本號碼規劃自行指定。構建版本號碼或修訂版本號碼可以自動產生,此時這裡的AssemblyVersion屬性值可以是1.0.0.*或1.0.*,當使用這種方式時,自動產生的Build為從2000-01-01起的天數,Revision為從淩晨00:00:00 起的秒數/2。我們舉個簡單的例子。
using System;using System.Reflection;[assembly: AssemblyVersion("1.0.*")]public class a{ public static void Main() { DateTime dtBase=new DateTime(2000,1,1); DateTime dtWeeHours=new DateTime(DateTime.Now.Year,DateTime.Now.Month,DateTime.Now.Day,0,0,0); Console.WriteLine("Build版本號碼:" + DateTime.Now.Subtract(dtBase).Days); Console.WriteLine("Revision版本號碼:" + (int)DateTime.Now.Subtract(dtWeeHours).TotalSeconds / 2); }}
運行之後的結果如下
dll的版本號碼如下(修訂編號相隔1是因為編譯時間和已耗用時間不同,如果用版本號碼去反算產生時間,就不會有這個問題)
這樣一來就很明確了,Build版本號碼對應到產生的日期,Revision對應產生的時間,只要兩台機器的時間不出問題,自動產生的dll版本永不會重複。在實際項目中我們肯定是把版本資訊(上面那段屬性聲明)放到一個單獨的檔案中,vs建立項目時會預設產生一個AssemblyInfo.cs檔案,這個檔案裡面就包含這個屬性,當然還包含了很多其他的內容,像上面中的產品版本,內部名稱,檔案版本等等,大家可以去瞭解下。
地區性
[assembly: AssemblyCultureAttribute("en-Us")]
程式集的地區性是通過上面的定製Attribute來標識的,地區性可以用來標識程式集的語言,如果不指定,則認為是語言中性。為了讓應用程式支援多語言,按照微軟的官方提倡的做法,應該是把與語言文化有關的內容放到一個單獨的資源檔中,分不同的語言提供多個不同的dll,不同的語言之間用地區性來標識,在主程式調用時通過反射的方式來載入(注意不是直接引用,這樣一來可以更加方便的部署和發布)。正因為如此,如果某個程式集直接引用了一個地區性不為中性的dll,則會拋出一個警告“CS1607: 程式集產生 -- 引用的程式集“××××”是本地化附屬組件”。
地區性不為中性的程式集被稱作附屬組件,可執行檔不能為附屬組件,如果標識的地區性不為空白,則編譯時間會報錯“ error CS0647: 發出“System.Reflection.AssemblyCultureAttribute”屬性時出錯 -- “可執行檔不能是附屬組件,地區性應始終為空白””。 除此之外,在運行時載入附屬組件也會與語言中性程式集有所不同,這一點我們在後面應用程式配置中再做深入說明。
公開金鑰標記
<add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
以上內容是從Web.Config中截取到的,最後的那一部分,PublicKeyToken=B77A5C561934E089,等號後面的那部分就是公開金鑰標識,它其實是嵌入在程式集中公開金鑰經過SHA-1散列運算得到的密文的最後8個位元組。在繼續講公開金鑰標記前,我們先來瞭解一下程式集產生時使用公開金鑰/私密金鑰對進行簽名的一個過程。
1 產生一個公開金鑰私密金鑰對,這個公開金鑰的大小可以指定
sn -k d:\liuzh.snk
2 產生時使用keyfile開關指定密鑰檔案
csc /out:d:\apple.dll /t:library /keyfile:d:\liuzh.snk d:\apple.cs
在使用/keyfile:開關後,在產生時編譯器會做如下事情:
1 對程式集FileDef清單中的各個檔案進行hash運算(預設是SHA-1,使用AL.exe產生時可以用/algid來覆蓋),再使用私密金鑰(私密金鑰來自於liuzh.snk)對該散列值進行加密,得到密文。
2 將公開金鑰(公開金鑰也來自於liuzh.snk)和1中的密文嵌入到程式集中。
展示了這一過程
微軟保證了任何情況下sn產生的公開金鑰/私密金鑰的內容都不會相同,也就是說,嵌入到程式集中的公開金鑰都是唯一的,在引用該程式集時,假如在引用的程式集中記錄下被引用的程式集的完整的公開金鑰會使得檔案特別的大(比如說,任何一個 程式集都包會含了對mscorlib.dll的引用,因為任何類都繼承於Object)。展示了產生的程式集中的公開金鑰
由於引用的程式集只能記錄被引用程式集公開金鑰散列運算後的部分內容,這樣一來就存在一個問題,就是可能有兩個程式集最後的公開金鑰標識一致,但是機率會很低(小於1/264),加上程式集的唯一性還有其他內容來區分,所以這不會出現什麼問題。
順便提一下,進行過公開金鑰/私密金鑰簽名的程式集可以從一定程式上防止篡改,在運行時CLR會用公開金鑰將使用私密金鑰加密後的密文進行解密,將解密後的密文與程式集中各模組hash後的散列進行對比,如果不一致,則可以判定被篡改了。之所以說是一定程式上,是因為這種保護機制很脆弱,這點我們在後面進行說明。
程式集的名稱
程式集的名稱預設就是/out開關指定的檔案的名稱(不帶副檔名)。使用vs時,這個名稱可以在項目屬性中指定,正因為在產生時程式集名稱與檔案名稱保持一致,所以你在項目屬性中指定的是什麼名稱,最後產生的dll,exe就會使什麼名稱
2 全域組件快取
如果一個程式集需要被多個應用程式訪問(如微軟提供的類庫),那麼必須把它放到一個已知的目錄,而且CLR在檢測到對該程式集的一個引用時,需要知道自動檢查目錄。這個已知的目錄就叫做全域組件快取(Global Assembly Cache,GAC),放到GAC中的程式集稱為共用組件,因為它可以被其他應用程式共用,僅在程式運行目錄下的,我們稱之為私用組件。對於.net 3.5 以前版本,這個目錄位於C:\Windows\Assembly,對於.net4.0,它位於C:\Windows\Microsoft.NET\Assembly。
GAC的目錄是結構化的,其中包含了許多子目錄,並有一個演算法來產生這些子目錄,永遠不要手動將組件檔手動複製到GAC,相反,應該使用工具來完成,這個工具是GACUtil.exe,可以在命令列查看它的用法。它包含了比較常用的功能如安裝,卸載,刪除,重新安裝,篩選等。注意,只能將一個強命名的程式集安裝到GAC中,如果不是強命名(沒有進行過簽名),在安裝時就會提示“將程式集添加到緩衝失敗:試圖安裝沒有強式名稱的程式集”,在server2003下,這個檔案夾是這樣子的。
這個目錄裡面幾乎包含了應用程式在運行時的所需要的所有程式集,在安裝.net framework時,實際會安裝程式集檔案的兩個拷貝,一套安裝在編譯目錄,另一套安裝在GAC中。之所以要安裝兩套,是因為在編譯時間CSC.exe並不會去GAC中尋找你引用的dll,為什麼編譯器不去GAC中尋找呢,是因為在編譯時間/reference必須要知道具體的路徑,而GAC的目錄是不公開的,一個可能的替代方案是在編譯時間指定程式集的強式名稱,如System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089,但是這種方案很明顯都沒有直接部署兩套dll來得方便。
將程式集部署到GAC中是對程式集註冊的一種手段,雖然沒有記錄到註冊表中,但是很明顯破壞了我們的一個基本目標,簡單安裝,備份,還原,移動和卸載,所以如果我們不是提供類庫供他人使用的話,還是應該直接把dll放到程式運行目錄下
3 小結
進一步瞭解程式集相關資訊可以加強我們理解各種dll的引用方式及項目屬性中的配置原理,.net安裝目錄下各種dll及公用程式的功能,以在程式編譯或運行時,快速定位和解決問題。