C#與CLR學習筆記(2)—— 程序集的部署、查找與強名稱

本文是《CLR via C#》一書第2章和第3章的要點總結。

上篇內容:《CLR via C#》讀書筆記(1)—— 程序集的結構與CLR的啓動

目錄

程序集的生成與部署

程序集的生成

響應文件

語言文化

應用程序的管理配置與文件探測

程序集文件探測順序

強命名程序集

強名稱

公鑰與私鑰

創建強名稱程序集

簽名的過程

CLR對強命名程序集的檢查


程序集的生成與部署

程序集的生成

使用 csc.exe /out:outname  /t:target  /r:referenceFiles 命令可編譯一個源文件。其中/t指定生成的程序集類型,/r指定引用的其他文件。例如,若我們的代碼中用到了 System.Console 類型,由於它是微軟實現好的類型,因此需要指定 /r:MSCorLib.dll 引入該外部類型。然而實際中,MSCorLib.dll是個特殊的程序集,即使我們不指定 /r:MSCorLib.dll,它也會被編譯器自動引用,因爲它包含了所有核心類型例如 Sreing, Int32 等等。

響應文件

相應文件(.rsp)時包含了一組編譯器命令行開關的本文文件,執行 csc.exe 時,編譯器會自動打開相應文件,並讀取、執行其中的開關。安裝.net framework 時,會在 %SystemRoot%\Microsoft.NET\Framework(64)\vX.X.X 目錄中默認安裝全局 CSC.rsp 響應文件。此文件中引用的dll 不必在編譯時再指定 /r 開關引用。

語言文化

如果應用程序包含語言文化特有的資源,微軟建議專門創建一個特有資源文件,它不包含任何代碼,作爲主應用程序的一個附屬程序集(satellite assembly)。部署只含有資源數據的附屬程序集時,應該把它放到專門的子目錄中,子目錄名稱與語言文化的文本匹配,例如,如果應用程序的根目錄時 D:\MyApp,與中文對應的附屬程序集放大 D:\MyApp\zh-cn 子目錄中。在運行時,使用 System.Resources.ResourceManager 類訪問附屬程序集的資源。

應用程序的管理配置與文件探測

爲了實現對應用程序的管理控制,可在應用程序目錄中放入配置文件。CLR會解析文件內容來更改有一次文件的定位和加載策略。

配置文件包含XML代碼,它既能和應用程序關聯,也可以與機器關聯。

例如,如果如果根目錄下的program.exe要引用一個不在應用程序根目錄的程序集,CLR會拋出 System.IO.FileNotFound 異常,爲了解決此問題,可爲應用程序主程序集創建一個XML配置文件,且此文件必須與主程序集名稱相同,以.config爲擴展名,即program.exe.config。

例如,要想實現如下部署:

可在program.exe.config 中添加:

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas­microsoft­com:asm.v1">
      <probing privatePath="AuxFiles" />
    </assemblyBinding>
  </runtime>
</configuration>

CLR嘗試定位程序集文件時,總是先在應用程序根目錄查找,然後就會查找 AuxFiles 子目錄。可爲 probing 元素的 privatePath 特性指定多個以分號隔開的路徑,這裏的路徑都必須時相對根目錄的相對路徑。

對於一個可執行應用程序(.exe),配置文件必須在應用程序的根目錄,且必須採用exe文件全名作爲文件名,以.config爲擴展名。

對於 ASP.NET 應用程序,文件必須在Web 應用程序的虛擬根目錄中,且命名必須爲 Web.config。子目錄也可以有 Web.config,且會繼承父級配置項。例如,位於 http://Wintellect.com/Training 的Web 程序既會使用虛擬根目錄的 web.config 配置,也會使用 Training 目錄下的配置。

 在安裝.Net 時,會自動創建一個 Machine.config 全局配置文件,位於 %SystemRoot%\Microsoft.NET\Framework\version\CONFIG 路徑下。每個CLR版本都會對應一個 Machine.config 文件。一般情況下不要修改該配置文件,因爲會影響機器上其他應用程序。

程序集文件探測順序

CLR 通過元數據加載 IL 引用的類型或方法

如上圖所示,CLR 在運行時會通過元數據查找並定位 IL 所引用的類型或方法。如果引用的類型在另一個程序集上,那麼CLR會通過 AssemblyRef 查找程序集文件。由於 AssemblyRef 記錄表中只包含文件名、版本、文化、公鑰標記,不包含擴展名,因此 CLR會自動加上擴展名(.dll/.exe)查找程序集文件。

CLR定位程序集時會掃描幾個子目錄,順序如下:(其中,firstPrivatePath 和 secondPrivatePath 時通過上述配置文件指定的)

AppDir\AsmName.dll
AppDir\AsmName\AsmName.dll
AppDir\firstPrivatePath\AsmName.dll
AppDir\firstPrivatePath\AsmName\AsmName.dll
AppDir\secondPrivatePath\AsmName.dll
AppDir\secondPrivatePath\AsmName\AsmName.dll
...

因此,如果程序集位於其同名子目錄下,是不需要配置的,因爲CLR會默認掃描該子目錄。

如果上述任何子目錄都找不到目標程序集,CLR會從頭再來,用.exe 擴展名替換.dll ,再找不到就拋出 FileNotFoundException 異常。

附屬程序集遵循類似的規則,知識CLR會在與語言文化同名的子目錄下查找,例如:

C:\AppDir\en-­US\AsmName.dll
C:\AppDir\en­-US\AsmName\AsmName.dll
C:\AppDir\firstPrivatePath\en-­US\AsmName.dll
C:\AppDir\firstPrivatePath\en­-US\AsmName\AsmName.dll
C:\AppDir\secondPrivatePath\en-­US\AsmName.dll
C:\AppDir\secondPrivatePath\en-­US\AsmName\AsmName.dll

