1. 程式人生 > >《.NET之美》之程式集

《.NET之美》之程式集

一、什麼是程式集(Assembly)?

經由編譯器編譯得到的,供CLR進一步編譯執行的那個中間產物,在WINDOWS系統中,它一般表現為·dll或者是·exe的格式,但是要注意,它們跟普通意義上的WIN32可執行程式是完全不同的東西,程式集必須依靠CLR才能順利執行。 ----百度百科之程式集

程式集可分為兩種型別:
(1)、可執行程式,字尾為.exe(GUI,圖形使用者介面;或CUI,命令列使用者介面)
(2)、類庫,字尾為.dll
其結構如下圖:

在其構成中,只有PE頭、CLR頭、清單是必須的。其他均為可選的。

二、程式集結構解析

1、清單(Manifest)

我們要如何去檢視一個程式集的清單呢?
此時我們就要藉助微軟自帶的強大的工具ILDASM,此程式如果你要裝Visual Studio就會自動幫你裝上去,路徑在:
C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\ildasm.exe.
這裡為了演示方便,我新建了一個程式集AssemblyLib作為演示。我們點選此工具的檔案按鈕,選擇我們那個程式集檔案,介面就如下圖所示:

我們點選檢視-元資訊-顯示! 滾動到Assembly就可看到AssemblyDef元資料表

裡面包含了程式集的名稱、公鑰、版本、程式集特性等資訊。我們接著往下翻,就可以看到FileDef表、ExportedTypeDef表、ManifestResourceDef表。FileDef表描述了構成程式集的模組資訊、ExportedTypeDef表描述了外部模組中存在的型別資訊、ManifestResourceDef表包含的是嵌入到程式集的資源資訊。
除了主要的這四張表以外,清單中還包含了AssemblyRef表,此表定義了該程式集所引用的其他程式集資訊。
從上面可以看出,清單描述了程式集的幾乎一切資訊,回答了這樣幾個問題:“程式集是什麼(名稱、版本、特性等)”“由什麼構成(模組、資源)”和“外部依賴是什麼(引用了其他哪些程式集)”。

2、元資料

所謂元資料就是描述資料的資料,這樣看來,清單也是屬於元資料的一種。
元資料與清單類似,也包含了幾張表:
ModuleDef表,包含了當前模組的名稱和字尾等資訊。
TypeDef表,包含了每個型別的資訊,這些資訊包括型別名稱、型別的基類、標記等資訊(public、private等)。
MethodDef表,包含了每個方法的資訊,這些資訊包括方法名稱、簽名、標記等資訊(public、static、virtual等)。
類似地,還有FieldDef、ParamDef、PropertyDef、EventDef幾張表。
型別元資料中除了包含模組中定義的型別以外,還包含外部型別的引用,這些資訊包含在另外一組表中:TypeRef、MemberRef。
我們只要知道,型別元資料,定義了程式集中所有型別的資訊。

3、程式集資源

程式集中還可以包含資源(Resource),資源可以是字串,也可以是任何格式的檔案,比如圖片、Excel文件等。
現在假設我們需要在專案中得到一張圖片的資源,我們通常有三種做法:
<1>、將圖片儲存在程式根目錄的資料夾下,然後通過路徑獲得。
第一種方法我們都很熟悉,這裡就不作介紹了。
<2>、將圖片作為資源嵌入程式集內。
第二種方法我們平常是比較少遇到的,我們看一下如何去做。
程式集的資源(Resource)是一段具有名稱的位元組陣列。可以將資源想象成一個Dictionay<string,byte[]>,即一個以string為鍵,以byte[]
為值的字典。因為位元組陣列是二進位制形式,所以資源可以是任何檔案。
將資源嵌入程式集內也有兩種方法:
①、將檔案直接嵌入程式集
這種方法只要將檔案新增到專案中,然後檢視檔案的"屬性",將"生成操作"的值設為嵌入的資源。這裡需要注意兩點,一是資源加到程式集以後,資源的名稱並不等於檔名,VS會自動在檔名前面加上程式集的預設名稱空間、檔案所在的資料夾名。二是
資源的名稱是大小寫敏感的。
那我們在程式中如何獲取資源呢?
可以在呼叫Assembly型別的例項方法GetManifestResourceNames()中獲得程式集的所有資源名稱,接下來呼叫GetManifestResourceStream()方法獲得資源的位元組流 。

