程序集與託管模塊

本文是爲了學習程序集而整理的網上資料,主要包括兩個部分,概念和使用,前部分講怎樣理解程序集,後部分講述怎樣使用的細節。

程序集與託管模塊的概念

"程序集與託管代碼塊"(摘自Himage的blog),希望大家看了此篇文章後對程序集的概念清楚一點
    如果你正在開發面向DotNet平臺的應用程序,那麼你肯定對“程序集”和“託管模塊”這兩個概念不陌生,這是DotNet帶來的術語。這兩個概念很容易混淆,有人認爲它們指的是同一樣事物,其實不然。這裏,我寫下自己的一些理解。
     
       爲了便於說明問題,我們先看看一個公司的某項目開發團隊,這個“團隊”由團隊“成員”組成(可能還有一些資源),而在這些團隊成員中,必然有一個成員是這個團隊的頭頭,爲了表示不是普通的成員,頭頭都有一個稱號,比如說“經理”或者“負責人”。經常的,說到某個團隊,我們只要知道它的頭頭就可以了。也就是說,頭頭代表了這個團隊(注意,這裏是"代表")。如果其他人有什麼事情要與這個團隊交流(比如團隊向外說明本團隊完成了什麼任務等),沒有必要將每個成員找來交流,我們只要找到頭頭就可以了。 因爲頭頭知道本團隊的一切,他掌握了關於團隊的所有信息:本團隊有哪些成員,而且有些什麼資源可以利用,與其他哪些團隊有交流等等。常見的團隊有多個成員,一個成員的團隊很少見(頭頭也是他),但爲了說明問題,我們假定一個成員的團隊和多個成員的團隊都存在。
 
    從上面的討論中我們可以這樣總結:團隊是個邏輯概念,並不是指某個人或資源,它是一個集合,而且這個集合不爲空,只有當擁有大於或者等於多個成員的時候它才稱之爲團隊。
 
       嘿,大家看出來了,我之所以要說項目開發團隊和成員,是因爲在DotNet中的程序集和託管模塊的概念與此類似。程序集其實並不是說某個文件,它是一個邏輯概念,就像一個團隊。當然,我們習慣說a.exe 或者b.dll是個程序集,其實這樣說多少會讓人混淆,請看下面的說明。而託管模塊就像是團隊中的團隊成員。哈,你肯定想到了,其中必然有一個託管模塊是整個“團隊”的“頭頭”,它負責管理所有的託管模塊,關於這個程序集的一些信息也保存在這個託管模塊中。我們把這樣的模塊叫做“主託管模塊”(我記得某本書上是這樣叫的)。爲了區分主託管模塊和普通託管模塊,怎麼辦呢?這個好說,人最擅長給自己掛頭銜了,借鑑一下。我們給普通託管模塊和主託管模塊不同的後綴名,普通託管模塊的後綴名是.netmodule,而主託管模塊的後綴名是.exe 或者.dll。和開發團隊中頭頭代表整個團隊一樣,主託管模塊代表着整個程序集。既然是主託管模塊,那麼它肯定與其他託管模塊不一樣。一般的託管模塊包含有IL代碼和元數據,而這個主託管模塊有沒有IL代碼和元數據並不重要,但它一定要有“清單”,也就是關於整個程序集的數據。
 
爲什麼主託管模塊會有兩種後綴名呢?這是因爲分工的原因,有的程序集是爲其他程序集提供便利的,它實現了一些數據類型(類庫)或者擁有某些資源――這就是後綴名爲.dll的程序集。這樣的程序集不喜歡拋頭露面,它們是“幕後程序集”。另一種程序集,後綴名爲.exe,用戶直接和它們打交道。有的功能它自己實現,但有些呢就交給幕後程序集去實現。爲了區別,我們就把這樣的程序集叫做“前臺程序集”吧。DotNet規範中沒有這兩個概念,這裏是爲了理解才造的詞。當然,讀者朋友都會有自己比較習慣的理解名字。一個可執行應用程序中,只有一個前臺程序集的概念,但是可以有零個或一個或多個幕後程序集。
 
