程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> [你必須知道的.NET]第二十四回:認識元數據和IL(上)

[你必須知道的.NET]第二十四回:認識元數據和IL(上)

編輯:關於.NET

說在,開篇之前

很早就有說說Metadata(元數據)和IL(中間語言)的想法了,一直在這篇開始才算腳踏實地的對這兩個階級兄弟投去些細關懷,雖然來得沒有《第一回:恩怨情仇:is和as》那麼迅速,但是Metadata和IL卻是絕對重量級的內容,值得我們在任何時間關注,本文就是開始。

1 引言

你可曾想到,我們的C#代碼,編譯之後究竟為何物?你可曾認知,我們的可執行程序,運行之時的軌跡究竟為哪般?那麼,本文通過對Metadata(元數據)和IL(Intermediate Language, 中間語言)的認識開始,來逐步給出答案。在這個探索軌跡上,元數據、IL、程序集、程序域、JIT、虛分派、方法表和托管堆這些形形色色的神秘嘉賓將在某個時刻不期而遇,作為你必須知道的.NET 系列2.0版本的一部分,本文首先從認識元數據和IL這兩位重量級選手開始,而其他的嘉賓也將很快登場。

2 初次接觸

在事實上,編譯之後的cs代碼被組織為兩種基本的元素:元數據(Metadata)和IL。我們可以以最簡單的方式來了解程序集(*.dll)或可執行文件(*.exe)中包含的Metadata和IL的秘密,這種方式就是我們常說的反編譯,打開ILDasm並加載實現准備的程序集,我們可以看到托管PE文件的相關內容:

詳細的結構信息和IL代碼分析,可以參見[你必須知道的.NET]第3章 “一切從IL開始”的介紹,在此就不做太多的分析。另外,我們可以通過執行“View/MetaInfo/Show!”或者Ctrl+M快捷鍵來獲取該程序集所使用的MetaData信息列表:

其中該程序集使用的元數據主要有:Module、TypeRef、TypeDef、Method、Param、MemberRef、CostomAttribute、Assembly、AssemblyRef等,同時還包括#Strings、#GUID、#Blob、#US堆等。

當然,關於ILDasm工具,還有很多好玩的使用方式來滿足我們探索IL代碼的好奇心,例如:

ildasm Anytao.Insidenet.MetadataIL.exe /output:my.il,將反編譯結果導出為il代碼格式,生成一個my.il包含了所有的IL代碼和一個my.res包含了所有的資源文件。

ildasm Anytao.Insidenet.MetadataIL.exe /text,將反編譯結果以Console形式輸出。

當然我們還是推薦以GUI形式來查看IL細節,組織結構良好的Class View:

ildasm Anytao.Insidenet.MetadataIL.exe

下面首先給出參與編譯的相關代碼文件,然後再展開我們對Metadata和IL的討論:

// Release : code01, 2009/02/12
// Author  : Anytao, http://www.anytao.com
// List    : One.cs
public class One
{
     public int ID { get; set; }
}// Release : code02, 2009/02/12
// Author  : Anytao, http://www.anytao.com
// List    : Two.cs
public class Two
{
     public string SayHello()
     {
         return "Hello, world.";
     }
}// Release : code03, 2009/02/12
// Author  : Anytao, http://www.anytao.com
// List    : Program.cs
class Program
{
     static void Main(string[] args)
     {
         int id = 1;
         One one = new One();
         one.ID = id;
         Two two = new Two();
         Console.WriteLine(two.SayHello());
     }
}

接著,我們對上述程序的編譯執行過程進行一點探索,以命令行編譯器來演化其大致的編譯過程,以此進一步了解托管模塊,程序集和可執行文件之間的關系:

打開Visual Studio 2008 Command Prompt,並定位到cs代碼所在文件夾,編譯One.cs為托管模塊,執行命令:

csc /t:module One.cs

執行之後,將生成名為One.netmodule文件;

繼續執行,將多個模塊打包為程序集

csc /t:library /addmodule:One.netmodule Two.cs

執行之後,將生成名為Two.dll文件;

最後,編譯Main函數和Two.dll為可執行文件

csc /out:Anytao.Insidenet.MetatdataIL.exe /t:exe /r:Two.dll /r:mscorlib.dll Program.cs

最終將得到本文開始時所加載的用於反編譯的程序集文件Anytao.Insidenet.MetadataIL.exe,在該執行命令中對幾個指示符開關做點說明:

/out:Anytao.Insidenet.MetadataIL.exe,表示輸出的可執行文件,及其名稱

/t:exe,表示輸出的文件類型為CUI(控制台界面程序)程序;而/t:winexe,表示輸出為GUI(圖形界面程序)程序

/r:Two.dll,表示引用剛剛生產的Two.dll程序集

/r:mscorlib.dll,表示因為外部程序集mscorlib.dll,因為我們的程序中使用了Console靜態方法,而該方法則被定義在mscorlib.dll中。mscorlib.dll是如此的重要,我們將在本文之後的某些時候再次與mscorlib.dll握手,那時在對其進行一個詳細的分析,敬請期待。

在cmd中的執行過程可以參考:

通過分步執行的方式我們對csc編譯器的執行過程有個基本的了解,也同時從側面認識了每次在Visual Studio中執行“Build“或者“ReBuild”的縮影。綜上分析,我們可以簡單的看到:

