Assembly, 這里把它翻譯為配件或程序集, 以示和組件(Component)加以區別。一個配件有時候是指一個EXE或者DLL文件, 實際上是一個應用程序(就是指帶有主程序入口點的模塊)或者一個庫文件。但是配件實際上可以是由一個或者多個文件組成(dlls, exes, html等等), 代表一組資源, 以及類型的定義和實現的集合.。一個配件也可以包含對其它配件的引用。 所有這些資源、類型和引用都在一個列表(manifest)中描述。manifest也是配件的一部分,所以配件是一個自我描述的,不需要其它附加的部件。
對其描述配件的另一個重要特性是,它是.Net環境下類型標識的一部分,也可以說是基本單位。因為,區分一個類型的標識就是包含這個類型的配件名字加上類型名本身。
舉個例子,配件A定義了類型T, 配件B也定義了同名類型T,但是.Net把這兩個類型認為是不同的類型。 注意,不要把配件(assembly)和命名空間(namespace)混淆起來。其實命名空間僅僅是用來把類型名用樹的形式組織起來的手段。對於運行是環境來講,類型名就是類型名,和名字空間一點關系都沒有。 總之,記住配件名加上類型名唯一標識一個運行時類型。 另外,配件也是.Net框架用於安全策略的基本單位,許多安全策略都是基於配件的。
怎樣生成一個配件呢?
生成一個配件的最簡單辦法就是用.Net編譯器。例如:下面是一個C#程序ctest.cs
-
public class CTest
-
{
-
public CTest()
-
{
-
System.Console.WriteLine( "Hello from CTest" );
-
}
-
}
命令行這樣寫:
csc /t:library ctest.cs
然后,你可以用ILDM查看一下這個配件中究竟定義了什么。
產生配件的另外一種辦法是,把多個模塊(modules, 它也是由編譯器產生的,對於C#,就是用/target:module選項,用配件連接器(al.exe)裝配成一個配件。
私有配件和共享配件之間有什么區別?
私有配件通常只被一個應用程序使用,一般它被保存在應用程序目錄,或者其子目錄下面.。而共享配件通常保存在全局的配件catch緩沖區中, 它是一個由.Net運行時環境維護的配件倉庫。
共享配件通常是許多程序都要使用的代碼庫,比如.Net框架的類庫就是如此。
事實上, 我們應該如下區分三種配件:
* 私有(private):
只對一個應用程序可見; 這是缺省配置, 其它的應用程序不能對其引用,這個配件必須在應用程序目錄或者其子目錄下面有個拷貝.
* 公有(public):
對其它的應用程序可見, 不管它在什么目錄下面(可以是URL),其它的應用程序都可以對其直接引用.
* 公有共享(public shared):
共享的帶有版本控制的配件的當前實現, 應該使用這種類型. 這種類型特別適合於第三方控件.
Net環境怎樣查找配件?
當然是按照路徑查找, 規則如下:
* 在應用程序所在目錄及其子目錄下面私有配件
* 對於共享組件, 除了上面的規則, 再加上.Net提供的共享配件緩沖區路徑.
配件怎樣版本化?
我們已經知道所有的類型對象是使用全局的ID標識的, 那么配件是怎樣版本化呢?
配件通過版本號控制所謂的版本兼容性, 引用配件的時候,就需要給出配件名字和版本號.
版本號分為4個部分(舉例, 5.5.2.33). 分類如下:
不兼容: 前兩個不同
可能兼容: 前兩個相同, 第3個不同
兼容: 前三個相同, 第4個不同
注意: 版本控制只適用於共享配件!
介紹
在傳統的Windows應用程序開發中,動態連接庫(DLL)為軟件提供了一種重要的可重用機制。同樣組件對象模型(COM)也通過DLLs和EXEs的形式提供了組件重用機制。在.NET的世界里, 則由assembly(譯者注:可以翻譯為程序集,不過感覺不十分貼切,因此以下均保留了英文原文)提供了類似的可重用的代碼綁定機制。Assembly中包含了可以在CLR(Common Language Runtime)中執行的代碼。所有的.NET應用程序都是由一個或多個assembly組成的,不論你在創建一個Console, WinForms,WebForms應用程序或者一個類庫時,實際上你都是在創建assembly。甚至.NET本身也是通過assembly來實現其功能。
一個assembly可以由一個或者多個文件組成,簡單來說,你可以把assembly理解成一個邏輯上的DLL。每個assembly必須有一個單獨的執行入口如DllMain, WinMain, Main等。Assembly也有一套配置(Deploying)和版本控制(Versioning)的機制。和傳統的DLL等COM組件相比,.NET有着明顯的優點(我們將在后面看到),另外它還可以避免一些諸如DLL兼容性等問題的困擾(地獄般的困擾,譯者深有體會),並可以大大簡化配置上存在的問題。
靜態和動態的Assembly
通常我們可以用Visual Studio.NET或命令行編譯器(.NET SDK中帶的)來生成assembly。
如果你正確的編譯你的代碼,assembly就會以DLL或者EXE的形式保存在磁盤上。像這樣保存在物理磁盤上的assembly被稱為靜態assembly。
.NET也允許你通過Reflection APIs來動態生成assembly。(Reflection指獲得assembly信息以及assembly類型信息的功能,類型信息指assembly中的class, interface, member, method等內容。Reflection APIs在System.Reflection名稱空間內)。像這樣的駐留在內存里的assembly被稱作動態assembly。如果需要,動態assembly也可以保存在磁盤中。
系統需求
下面我們將主要討論靜態assembly,本文所帶例程需要運行在裝有.NET SDK的機器上(Beta1或者Beta2)。你可以使用Visual Studio.NET來創建單文件的assembly。
私有的和共享的Assembly
當啟動一個.NET應用程序的時候,程序首先要檢查自己的安裝目錄中是否有需要的assembly,如果幾個程序運行,那么每個都要在自己的安裝目錄中查找自己需要的assembly。也就是說每個程序都使用自己的assembly備份,這樣的assembly稱為私有assembly。它們只在應用程序的安裝目錄范圍內有效。
一些情況下,你可以發現多個應用程序需要使用共享的assembly而不只是使用他們自己的,對這種情況,你可以在全局assembly緩存(譯者:Global Assembly Cache,這個翻譯有點不倫不類,大家明白就好)中管理該assembly(后面會提到)。這樣的assembly在全局過程中有效,可以被機器內的所有程序共享,被稱為共享Assembly。如果應用程序不能在自己的安裝目錄中得到需要的assembly,它將在全局assembly緩存中查找。如果願意你可以考慮把你的assembly成為共享assembly。
Assembly的優點
在深入assembly細節之前,我們先來大概了解一下和傳統的COM組件相比,assembly有那些優點:
Assembly可以把你從DLL地獄中解救出來。
DLL地獄很折磨人,典型的COM組件應用通常是把一個單獨版本的組件放在指定的機器上,這樣帶來的問題就是開發人員在更新或者維護組件時常常會因為組件版本的向后兼容性的限制而碰釘子。而.NET中解決這個問題的方式很簡單:建一個私有的assembly好了,它將有能力管理同一組件的不同版本,assembly保留其不同版本的copy,如果不同的應用程序需要使用同一組件的不同版本,那么通過調用組件不同的copy就可以。這樣就可以避免組件兼容性常常出現的問題。.NET也允許我們跨機器來共享assembly,當然這種共享要受到嚴格的限制。
Assembly支持並行(side-by-side execution)執行
這么說有點不好理解,不過很簡單,也就是說同一assembly的不同版本可以在同一個機器上同時執行。不同的應用程序
可以同時使用同一assembly的不同版本。共享式assembly支持這種並行執行。
Assembly是自描述的
COM組件需要把一些細節信息保存在系統注冊表或類型庫里。當使用COM組件的程序運行時,它首先要去注冊表里收集組件的細節信息然后才能調用。不象COM組件,.NET Assembly是自描述的,它們不需要把任何信息保存在注冊表里,所有的信息都存在於assembly自己的元數據(Metadata)里了(后面會講到Metadata)。
配置簡化
assembly是自描述的,它不依賴於注冊表保存信息,因此完全可以使用XCOPY之類的方法來配置它。
卸載容易
不需要注冊表,當然簡單的刪掉就算是卸載了。
Assembly的結構
載創建一個assembly之前,我們先來了解一下assembly的組成結構。Assembly由以下幾部分組成:
Assembly Manifest(譯者:Assembly清單?不貼切,其實類似於一個目錄或者入口)
包含assembly的數據結構的細節。
類型元數據(Type Metadata)
包含assembly中允許的類型數據。(前面提到過,class, interface,member, property等)
Microsoft Intermediate Language code (MSIL)
單文件和多文件Assembly
上面提到的assembly結構中包含的東西可以被綁定到一個單獨的文件里。這樣的assembly叫單文件assembly。另外,所有的MSIL代碼和相關的元數據也可以被分到多個文件中,這些文件中每一個單獨的文件稱為一個.NET Module(模塊),.NET module中也可以包括其他一些文件如圖像文件或資源文件。
下面我們了解一下assembly manifest的更詳細的信息。Assembly manifest保存了assembly細節的數據結構。對多文件assembly來說,assembly manifest好像一個“綁定器”把多個文件綁定到一個assembly中。請注意Manifest和Metadata並不相同,Metadata保存的是在assembly和module里用到的數據類型(如class, interface, method等)的相應信息,而Manifest是用來描述assembly本身結構的細節信息的。
對單文件Assembly來說,Manifest嵌在DLL或EXE文件內,對多文件assembly, Manifest可以內嵌在每個文件中也可以存在於一個委托(constituent)文件里。后面將會有詳細說明。
下面列出了Manifest中的主要信息:
- *Assembly名字
- 版本號
- Assembly運行的機器的操作系統和處理器
- Assembly中包含的文件列表
- 所有assembly依賴的信息
- Strong Name信息
Metadata
Metadata數據是對assembly中數據的定義。每個EXE或DLL包含自己的詳細的類型信息,這種數據叫Metadata。主要包括以下信息:
- Assembly的名字和版本
- Assembly暴露出的類型信息
- 基本的類和接口信息細節
- 安全訪問細節
- 屬性細節(complier and custom)
Modules
前面提過Assembly可以有一個或多個Modules組成。Module可以看作是一系列可管理的功能模塊。它們轉化為MSIL,一旦代碼在runtime中運行,它們就可以被加入assembly。請注意module本身並不能執行,要利用它們首先要把它們加到assembly里。當然一個module可以被加到多個assembly中;配置一個assembly的同時也必須配置所用的modules。
創建單文件Assemblies
現在我們了解了.NET Assembly的一些基本知識,下面我們可以用C#創建一個簡單的assembly。你可以用VS.NET或者命令行編譯器,下面這個例子可以使用命令行編譯:
-
using System;
-
-
public class Employee
-
{
-
string m_name;
-
public string Name
-
{
-
get
-
{
-
return m_name;
-
}
-
set
-
{
-
m_name= value;
-
}
-
}
-
-
public int GetSalary()
-
{
-
//put your logic instead of hard coded value
-
return 10000;
-
}
-
}
上面的代碼說明創建了一個叫Employee的類,該類包含了一個的方法和一個屬性,你可以在文本編輯器中輸入以上代碼並保存為employee.cs。用下面的形式做命令行編譯:
csc /t:library employee.cs (csc是C Sharp Compiler)
執行過上面的命令,你將得到一個叫Employee.dll的文件,這就是一個單文件的assembly。
創建多文件的Assembly
這里我們將建立一個叫CompanyStaff的assembly,包括兩個類Clerk和Manager。下面我們看看創建多文件assembly的兩種辦法:
第一種方法是分別編譯Clerk和Manager兩個類到不同的modules中,然后把兩個modules加到CompanyStaff DLL中去得到最終的assembly。這時CompanyStaff DLL將管理assembly manifest。這種方法可以用正常的命令行編譯實現。(這里是CSC)
第二種方法是分別編譯Clerk和Manager兩個類到不同的modules中,然后生成一個單獨的包含有assembly manifest的文件,並用這個文件來表示最終的assembly。這種方法將使用一個叫做AL.EXE的工具來創建assembly。
使用命令行編譯器創建多文件assembly
我們將進行以下步驟:
- 創建一個叫Clerk的類到一個module中,包含一個方法叫GetClerkName,返回一個數組包含公司內職員的名字。
- 創建一個叫Manager的類到一個module中,包含一個方法叫GetManagerName,返回一個數組包含公司內經理的名字。
- 創建CompanyStaff類,包含一個叫做DisplayStaff的方法來實例化Clerk和Manager兩個類並把其中職員及經理的名字簡單的打印出來。把這個類編譯到一個assembly(DLL)中,這時也就將Clerk和Manager的module信息編譯到最終的DLL中去了。
- 創建一個客戶端程序來使用該assembly。
Step1: 創建Clerk Module
把下面的代碼輸入到Clerk.cs中
-
using System;
-
-
public class Clerk
-
{
-
public string[] GetClerkNames()
-
{
-
string[] names={"Clerk1","Clerk2","Clerk3"};
-
return names;
-
}
-
}
用命令行編譯這個類:
csc /t:module clerk.cs
這里/t:module開關告訴編譯器把代碼編譯成一個module。
需要說明的是,在beta1中編譯時,如果使用C# compiler,將得到擴展名為.dll的module,如果用VB.NET的complier,得到的擴展名為.MCM。而在beta2種得到的都是擴展名為.NETMODULE的module.
Step2: 輸入下面代碼到Manager.cs文件
-
using System;
-
-
public class Manager
-
{
-
public string[] GetManagerNames()
-
{
-
string[] names={"Manager1","Manager2","Manager3"};
-
return names;
-
}
-
}
用下面的命令行形式編譯:
csc /t:module manager.cs
Step3: 創建CompanyStaff assembly
在companystaff.cs文件中輸入以下代碼:
-
using System;
-
-
public class CompanyStaff
-
{
-
public void DisplayStaff()
-
{
-
Clerk c= new Clerk();
-
Manager m= new Manager();
-
string[] ClerkNames;
-
string[] ManagerNames;
-
ClerkNames=c.GetClerkNames();
-
ManagerNames=m.GetManagerNames();
-
Console.WriteLine( "Clerks :");
-
Console.WriteLine( "=======");
-
for(int i=0;i<ClerkNames.Length;i++)
-
{
-
Console.WriteLine(ClerkNames);
-
}
-
Console.WriteLine();
-
Console.WriteLine( "Managers");
-
Console.WriteLine( "=======");
-
for(int i=0;i<ManagerNames.Length;i++)
-
{
-
Console.WriteLine(ManagerNames);
-
}
-
}
-
}
用下面的命令行形式編譯:
csc /t:library /addmodule:clerk.dll /addmodule:manager.dll companystaff.cs
這里/addmodule開關用來把前面建好的兩個module加到CompanyStaff.dll中,也就是一個多文件assembly中。
Step4: 創建一個客戶程序來使用assembly
在SimpleClient.cs文件中輸入以下代碼。
-
using System;
-
-
public class SimpleClient
-
{
-
public static void Main()
-
{
-
CompanyStaff cs = new CompanyStaff();
-
cs.DisplayStaff();
-
Console.Write( "Press Enter To Exit...");
-
Console.ReadLine();
-
}
-
}
用下面的命令行形式編譯:
csc simpleclient.cs /r:companystaff.dll
這樣就准備好了,你可以運行simpleclient.exe,將會列出clerk和manager的名字。
用AL工具創建一個多文件assembly
現在我們可以使用AL來創建CompanyStaff assembly了。AL是一個用來整合一個或多個MSIL代碼文件或者資源文件並生成一個帶有管理manifest assembly的工具。和前面例子中的Step1與Step2一樣生成modules。因為我們要在一個獨立的文件中建立assembly manifest,所以我們不必再親自創建CompanyStaff.dll文件,我們要用AL來生成它。
輸入下面的命令行:
al clerk.dll manager.dll /out:CompanyStaffAL.dll /t:library
AL命令需要接受MSIL文件或資源,以空格分開,另外我們還要指定輸出文件名(這里是CompanyStaffAL.dll,是為了與前面已經生成的文件名區分開)
現在你的assembly准備好了,我們可以創建一個客戶程序來使用它。在前面的例子里,我們把DisplayStaff方法寫在了CompanyStaff類內,現在,我們可以通過AL得到CompanyStaff.dll,所以我們可以在客戶程序中寫一個同樣的代碼來實現同樣的功能了。
在SimplaClientAL.cs文件中輸入下面代碼:
-
using System;
-
-
public class SimpleClientAL
-
{
-
public void DisplayStaff()
-
{
-
Clerk c= new Clerk();
-
Manager m= new Manager();
-
string[] ClerkNames;
-
string[] ManagerNames;
-
ClerkNames=c.GetClerkNames();
-
ManagerNames=m.GetManagerNames();
-
Console.WriteLine( "Clerks :");
-
Console.WriteLine( "=======");
-
for(int i=0;i<ClerkNames.Length;i++)
-
{
-
Console.WriteLine(ClerkNames);
-
}
-
Console.WriteLine();
-
Console.WriteLine( "Managers :");
-
Console.WriteLine( "=======");
-
for(int i=0;i<ManagerNames.Length;i++)
-
{
-
Console.WriteLine(ManagerNames);
-
}
-
}
-
-
public static void Main()
-
{
-
SimpleClientAL cs = new SimpleClientAL();
-
cs.DisplayStaff();
-
Console.Write( "Press Enter To Exit...");
-
Console.ReadLine();
-
}
-
-
}
編譯上面的代碼並運行,你可以得到和前面的例子一樣的結果。
共享式assembly和全局assembly緩存
到目前為止,我們看到的都是私有式的assembly。當然在.NET應用中,我們多數都在單獨的使用一些私有式的assembly,然而有時候你可能會需要在很多個應用程序中共享一個單獨的assembly的備份。我們前面提到過,共享assembly需要把assembly放到全局assembly緩存中去(Global Assembly Cache)。全局assembly緩存是磁盤上一個特殊的目錄,一般它位於<driver>\WINNT\ASSEMBLY目錄下。注意當安裝過.NET后,這個目錄在explorer下顯示和其他目錄有點不同,如果想看一下它的實際內容,你可以用命令行的形式來查看。
注意:不能簡單的把你的assembly copy到這個目錄下。首先你需要給你的assembly一個strong name,然后可以用AL把這個assembly安裝到全局assembly緩存中去。
Strong Name
如果想把assembly設為共享,為了和其他共享的assembly區分開來,每一個assembly都需要一個唯一標志,這個標志指的就是Strong Name。 Strong Name是通過公鑰加密技術生成的。一個有私鑰的assembly可以生成和令一個帶有不同私鑰的assembly完全不同的strong name。.NET SDK使用一個叫SN.EXE(Shared Name)的工具來產生這樣的公鑰/私鑰對。
Versioning
向前面看到的那樣,多數時候,.NET assembly被用作私有模式。對這樣的assembly,因為它們都位於應用程序自己的目錄下,所以versioning看起來並不是十分重要。然而對共享式assembly來說,versioning是很重要的。共享式assembly可以以並行的形式使用(前面提到過並行使用的概念),因此完全有可能在同一台機器上存在同一個assembly的不同版本。當應用程序要使用一個assembly時候,它就應該提供最近的或以前的版本的信息。如果開發者需要使用不同版本,就需要在代碼中明確的設置版本號,其格式如下:
<major version>.<minor version>.<build number>.<revision>
Runtime將通過前兩個參數來決定當前使用的版本是否和以前的版本兼容(major version和minor version)。如果這兩個參數有變化,那么assembly將被認為是不兼容的,全局assembly緩存會為該assembly生成一個單獨的入口,如果在代碼中指定了版本號的話,major version就是可選的了。
下面顯示了.NET如何將同一個assembly(EmployeeShared)的不同版本視為不同的assembly的例子。
創建一個共享式的assembly
現在你應該已經知道什么是共享式assembly了,下面我們將創建一個叫EmployeeShared的assembly。創建一個共享式assembly包括以下幾個步驟:
- 創建assembly代碼並在代碼中指定其版本號。
- 用SN工具創建一個公鑰/私鑰對。
- 編譯assembly並用上一步中創建的公鑰/私鑰對簽名。
- 在全局assembly緩存中安裝該assembly。
Step1: 創建assembly代碼並在代碼中指定其版本號
在EmploeeShared.cs文件中輸入以下代碼:
-
using System;
-
-
using System.Runtime.CompilerServices;
-
-
[
-
public class EmployeeShared
-
{
-
string m_name;
-
public string Name
-
{
-
get
-
{
-
return m_name;
-
}
-
set
-
{
-
m_name= value;
-
}
-
}
-
-
public int GetSalary()
-
{
-
//put your logic instead of hard coded value
-
return 10000;
-
}
-
}
我們創建了這個類,包含一個屬性Name和一個方法GetSalary。注意AssemblyVersionAttribute的用法,它為該assembly設置了版本信息。
Step2: 用SN工具創建一個公鑰/私鑰對
為了給你的assembly賦一個Strong Name,你需要一個公鑰/私鑰對。可以使用.NET SDK提供的工具SN (Shared Name),輸入以下命令行:
Sn -k employshared.snk
該命令在指定文件里創建了一個鑰匙對,參數-k表示我們要把鑰匙對寫到輸出文件里。
擴展名.SNK只是個習慣,你可以讓它叫任何名字。
Step 3: 編譯assembly並用上一步中創建的公鑰/私鑰對簽名
現在我們可以用鑰匙對為我們的assembly簽名了,這可以通過在編譯時加上/a.keyfile開關來實現:
csc /t:library employeeshared.cs /a.keyfile:employeeshared.snk
注意如果你在使用VS.NET,你可以更簡單的在AssemblyInfo文件中指明key文件。如下所示:
[assembly:AssemblyKeyFile("employeeshared.snk")]
你也可以在這個文件里加上我們前面提過的般本號屬性而不用在源代碼里指定。
Step4: 在全局assembly緩存中安裝該assembly
我們的assembly已經用過私鑰簽名了,下面可以把它放在全局assembly緩存中了。像前面一樣使用AL,命令行如下:
al /I:employeeshared.dll
開關/I表示我們要將assembly安裝到全局assembly緩存中。
好了,現在assembly被安裝在全局assembly緩存並且可以使用了。想驗證一下的話到explorer中看一下Assembly目錄。
注意:在Beta2中,安裝assembly到全局assembly緩存中可以使用一個叫GACUTIL的工具。可以使用ILDASM.exe查看assembly信息。
有時候你可能需要分析assembly,尤其是別人開發的assembly。這種情況下你可以使用一個叫ILDASM (Intermediate Language Disassembler)的工具。這個工具就象我們從前用過的OLE View或者VB的object viewer一樣,你可以把你的assembly或者module導入這個工具來查看assembly各方面的特性,比如它包含的member, method和manifest。看起來就像下面這樣。
你可以通過雙擊樹結構中的節點得到更多的信息。
總結
assembly是.NET的組成模塊。.NET應用程序由一個或者多個assembly組成。一個.NET assembly由一個或多個文件組成並且在其自身的manifest中保存自己的注冊信息。通常一個assembly只為一個應用程序服務,這樣的assembly叫做私有式assembly。你也可以通過配置讓一個assembly的copy為多個應用程序服務。這樣的assembly叫做共享式assembly。共享式assembly在全局assembly緩存中被管理。共享式assembly必須有一個在整個機器范圍內唯一標識的strong name。
引用:https://blog.csdn.net/fuhanghang/article/details/84478964