我們又回到實際生活中的開發團隊概念上來。團隊的組成有這樣幾種情況:
a.一個成員,這樣的成員是比較厲害的那種,比如個人軟件開發者,自己對自己負責,自己是自己的頭頭;
b.多個成員,其中某個成員是頭頭;
程序集與此類似,所以就有了單模塊和多模塊程序集(有時也稱單文件程序集和多文件
程序集)。如果要實現的功能不是很煩雜,那麼就用單模塊程序集吧;但是,如果要實現的功能比較多,而且也好分開,那麼建議你用多模塊程序集吧,如果以後某個功能的實現方案改變的話,只要修改這個模塊就行了,這樣一來,極大的降低了開發的煩雜度。還有幾點要注意,有的託管模塊並不實現某種計算功能,它們僅僅是提供一些資源,比如說字符串;有時程序集除了包含託管模塊外,還包含一些另外的資源文件。上面說的好像不是很直觀,我們再結合下面的幾幅圖來理解。  
    我相信現在大家對程序集和託管模塊分別是什麼以及兩者間的關係有了較好的理解。但是如果有源代碼輔助一下那就更好了。是的。對程序員來說,源代碼比什麼都親切。好的,下面就舉兩個簡單的例子(用C#語言表述)。一個是單模塊程序集,一個是多模塊程序集。兩者都是前臺程序集(後綴名是exe)。我用Visual C# 2003 集成開發環境試了一下,竟然發現它不支持多程序集的開發(希望是我沒有找到)。沒關係,我們還有DotNet FrameWork SDK呢,不用怕。它自帶的C#編譯器csc.exe很好用。至於csc的用法我就不多說了。
例子1。單模塊程序集:
首先找個文本編輯器,將下面的代碼敲進去,然後將它保存起來,取名爲hello.cs。 例如我把它存爲e:"test"hello.cs。
/* 
 *hello.cs 在控制檯上顯示一行字符串 
 */ 
using System;
namespace nsApp
{
    public class CEnterPoint
    {
       static void Main()
       {
           Console.WriteLine("Hello, verybody!");
       }
    }
}
 然後,打開SDK命令行提示,定位在hello.cs所在的文件夾,比如我的是e:"test。在命令行提示中輸入命令 csc hello.cs,這個命令默認的是生成 .exe文件,也就是一個主託管模塊。 現在到hello.cs所在的文件夾中看看,你會發現一個新文件hello.exe。好,一個主託管模塊誕生了。既然有主託管模塊,那麼就標誌着一個程序集的誕生。再看看,沒有其他的託管模塊,那麼這個程序集就是一個單模塊程序集。
例子2。多模塊程序集:
假設我們要編一個程序來管理動物園。這裏爲了簡單,不考慮繼承等問題,每個動物用一個類來表示,並且用一個託管模塊來實現。將所有的動物都放在命名空間nsZoo中。現在動物園有兩種動物:Dog和Cat。看下面的代碼:
/* Dog.cs */
using System; 
 
namespace nsZoo
{ 
    public class Dog
    { 
       public void SayHello()
       {
           Console.WriteLine("I am a dog, wang wang wang");
       } 
    }
}
  
/* Cat.cs */
using System; 
 
namespace nsZoo
{ 
    public class Cat
    {    
       public void SayHello()
       {
           Console.WriteLine("I am a cat, miao miao miao");
       }
    }
}
  
/* Main.cs */ 
using System; 
using nsZoo; 
  
namespace nsApp 
{   
    class CEnterPoint
    {    
       static void Main(string[] args)
       {    
           Dog aDog = new Dog();
           Cat aCat = new Cat();
 
           aDog.SayHello();
           aCat.SayHello();
 
           Console.ReadLine();
       }
    } 
} 
 
將上面的三個文件保存好,比如放在文件夾e:"zoo 中。再一次打開SDK命令行提示,定位到e:"zoo  
輸入命令 csc /t:module Dog.cs 看看文件夾,你會發現一個新文件 Dog.netmodule
哈,這是一個託管模塊,普通的託管模塊。
接着輸入命令 csc /t:module Cat.cs 同樣得到一個普通託管模塊 Cat.netmodule 。好現在有了兩個普通託管模塊。
爲了實現一個程序集的夢想,必須還要有一個主託管模塊。好,接着輸入
csc /addmodule:Dog.netmodule;Cat.netmodule Main.cs  你會在文件夾中發現一個名爲Main.exe的文件,這就是你想要的主託管模塊。
 
好了。現在你有三個託管模塊,並且任命其中一個爲主託管模塊,這樣你就擁有了一個程序集了。這個程序集的組成是:Dog.netmodule, Cat.netmodule, Main.exe 。我們習慣說這個程序集是Main.exe 。但是一定要知道,Main.exe 其實是一個程序集的“頭頭”,由它代表着整個程序集。
程序集的使用
 任何.NET二進制代碼使用的.NET運行時是一個或一組程序集組成的。當你編譯一個應用程序時,實際上是在創建一個程序集。

        任何時候構建一個EXEDLL文件時必須使用/t:library編譯參數創建與該應用程序相對應的包含清單(manifest)的程序集,清單(manifest)記錄了.NET運行時程序集的相關信息。另外,使用/t:module編譯參數,創建一個以.netmodule爲擴展名的DLL 文件,這個DLL文件不包含清單。換句話說,雖然邏輯上模塊(module)依然是一個DLL文件,但是它不屬於程序集,當編譯應用程序或使用程序集生成工具(the Assembly Generation tool)時必須使用/addmodule開關將這個DLL文件加入到其他程序集中。

清單數據(Manifest Data

       程序集的清單有不同的方法存儲。若編譯一個獨立的應用程序或DLL文件,清單與程序或DLLPE結合在一起,這稱爲單一文件的程序集(single-file assembly)。多文件程序集(multifile assembly)是指清單以另外一個獨立實體的形式作爲程序集的一部分,或者作爲程序集中的一個模塊。

       同樣程序集的定義很大程度取決你如何使用它。從客戶的角度來看,程序集是模塊、導出類型、(必需、可選)資源的命名和版本化(versioned)的集合。從程序集創建者的角度來看,程序集是客戶能使用的一組相關聯的模塊、類型、資源、導出的方法的封裝包(mean of packaging)。也就是說,清單是程序集和用戶之間溝通的橋樑。下面列出程序集中清單包含的信息:
·          程序集名稱(Assembly name
·          版本信息(Versioning information
        版本信息是由四個不同的部分組成的版本號,分別是主版號、子版號、構建序號、修訂號。
·          (可選的)共享名和帶符號的程序集散列(AnOptionalshared name and signed assembly hash
        主要是關於程序集部署的相關信息。
·          文件(Files
         包含在程序集中的文件列表。
·          引用的程序集(Referenced assemblies
         直接引用的外部程序集列表。
·          類型(Types
        程序集內部的所有類型及模塊映像包含的類型的列表。(This is list of all types in the assembly with a mappig to containing the type.
·          安全(Security
        安全權限的列表,權限已明確地被程序集拋棄。(permissions This is a list of security permissions that are explicity refused by the assembly.
·          定製屬性(Custom attributes
·          產品信息        包含公司、商標、產品及版權信息。

程序集的優勢
·          程序集的封裝:將多個模塊封裝到一個物理文件,起到性能優化的作用。當你創建一個應用程序並使用多文件程序集時,.NET運行時只需加載相關的模塊。這種策略起到減少應用程序的工作集。
·          程序集的部署
             .NET框架中程序集是最小的部署單元。雖然它誘人的說,程序集是部署應用程序的方法,但是技術上並不是如此。許多關於程序集部署的正確的觀點是:在.NET中程序集應該看作一個窗體的類部署(class deployment)-就像Win32中的一個DLL文件。一個單獨的應用程序是由多個程序組成的。
             因爲程序集是自描述的(self-desciribing),所以部署程序集最簡單的方法就是直接把程序集複製到目的文件夾。當嘗試運行包含這個程序集的應用程序時,清單將向.NET運行時提供包含在這個程序集中的方法信息。另外,清單(manifest)也向應用程序提供該程序集所引用的外部程序集的相關信息。
             許多通過公共方法部署的程序集依然是私有的程序集,即程序集被複制到文件夾,但它們不是共享的。缺省情況下程序集是私有的,除非明確地指定程序集爲共享程序集(shared assembly)。

·          程序集的版本

             使用程序集另外一個主要優勢是程序集內置了版本號。程序集中止了DLL地獄。DLL地獄是指當一個應用程序重寫了一個被其他應用程序引用的DLL文件,而該應用程序引用的是低版本的同名DLL文件,這就造成了引用低版本DLL的應用程序運行出錯,這種情況。雖然Win32資源文件格式內置了版本資源類型,但是操作系統不會強制執行任何版本控制,以便依賴這個DLL文件的應用程序能正常運行。

             基於這個問題,清單不僅包含程序集本身的版本信息,也包含了該程序集所有被引用的程序集和這些程序集的版本信息。因爲有了這個體系結構,.NET運行時能確保版本規則被支持,當出現新版本的程序集時,版本不兼容的共享DLLs由操作系統自動安裝,使得應用程序可以繼續正常運行。 

用多個模塊創建程序集(Creating Assembies with Multiple Modules)

   模塊是基於DLL,但是它不是真正意義上的DLL,它必須被包含程序集中。示例:
// NetModuleTestServer.cs
// Build with the following command-line switches:
//     csc /t:library NetModuleTestServer.cs
public class NetModuleTestServer
{
    public static void Bar()
    {
        System.Console.WriteLine("NetModuleTestServer.Bar(NetModuleTestServer.netmodule)");
    }
}

// NetModuleTestClientApp.cs
// Build with the following command-line switches:
//
// csc addmodule:NetModuleServer.netmodule NetModuleTestClientApp.cs
using System;
using System.Diagnostics;
using System.Reflection;

class NetModuleTestClientApp
{
    public static void Main()
    {
        Assembly DLLAssembly = Assembly.GetAssembly(
            typeof(NetModuleTestServer));
        Console.WriteLine(""nNetModuleTestServer.dll Assembly " +
            "Information");
        Console.WriteLine(""t" + DLLAssembly);

        Process p = Process.GetCurrentProcess();
        string AssemblyName = p.ProcessName + ".exe";
        Assembly ThisAssembly = Assembly.LoadFrom(AssemblyName);
        Console.WriteLine("NetModuleTestClient.exe Assembly " +
            "Information");
        Console.WriteLine(""t" + ThisAssembly + ""n");

        Console.WriteLine("Calling NetModuleTestServer.Bar");
        NetModuleTestServer.Bar();
    }
}
 創建共享程序集

   若要創建一個在多個應用程序使用的程序集,並且版本信息相當重要,則必須共享程序集。若要共享程序集,必須給程序集指定強名稱。可以通過.NET SDK提供強名稱工具(the Strong Name tool)創建強名稱。使用強名稱有四個主要理由:

·          它是.NET生成唯一的全局名稱的機制

·          因爲強名稱工具生成的密鑰對包含了一個簽名,你可以判斷程序集創建之後是否被篡改。(Because the generated key pair includes a singature,you can tell whether an assembly has been tampered with after its original creation.)

·          強名稱能防止第三方在你構建的程序集的基礎上發表新的版本。這是因爲密鑰對包含簽名起了作用,因爲第三方不知道你的私鑰。

·          當.NET加載程序集時,運行時會自動驗證調用者是否合法。(When .NET load an assembly,the runtime can verify that the assembly came from the publisher that the caller is expecting.)

創建強名稱的步驟:

·          使用強名稱工具爲程序集創建一個強名稱。示例:

    sn -k InsideCSharp.key

·          在客戶端源代碼文件中添加屬性AssemblyKeyFile,把強名稱指定給程序集。

使用全局程序集緩存工作

   .NET每次加載程序集時都會創建一個代碼緩存區,通常成爲全局程序集緩存。使用全局程序集緩存有三個作用:

·          存儲從Internet或文件服務器下載的代碼。注:從一個特定的應用程序加載的代碼存儲在緩衝中的私有區域,以防止其他程序集訪問。

·          存儲被多個.NET應用程序共享的組件數據。利用全局緩存工具將程序集加載到緩存全局區域,使得該程序集可以被本地機器的所有應用程序訪問。

·          程序集的本地代碼是預運行時編譯的,同時存儲在全局程序集緩存區。(native code versions of assemblies that have been preJITted are stored in the cache.)

緩存視圖

1、在windows"assembly文件夾中可以查看程序集的相關信息,如版本號、語言(culture)、公鑰標記等等信息。

2、在命令行使用gacutil。其中-i參數:加載程序集;-u參數:卸載程序集(可以指定版本號)、-l參數:列出程序集的相關信息。
// DllTestServer.cs
// Build with the following command-line switches:
//     csc /t:library DllTestServer.cs
public class DllTestServer
{
    public static void Foo()
    {
        System.Console.WriteLine("DllTestServer.Foo " +
            "(DllTestServer.DLL)");
    }
}

// DllTestClient.cs
// Build with the following command-line switches:
//     csc DllTestClient.cs /r:DllTestServer.dll 
using System;
using System.Diagnostics;
using System.Reflection;

class DllTestClientApp
{
    public static void Main()
    {
        Assembly DLLAssembly =
            Assembly.GetAssembly(typeof(DllTestServer));
        Console.WriteLine(""nDllTestServer.dll Assembly " +
            "Information"); 
        Console.WriteLine(""t" + DLLAssembly);

        Process p = Process.GetCurrentProcess();
        string AssemblyName = p.ProcessName + ".exe";
        Assembly ThisAssembly = Assembly.LoadFrom(AssemblyName);
        Console.WriteLine("DllTestClient.exe Assembly " +
            "Information"); 
        Console.WriteLine(""t" + ThisAssembly + ""n");

    Console.WriteLine("Calling DllTestServer.Foo");
    DllTestServer.Foo();
    }
}
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章