Assembly asm = Assembly.GetExecutingAssembly(); // 獲得當前執行的程式集
string[] nameArray = asm.GetManifestResourceNames(); // 獲得資源名稱
foreach (string name in nameArray) {
Console.WriteLine(name);
using (Stream s = asm.GetManifestResourceStream(name)) {// 獲得位元組流
}

②、將.resources資原始檔嵌入程式集
在第一種方法,資源在程式集中是零散的,我們為了集中管理資源,可以使用.resx檔案將資源嵌入到程式集當中。.resx檔案是一個XML格式的文字檔案,記錄了程式集中包含的資源名稱和路徑,它是程式開發時的設計工具,通過視覺化的方式來對程式集中的資源進行分類和管理。注意.resx只是一個XML文字檔案,類似一個資源清單,本身並不是程式集資源。在生
成程式集時,.resx會被自動轉換為.resources檔案,.resources檔案包含了實際的資原始檔(例如圖片或者音訊),並嵌入到程式集當中,但習慣上仍將.resx稱作資原始檔。
<3>、將資源作為獨立程式集
在第二種方法中我們將資源直接嵌入到程式集中,會迅速增大程式集的體積,因此,我們可以將資源單獨放在一個單獨的程式集中,然後再由主程式集引用它。這樣做的好處就是如果主程式沒有
用到資源,那麼就不用去載入這個程式集。我們可以先引用資源程式集的.dll(假設為res.dll),然後用如下的程式碼去訪問資源程式集的資源:

Assembly asm = Assembly.Load("res");
ResourceManager r = new ResourceManager("Resource", asm);
...

在多語言的應用程式中,通常會將各個不同地區的語言文字作為資源,單獨放到各自獨立的程式集中,使得應用程式可以根據計算機的本地語言來顯示相應資源中的文字。
此時我們只要在專案中新建一個資原始檔,Resource.en.resx,在這個資原始檔中新增字串資源,名稱為"address",值為"China,GuangDong,Shenzhen"。
重新生成專案,在bin\debug資料夾下,會看多多了一個子資料夾en,其中包含了ConsoleApp.resources.dll檔案,該程式集包含了英文版本的資源。類似地,可以建立包含了德
語、日語等其他國家語言的程式集,這種程式集有一個形象的名字,叫做衛星程式集(Satellite Assembly)

Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en");
ResourceManager r = new ResourceManager("ConsoleApp.Resource",
Assembly.GetExecutingAssembly());
string address = r.GetString("address");
Console.WriteLine("address:" + address);

其中的"Thread.CurrentThread.CurrentUICulture=new System.Globalization.CultureInfo("en");"語句,將當前的UI區域性改為了en,即英語地區國家。之後,執行ConsoleApp.exe,會看到顯示的輸出為:

address:China, Guang Dong, Shenzhen

三、強名稱程式集

1、強名稱的定義

我們在新建程式集時,對程式集進行命名,那此時怎麼命名比較好呢?
比如在上面我們建的程式集Assemblylib,別人也可以建這個名稱的程式集,我們如何去比較好的劃分呢。首先,我們先對同一個程式集不同版本進行劃分,我們給程式集加上版本號以及區域性。 如下圖:

這樣之後,我們可以很好地分清自己的程式集之間不會發生衝突,但還是無法區分別人與自己的程式集。
為了解決這個問題,我們可以繼續加入公司名、公司的URL以及GUID,這樣,這個程式集的規則就有了唯一標識,這個時候就會衍生了另一個問題別人拿到你這個程式集後就可以看到你這個程式集的資訊,從而進行仿冒。出於這些考慮,微軟選擇了使用公鑰/私鑰非對稱加密(RSA)的方式,並結合使用了雜湊函式(SHA1)來保證:程式集的唯一性、防仿冒性、防篡改性。

2、為程式集賦予強名稱

接下來我們看下如何實現防偽冒性:
我們需要用到一個工具SN.exe ,這個工具在你裝VS的時候就會自動幫你裝好,這個檔案筆者的目錄為:C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools
我們可以使用vs自帶的開發人員命令提示符,此工具筆者在:C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Visual Studio 2017\Visual Studio Tools下 開啟命令提示符輸入sn.exe,我們可以看到如下圖的幫助頁面:

再輸入 sn -k D:\Assemblysnk.snk 回車後就會將祕鑰寫入D盤根目錄下

我們開啟D盤根目錄即可看到這個祕鑰檔案。

上面的檔案中包含公鑰及私鑰,接下來我們將公鑰提取出來並另存為另一個檔案。

從上面的結果可以看出,公鑰的位元組數很長,有128位元組,操作起來很不方便。因此,對公鑰進行了雜湊運算,獲得了一個它的8位元組的雜湊值,也就是公鑰標記。由於公鑰標記是公鑰的摘要,或者“指紋”,它們是對等的,此時我們只要關注公鑰標記即可。 我們再將公鑰加入程式集規則即可。 單純加公鑰別人可以仿冒,並無多大意義,公私鑰對就顯出了效果。
我們使用VS,將公私鑰對加入簽名中。 這樣就做到了程式集的防偽冒。(加密部分筆者這裡就不一一贅述,有興趣的請自行了解)。

總結

在這一部分的閱讀學習中,筆者先解釋了什麼是程式集,分析了程式集的結構,還介紹了在程式集中嵌入資源的幾種方法,接下來講了強名稱程式集如何一步步去做到唯一性與防偽。在我們日常的開發過程中,程式集是我們接觸最多的檔案,平時我們只懂得如何去生成與引用,並不知其具體的原理,這樣進一步的瞭解,對我們以後的開發有著奠定基石的作用!