C:\AppDir\en­-US\AsmName.exe
C:\AppDir\en­-US\AsmName\AsmName.exe
C:\AppDir\firstPrivatePath\en-­US\AsmName.exe
C:\AppDir\firstPrivatePath\en-­US\AsmName\AsmName.exe
C:\AppDir\secondPrivatePath\en-­US\AsmName.exe
C:\AppDir\secondPrivatePath\en-­US\AsmName\AsmName.exe

C:\AppDir\en\AsmName.dll
C:\AppDir\en\AsmName\AsmName.dll
C:\AppDir\firstPrivatePath\en\AsmName.dll
C:\AppDir\firstPrivatePath\en\AsmName\AsmName.dll
C:\AppDir\secondPrivatePath\en\AsmName.dll
C:\AppDir\secondPrivatePath\en\AsmName\AsmName.dll

C:\AppDir\en\AsmName.exe
C:\AppDir\en\AsmName\AsmName.exe
C:\AppDir\firstPrivatePath\en\AsmName.exe
C:\AppDir\firstPrivatePath\en\AsmName\AsmName.exe
C:\AppDir\secondPrivatePath\en\AsmName.exe
C:\AppDir\secondPrivatePath\en\AsmName\AsmName.exe

 

強命名程序集

強名稱

私有部署的程序集(部署到應用程序根目錄或子目錄中的程序集)通常來說沒有太大問題,但是全局部署的程序集(共享程序集)必須放在公共的目錄下(例如System32目錄),這就會導致一系列問題,例如 DLL Hell 問題。

爲了避免 DLL Hell 問題,只根據文件名來區分程序集是不夠的,因此便誕生了強命名程序集(strongly named assembly)。強命名程序集與一般程序集結構完全相同,生成工具也相同。區別在於,強命名程序集使用了發佈者的 公鑰/私鑰對 進行了簽名。

弱命名的程序集只能以私有的方式部署,而共享程序集必須要有強名稱。

強名稱的四大特性:

  • 文件名(不包括擴展名)
  • 版本號
  • Culture
  • 公鑰標記(public key token),一般公鑰很大,爲了節約空間,經常使用公鑰哈希值的最後8個字節來代表公鑰,這個哈希值就是公鑰標記。

以下程序集標識字符串(即 程序集顯示名稱)標識了4個不同的程序及文件:

"MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
"MyTypes, Version=1.0.8123.0, Culture="en­US", PublicKeyToken=b77a5c561934e089"
"MyTypes, Version=2.0.1234.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
"MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

公鑰與私鑰

可利用VS自帶的SN.exe程序生成公鑰/私鑰對。例如:

SN -k MyCompany.snk

上述可以創建一個包含了公鑰和私鑰的snk文件。再使用

 sn -p MyKeys.snk MyPublicKey.publickey 

命令可將公鑰提取到 MyPublicKey.publickey 文件中,然後使用 

sn -tp MyPublicKey.publickey 

命令可查看公鑰以及公鑰標記,如下圖。其中給出的sha1算法是默認的生成公鑰標記的算法,也可在提取公鑰時手動指定其他哈希算法。

創建強名稱程序集

可通過編譯器創建一個強名稱程序集:

csc /keyfile:MyKeys.snk Program.cs

使用上述命令,編譯時,編譯器用私鑰對程序集進行簽名,並將公鑰簽入清單中。所以,只能對含有清單的程序集進行簽名

另外,通過VS也能創建強名稱程序集。選中項目-【屬性】-【簽名】- 勾選【爲程序集簽名】,在選擇密鑰文件下拉框中選擇密鑰文件,或者選擇【新建】來生成一個密鑰文件,就可以生成強名稱程序集。

簽名的過程

首先,程序集清單的 FileDef 表中包含了每個組成文件的名稱,每加入一個文件,都會對該文件內容進行一次哈希運算(默認使用SHA1),哈希值與文件名一同存放在 FileDef 表中。當包含清單的PE文件創建以後,PE文件的全部內容(除了 Authenticode signature,程序集強名稱數據,PE頭校驗和)做一次哈希運算,這個哈希值使用發佈者的私鑰進行一次簽名(加密),得到的RSA數字簽名被存放在PE文件中的一個保留區域中。然後,PE文件的CLR頭會被更新,來說明數字簽名在文件中嵌入的位置。

發佈者的公鑰也會被嵌入到PE文件的 AssemblyDef 清單元數據表。這樣,一個強命名程序集就完成了。

強名稱程序集的簽名過程

注意,爲了節約空間,在 AssemblyRef 元數據中存放引用的其他程序集的公鑰標記,而 AssemblyDef 中存放自己完整的公鑰,以防止文件被篡改。從程序集的 AssemblyDef 清單中抽取公鑰標記、程序集名稱、Culture、版本信息,就可以得到程序集標識字符串( 程序集顯示名稱)。

CLR對強命名程序集的檢查

在將一個強命名程序集安裝到GAC中時,系統會自動校驗該程序集,以防止不安全的程序集進入GAC中。系統對包含清單的那個主模塊文件內容進行哈希處理,與經過公鑰解密後的簽名進行比較,只有二者一致時,程序集才能被安裝到GAC中。

在運行時,若需要加載一個強命名的程序集,CLR首先從GAC中查找該程序集。若該程序集位於GAC中,且被加載到一個信任的AppDomain中,CLR將不會再檢查是否被篡改。若從非GAC目錄加載強命名程序集,CLR會對程序集進行檢查,這會犧牲一部分性能,來確保安全性。如果校驗未通過,會拋出 System.IO.FileLoadException 異常。

 

 

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