Note:在Visual Studio中,編譯是分模塊進行的,編譯結果保存在obj目錄中,最後再合並為可執行文件於bin目錄,同時默認情況下,編譯過程是增量式的,僅編譯發生修改的模塊,我將在後文給出較為詳細的過程。

同時,我們還可以收獲以下幾個基本的結論:

cs代碼編譯之後將生成元數據和IL,並組成托管模塊(Module)的基本單元。

多個托管模塊組成程序集,其實還包括一定的資源文件,只是沒有在此體現。

程序集或者可執行文件是邏輯組織的基本單元,符合基本的Windows PE文件格式,可以被x86或者x64Windows直接加載執行。

3 繼續深入

一個或者多個模塊,再加上資源文件就形成了程序集(Assembly),作為邏輯組織的基本單元,

事實上,此圖僅僅從粗粒度對程序集的基本組成有個大致的了解,實際上程序集中包含了復雜的結構和要素,例如PE Signature、Managed Resources、Strong Name Signature Hash,而其中最核心的要素則體現在上圖。

程序集清單(MANIFEST)包含了程序集的自描述信息,主要包含AssemblyDef、FileDef、ManifestResourceDef和ExportedTypeDef,在反編譯選項中MANIFEST包含了詳細的內容。在《你必須知道的.NET》3.1節 “從Hello,world開始認識IL”對其有過詳細的描述,此不贅述。

PE文件頭,標准Windows PE頭文件(PE32或PE32+),PE文件的基本信息,例如文件類型,創建時間,本地CPU信息等。

CLR頭,包含CLR版本、模塊元數據、資源等信息。

資源文件。

執行View/Statisctics菜單,可以打開相關的統計信息:

File size            : 5632
  PE header size       : 512 (496 used)    ( 9.09%)
  PE additional info   : 1691              (30.02%)
  Num.of PE sections   : 3
  CLR header size     : 72                 ( 1.28%)
  CLR meta-data size  : 2212               (39.28%)
  CLR additional info : 0                  ( 0.00%)
  CLR method headers  : 52                 ( 0.92%)
  Managed code         : 287               ( 5.10%)
  Data                 : 2048              (36.36%)
  Unaccounted          : -1242             (-22.05%)

  Num.of PE sections   : 3
    .text    - 3072
    .rsrc    - 1536
    .reloc   - 512

  CLR meta-data size  : 2212
    Module        -    1 (10 bytes)
    TypeDef       -    4 (56 bytes)      0 interfaces, 0 explicit layout
    TypeRef       -   25 (150 bytes)
    MethodDef     -    8 (112 bytes)     0 abstract, 0 native, 8 bodies
    FieldDef      -    1 (6 bytes)       0 constant
    MemberRef     -   29 (174 bytes)
    ParamDef      -    2 (12 bytes)
    CustomAttribute-   16 (96 bytes)
    StandAloneSig -    4 (8 bytes)
    PropertyMap   -    1 (4 bytes)
    Property      -    1 (6 bytes)
    MethodSemantic-    2 (12 bytes)
    Assembly      -    1 (22 bytes)
    AssemblyRef   -    1 (20 bytes)
    Strings       -   920 bytes
    Blobs         -   328 bytes
    UserStrings   -    68 bytes
    Guids         -    16 bytes
    Uncategorized -   192 bytes

  CLR method headers : 52
    Num.of method bodies  - 8
    Num.of fat headers    - 4
    Num.of tiny headers   - 4

  Managed code : 287
    Ave method size - 35

我們將在後篇《深入程序集和模塊》中對PE頭,CLR頭和資源文件進行詳細論述。

IL代碼被組織為

.class public auto ansi beforefieldinit Anytao.Insidenet.MetadataIL.Two
        extends [mscorlib]System.Object
     {
       .method public hidebysig instance string
               SayHello() cil managed
       {
         // Code size       11 (0xb)
         .maxstack  1
         .locals init ([0] string CS$1$0000)
         IL_0000:  nop
         IL_0001:  ldstr      "Hello, world."
         IL_0006:  stloc.0
         IL_0007:  br.s       IL_0009

         IL_0009:  ldloc.0
         IL_000a:  ret
       } // end of method Two::SayHello

       .method public hidebysig specialname rtspecialname 
               instance void  .ctor() cil managed
       {
         // Code size       7 (0x7)
         .maxstack  8
         IL_0000:  ldarg.0
         IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
         IL_0006:  ret
       } // end of method Two::.ctor

     } // end of class Anytao.Insidenet.MetadataIL.Two

包裝在類似於匯編模樣的外衣下,我看依稀可見class, System.Object, method, public, string這些面向對象高級語言中的熟悉面孔,不同的只是多了很多benforefieldinit(參考:[你必須知道的.NET]第二十三回:品味細節,深入.NET的類型構造器), ret, maxstack, ldstr, stloc這些陌生的指令。然而IL並非一個怪胎,而正是基於其本身面向對象的匯編式風格,才造就了IL代碼成為名副其實的“中間語言”的重任。通過IL代碼,CLR就可在編譯時由JIT編譯轉換為Native Code,我們將在下節繼續分析這個過程的來龍去脈。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved