.NET程序集

【主要內容】
@將源代碼編譯爲託管模塊
@將模塊組合爲程序集
@共享程序集(強簽名)

【概念闡述】
@將源代碼編譯爲託管模塊
1、在.NET框架裏,我們可以用任何支持CLR(Common Language Runtime)的編程語言來創建源代碼文件,然後用相應的編譯器來做
    語法檢查和源代碼分析,不管使用的是何種編程語言及編譯器,最後生成的結果都是一個託管模塊(Managed Module)。
 
    高級語言代碼 --> 託管模塊(包含IL和元數據的PE文件)--> CPU指令(運行時從IL(Intermediary Language中間語言)編譯產生)

2、託管模塊是一個需要CLR才能執行的標準PE(可移植可執行Portable Executable)文件。
     PE文件是一個非常重要的概念,它由PE表頭、CLR表頭、元數據和IL(中間語言)代碼四部分組成。

     PE表頭:主要指出了文件的類型,GUI(圖形用戶)、CUI(控制檯)或是DLL(在.NET中DLL特指程序集文件的一種形式,不同於以前的動態鏈接庫文件)。
                 另外該表頭還包括一個時間標記用於表示文件創建的時間。對於僅包含IL代碼的模塊該表頭的大部分信息會被忽略。對於包含有本地CPU代碼的模塊,
                 該表頭還會包含有關本地CPU代碼的一些信息。
     CLR表頭:包含標識託管模塊的一些信息(可以被CLR或者一些使用工具解析)。這些信息包括託管模塊所需要的CLR版本號,一些標記,託管模塊入口點
                   方法(MethodDef)元數據標記,以及有關託管模塊的元數據、資源、強命名標記和其他一些意義不是太大的信息的位置和尺寸。
     【元數據】(表):該表用於描述代碼中用到的類型和成員,描述的類型和成員有兩類,一是源代碼中定義,一是引用的。元數據總是和包含IL代碼的文件相關聯,實際上元數據總是和這些代碼一起被嵌入到同一個exe/dll文件中,編譯器總是同時產生元數據和IL代碼,並且總是同時將它們嵌入到生產的託管模塊中,兩者始終保持同步。
                 元數據的用處:
                 @元數據省去了源代碼編譯時對頭文件和庫文件的需求。這是因爲在含有實現類型和成員的IL代碼文件中,已經包含了所有被引用的類型和成員的信息,
                    編譯器可以直接從託管模塊中讀取元數據來獲得這些信息。
                 @Visual Studio.NET的智能感知(Intellisense)功能是通過分析元數據來實現的。
                 @CLR代碼驗證過程可以利用元數據來確保代碼僅執行“安全”的操作。
                 @利用元數據,可以將一個對象的字段序列化後發送到遠程機器,然後在遠程機器上執行反序列化,從而重新創建對象和它的狀態。
                 @利用元數據,垃圾收集器可以追蹤對象的生存期。對於任何對象,垃圾收集器都能通過元數據來確定該對象的類型,並且可以獲知該對象的哪些字段引
                    用了其他對象。
     IL代碼:編譯器在編譯源代碼是產生的指令,CLR運行時將它們編譯成本地CPU指令。
3、【程序集】
      程序集(assembly)是一個抽象的概念,它是一個或多個託管模塊,以及一些源文件的邏輯組合。
      可以通過Assembly Linker(【AL.exe】)將這些文件組合到一個程序集中。

4、IL(Intermediary Language中間語言)
     高級語言編譯成的IL代碼,可以用微軟提供的【ILDasm.exe】進行反編譯。可以直接用IL來編寫代碼,微軟也提供了一個彙編器【ILAsm.exe】供使用。
     高級語言提供的都只是CLR全部功能的一個子集,IL彙編語言包含CLR全部功能,當我們選擇的語言沒有提供我們需要的CLR的某些功能時,則可以選擇IL
     彙編語言,或另一個提供了該功能的高級語言來編寫這部分程序。

【實驗步驟】
=========
@查看元數據
=========
1)用notepad新建文件D:\mytest\App.cs並輸入以下實驗代碼

///////////////////////////////////////////////////////////////////////////
using System;
// 自定義類型 App。
public class App{
 private Int32 afield;
 static public void Main(System.String[] args){
  // 引用類型 System.Console。
  System.Console.WriteLine("Hi");
 }

 // 構造函數
 public App(Int32 fi){
  afield = fi;
 }
 
 // 析構函數
 ~App(){}

 // 重載操作符
 public static Boolean operator == (App a1, App a2){  
  return true;
 }

 public static Boolean operator !=  (App a1, App a2){
  return false;
 }

 public static App operator + (App a1, App a2){
  return null;
 }

 // 屬性
 public Int32 Aproperty{
  get{ return afield;}
  set{ afield = value;}
 }

 // 索引器
 public String this[Int32 x]{
  get{ return null; }
  set{}
 }

 event EventHandler AnEvent;
}
/**////////////////////////////////////////////////////////////////////////////

 

2)打開【SDK命令提示】,輸入命令:

D:\Program Files\Microsoft Visual Studio 8\SDK\v2.0>cd d:\mytest
D:\mytest>【csc.exe 】/out:App.exe /t:exe /r:MScorLib.dll App.cs

3)使用 ILDAsm.exe 來查看元數據
D:\mytest>ildasm.exe App.exe
【視圖】--> 【元信息】--> 【顯示】
 或使用快捷鍵:Ctrl + M

=========================
@將源代碼編譯爲模塊並加入到程序集中
=========================
1)建立三個源代碼文件

//==========================
//D:\mytest\ModuleDemo.cs
using System;
public class ModuleDemo{
 public static void Main(string[] args){
  ModuleA a = new ModuleA();
  ModuleB b = new ModuleB();
  Console.WriteLine(a.ToString() + "\n" + b.ToString());
 }
}

//D:\mytest\ModuleA.cs
public class ModuleA{
}

//D:\mytest\ModuleB.cs
public class ModuleB{
 private ModuleA moduleA;
}
//==========================

 

將ModuleA.cs和ModuleB.cs編譯爲模塊
D:\mytest>csc /t:module ModuleA.cs
D:\mytest>csc /t:module ModuleB.cs
ModuleB.cs(2,10): error CS0246: 找不到類型或命名空間名稱“ModuleA”(是否缺少 using 指令或程序集引用?)
D:\mytest>csc /t:module /addmodule:ModuleA.netmodule ModuleB.cs

將ModuleDemo.cs編譯爲程序集
D:\mytest>csc /t:exe  ModuleDemo.cs.
ModuleDemo.cs(3,3): error CS0246: 找不到類型或命名空間名稱“ModuleA”(是否缺少 using 指令或程序集引用?)
ModuleDemo.cs(3,19): error CS0246: 找不到類型或命名空間名稱“ModuleA”(是否缺少 using 指令或程序集引用?)
ModuleDemo.cs(4,3): error CS0246: 找不到類型或命名空間名稱“ModuleB”(是否缺少 using 指令或程序集引用?)
ModuleDemo.cs(4,19): error CS0246: 找不到類型或命名空間名稱“ModuleB”(是否缺少 using 指令或程序集引用?)
ModuleDemo.cs(5,3): error CS0103: 當前上下文中不存在名稱“Console”

雖然B中添加了ModuleA.netmodule,同時ModuleDemo.cs編譯時加入了ModuleB.cs,但仍需要添加ModuleA.netmodule。
D:\mytest>csc /t:exe  /r:MSCorLib.dll /addmodule:ModuleB.netmodule ModuleDemo.cs
ModuleDemo.cs(4,3): error CS0246: 找不到類型或命名空間名稱“ModuleA”(是否缺少 using 指令或程序集引用?)
ModuleDemo.cs(4,19): error CS0246: 找不到類型或命名空間名稱“ModuleA”(是否缺少 using 指令或程序集引用?)

D:\mytest>csc /t:exe  /r:MSCorLib.dll /addmodule:ModuleA.netmodule,ModuleB.netmodule ModuleDemo.cs

執行
D:\mytest>ModuleDemo.exe
ModuleA
ModuleB

將ModuleA.netmodule和ModuleB.netmodule移動到新建文件夾D:\mytest\mymodule下
D:\mytest>ModuleDemo.exe
未處理的異常:  System.IO.FileNotFoundException: 未能加載文件或程序集“ModuleA.netmodule”或它的某一個依賴項。系統找不到指定的文件。
文件名:“ModuleA.netmodule” ---> System.IO.FileNotFoundException: 系統找不到指定的文件。 (異常來自 HRESULT:0x80070002)在 ModuleDemo.Main(String[] args)

建立配置文件
D:\mytest\ModuleDemo.exe.config

<?xml version="1.0"?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="mymodule"/>
    </assemblyBinding>
  </runtime>
</configuration>

 

錯誤同上。該配置文件用來查找程序集的,模塊必須依賴於程序集。參考【程序集與模塊管理】。

將ModuleA.cs和ModuleB.cs分別編譯爲dll文件。
D:\mytest>csc /t:library ModuleA.cs
D:\mytest>csc /t:library /r:ModuleA.dll ModuleB.cs

新建文件夾:D:\mytest\mydll
將ModuleA.dll移動到D:\mytest\mydll
D:\mytest>csc /t:library /r:ModuleA.dll ModuleB.cs
error CS0006: 未能找到元數據文件“ModuleA.dll”

修改剛纔建立的配置文件
<probing privatePath="mydll"/>
D:\mytest>csc /t:library /r:ModuleA.dll ModuleB.cs
error CS0006: 未能找到元數據文件“ModuleA.dll”

說明該配置文件對編譯器不起作用。用如下命令纔可以。
D:\mytest>csc /t:library /r:mydll\ModuleA.dll ModuleB.cs

將生成的ModuleB.dll也移動到mydll文件夾下。
D:\mytest>csc /t:exe  /r:MSCorLib.dll,ModuleA.dll,ModuleB.dll ModuleDemo.cs
error CS0006: 未能找到元數據文件“ModuleA.dll”
error CS0006: 未能找到元數據文件“ModuleB.dll”
要使用如下命令編譯。
D:\mytest>csc /t:exe  /r:MSCorLib.dll,mytest\ModuleA.dll,mytest\ModuleB.dll ModuleDemo.cs

執行
D:\mytest>ModuleDemo.exe
ModuleA
ModuleB

修改配置文件
<probing privatePath="myxxx"/>

D:\mytest>ModuleDemo
未處理的異常:  System.IO.FileNotFoundException: 未能加載文件或程序集“ModuleA, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null”或它的某一個依賴項。
系統找不到指定的文件。文件名:“ModuleA, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null”在 ModuleDemo.Main(String[] args)

警告: 程序集綁定日誌記錄被關閉。
要啓用程序集綁定失敗日誌記錄,請將註冊表值 [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD)設置爲 1。
注意: 會有一些與程序集綁定失敗日誌記錄關聯的性能損失。
要關閉此功能,請移除註冊表值 [HKLM\Software\Microsoft\Fusion!EnableLog]。

修改配置文件
<probing privatePath="mydll"/>

執行
D:\mytest>ModuleDemo.exe
ModuleA
ModuleB

刪除mydll下的ModuleA.dll和ModuleB.dll,將mymodule下的ModuleA.netmodule和ModuleB.netmodule移動到mydll下,然後執行命令:
D:\mytest>al.exe /out:mydll\ModuleAB.dll /t:library mydll\ModuleA.netmodule mydll\ModuleB.netmodule
D:\mytest>csc /t:exe  /r:MSCorLib.dll,mydll\ModuleAB.dll ModuleDemo.cs
D:\mytest>ModuleDemo.exe
ModuleA
ModuleB

刪除ModuleA.netmodule後再執行命令:
D:\mytest>ModuleDemo.exe
未處理的異常:  System.IO.FileNotFoundException: 未能加載文件或程序集“ModuleB.netmodule”或它的某一個依賴項。系統找不到指定的文件。
文件名:“ModuleB.netmodule” ---> System.IO.FileNotFoundException: 系統找不到指定的文件。 (異常來自 HRESULT:0x80070002) 在 ModuleDemo.Main(String[] args)

撤銷刪除。

=====================
@將程序集安裝到全局程序集緩存
=====================
新建源代碼文件 NamedDemo.cs

public class NamedDemo{
}

 

編譯爲dll文件。
D:\mytest>csc /t:library NamedDemo.cs

D:\mytest>【gacutil】 /i NamedDemo.dll
將程序集添加到緩存失敗: 試圖安裝沒有強名稱的程序集

獲取密鑰文件。
創建一個名爲MyCompany.keys(名稱自定)的文件,生成一對以二進制而是存儲的共有密鑰和私有密鑰。
D:\mytest>【SN】 -k MyCompany.keys
密鑰對被寫入 MyCompany.keys

從密鑰對中提取公鑰,並將其複製到一個單獨的文件MyCompany.Publickey(名稱自定)中。
D:\mytest>SN -p MyCompany.keys MyCompany.Publickey
公鑰被寫入 MyCompany.Publickey

生成強簽名的程序集。
修改源代碼。

using System.Reflection;
[assembly:AssemblyKeyFile("MyCompany.keys")]
public class NamedDemo{
}

 

D:\mytest>csc /t:library NamedDemo.cs
NamedDemo.cs(2,11): warning CS1699: 使用命令行選項“/keyfile”或適當的項目設置代替“AssemblyKeyFile”

根據提示,修改源代碼

public class NamedDemo{
}

 

使用私鑰進行程序集簽名,執行命令:
D:\mytest>csc /t:library /keyfile:MyCompany.keys NamedDemo.cs

D:\mytest>gacutil /i NamedDemo.dll
程序集已成功添加到緩存中

D:\mytest>gacutil /u NamedDemo.dll
找不到與以下內容匹配的程序集: NamedDemo.dll
卸載的程序集數 = 0
失敗次數 = 0

D:\mytest>gacutil /u NamedDemo
程序集: NamedDemo, Version=0.0.0.0, Culture=neutral, PublicKeyToken=faeeaad7fd870aaa, processorArchitecture=MSIL
已卸載: NamedDemo, Version=0.0.0.0, Culture=neutral, PublicKeyToken=faeeaad7fd870aaa, processorArchitecture=MSIL
卸載的程序集數 = 1
失敗次數 = 0

查看公鑰
D:\mytest>sn -tp MyCompany.Publickey
公鑰爲...
公鑰標記爲 faeeaad7fd870aaa
注意此處顯示的公鑰標記與卸載程序集時顯示的PublickeyToken是一樣的。

不能查看私鑰。
D:\mytest>sn -tp MyCompany.keys
未能將密鑰轉換爲標記 -- 程序集“(null)”的公鑰無效。

不能使用公鑰加密。
D:\mytest>csc /t:library /keyfile:MyCompany.Publickey NamedDemo.cs
error CS1548: 對程序集“d:\mytest\NamedDemo.dll”簽名時加密失敗
        --“密鑰文件“d:\mytest\MyCompany.Publickey”缺少簽名所需的私鑰”

【參考資源】

SDK命令提示 http://msdn.microsoft.com/zh-cn/library/ms229859(VS.80).aspx   
csc.exe  http://msdn.microsoft.com/zh-cn/library/2fdbz5xd(VS.80).aspx
程序集  http://msdn.microsoft.com/zh-cn/library/hk5f40ct.aspx  
ILAsm.exe http://msdn.microsoft.com/zh-cn/library/496e4ekx.aspx
ILDasm.exe http://msdn.microsoft.com/zh-cn/library/f7dy01k1.aspx
元數據  http://msdn.microsoft.com/zh-cn/library/4y7k7c6k.aspx
AL.exe  http://msdn.microsoft.com/zh-cn/library/c405shex(VS.80).aspx
定位程序集 http://msdn.microsoft.com/zh-cn/library/yx7xezcf.aspx
程序集與託管模塊 http://www.cnblogs.com/JoeDZ/archive/2008/06/25/1229662.html
gacutil.exe http://msdn.microsoft.com/zh-cn/library/ex0ss12c.aspx
sn.exe  http://msdn.microsoft.com/zh-cn/library/k5b5tt23.aspx
《Microsoft.NET框架程序設計》
《C#高級編程》(第四版)
學習筆記《Microsoft.NET框架程序設計》(Blog):http://blog.csdn.net/difall/

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