基本介紹
- 中文名:MSIT
- 轉化:機器語言
- 由於:公共語言運行
- 特定:CPU特定的代碼
執行過程,解說,結論,
執行過程
解密微軟中間語言的系列文章將通過一些簡單易懂的方式來揭示中間語言的複雜原理。這些原理通過詳細的例子來闡述。在一些例子中同時給出了原始碼和中間代碼,通過比較原始碼和中間代碼,我們可以更好地理解編譯器的局限性,指導我們編寫出更好更快的代碼。
微軟中間語言概述 1.用中間語言編寫的一個簡單程式
讓我們從經典的Hello World例子開始。首先在一個文本編輯器中輸入以下的代碼,並保存為 HelloWorld,il:
.assembly HelloWorldIL {}
.method static void HelloWorld() {
.entrypoint
ldstr "Hello World."
call void [mscorlib]System.Console::WriteLine(class System.String)
ret
}
解說
在一個中間語言程式中,如果某一行以“.”開始,則代表這是一個傳輸給彙編工具的指令,該指令要求彙編工具執行某些操作,例如生成一個函式或類。而沒有以“.”開始的行是中間語言的代碼。在中間語言中方法通過彙編命令method來定義,彙編命令後跟方法的返回值、名稱和參數。方法體被包含在{}中。例子中的ret代表該方法的結束。
一個中間語言檔案可以包含很多函式,彙編工具沒有辦法分辨應該首先執行哪一個方法。在諸如C#或VB這一類高級語言中,程式的入口方法通常都有特定的名稱,例如在C#中的public static void Main()。這就是上面的彙編工具發出錯誤提示的原因。在中間語言中,第一個被執行的方法被稱為入口函式(EntryPoint Function)。為了告訴彙編工具HelloWorld是入口函式,我們需要在代碼中增加一條彙編命令entrypoint,該命令可以放在方法體中的任何位置。需要注意的是在一個程式集中只能有一個入口函式。
中間語言代碼通常被編譯成一個模組,該模組隸屬於一個程式集。在.Net中模組和程式集的概念非常重要,因此開發人員需要很清楚地了解它們。在後面的文章中我們將詳細討論.Net程式的結構。通過在代碼中加入assembly命令,可以告訴彙編工具中間代碼隸屬於那個程式集。assembly命令的格式如下:
.assembly <程式集名稱> {}
需要注意在method命令後加入了static關鍵字,這是因為每個入口函式必須是靜態的,例如在C#中我們將Main方法定義為public static void Main()。
接下來我們需要調用WriteLine方法將HelloWorld字元串輸出到螢幕。通過使用call指令(Instruction)我們可以達到這個目的。指令的格式如下:
call <return type> <namespace>.<class name>::<method name>
這裡我們可以看到當調用一個方法時,中間語言和其他的程式語言有很大的區別。在中間語言中,如果需要調用一個方法,需要指定方法的全名,包括他的名稱域(namespace)、類名、返回值類型和參數的數據類型。這樣就保證了彙編工具能夠找到正確的方法。
在調用WriteLine方法時需要一個字元串參數。所有傳遞給方法或函式的參數都被保存在記憶體的堆疊中。在中間語言中有一個指令ldstr可以從堆疊中載入一個字元串。(堆疊是記憶體中的一塊區域,它被用於將參數傳輸給方法,在後面我們會詳細討論堆疊的問題)。所有的方法都從堆疊中獲取它們的參數,因此ldstr指令是必不可少的。ldstr指令的格式如下所示:
ldstr <parameter string>
我們可以用ILAsm.exe來編譯這個程式。在運行ILAsm.exe之前,首先需要確認一下該程式已經包含在了Windows作業系統的Path環境變數中。ILAsm.exe 可在下面的路徑中找到:
%windir%\Microsoft Dot NET\Framework\v1.0.xxxx
其中xxxx是正在使用的.NET框架的內部版本號。例如我使用的版本號是3705,則應該如下設定Path環境變數:
Set Path = %Path%;c:\Windows\Microsoft Dot NET\Framework\v1.0.3705
然後運行cmd.exe(開始->運行->輸入cmd->按下確認鍵)。在彈出的命令視窗中輸入:
J:\Testcode>ilasm HelloWorld Dot il
彙編代碼後運行程式就可以看到Hello World.的輸出。
2.改進的HelloWorld例子
在.Net中的所有語言都是面向對象的語言,但是上面的HelloWorld例子是一個結構化的例子。下面讓我們來看一下如何將它轉化為面向對象的代碼。在面向對象的編程中,我們將操作定義在類中。為了將上面的HelloWorld例子轉化為面向對象的代碼,可以使用class命令:
.class HelloWorld { }
class命令後緊跟的是類的名稱。類的名稱在中間語言中是可選的。同時我們還需要為該指令添加一些屬性,例如存取控制類在記憶體中的布局和互用性等。這樣代碼就變成了:
.assembly HelloWorldIL {}
.class public auto ansi HelloWorld extends [mscorlib]System.Object {
.method public hidebysig static void HelloWorld() cil managed {
.entrypoint
ldstr "Hello World."
call void [mscorlib]System.Console::WriteLine(class System.String)
ret
}
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed {
ldarg.0
call instance void [mscorlib]System.Object::.ctor()
ret
}
}
在代碼中用到了三個屬性:
· public:public是訪問控制屬性,它表明了對於訪問該類的成員沒有限制。
· auto:auto屬性表明了當類被載入到記憶體中時,在記憶體中的布局是由公共運行庫而不是程式決定的。
· ansi:指定ansi屬性是為了在沒有被管理和被管理的代碼之間實現無縫的轉化。在.Net中,那些不可直接套用在公共語言基礎設施之上的代碼被稱為沒有被管理的代碼,例如C、C++和VB6的代碼。我們需要一個屬性來處理被管理的代碼和沒有被管理的代碼之間的互用性。在被管理的代碼中,字元串用雙位元組的Unicode字元表示,而在被管理的代碼中,字元串有可能用單位元組的ANSI字元表示。指定了ansi屬性就可以在不同的代碼間轉化字元串了。
我們知道在.Net框架中,所有的類都直接或間接地繼承了System.Object類。在代碼中我們明確指定了HelloWorld繼承了System.Object。
在HelloWorld方法中加入了public、hidebysig、cil managed屬性,下面是對這些屬性的解釋:
· hidebysig:一個類可以繼承其他的類,hidebysig屬性保證當前類中的方法在作為父類時不會被子類繼承。例如如果HelloWorldChild類繼承了HelloWorld類,在HelloWorldChild中不會看到HelloWorld方法。
· cil managed:該屬性將在後面討論。
在高級語言中(C#,VB 等),每個類必須有構造函式,而且構造函式的第一行需要調用基類的構造函式。如果類中沒有構造函式,基類的構造函式將被自動調用。通常這是由編譯器自動完成的,現在我們要在的代碼中加入構造函式,該構造函式通過.ctor命令調用基類的構造函式。
結論
本文我們從經典的Hello World例子開始,通過實例了解了微軟中間語言的基本語法規則以及中間語言與其他開發語言的關係。在下一篇文章中,我們將在此基礎上,運用實例程式講述.net應用程式的格式和結構等內容。