我們使用 C#、VB.NET 等 .NET 語言編寫的程式碼,最終都會被編譯成組件或中介語言。這意味著用 VB.NET 撰寫的程式碼可以在 C# 中修改,甚至在 COBOL 中使用。因此,深入理解 IL 是掌握 .NET 技術的關鍵基石。一旦熟悉 IL,所有 .NET 語言的運作原理都將不再有障礙,因為它們最終都會編譯為這門中性語言。值得注意的是,IL 才是先被發明的,隨後才衍生出 C#、VB.NET 等高階語言。
我們將透過一個簡短精悍的程式來展示 IL 的基本結構。假設讀者至少熟悉一門 .NET 語言。首先建立一個名為 a.il 的文字檔:
.method void vijay()
{
}
這是一個極簡的 IL 程式,定義了一個名為 vijay 的方法,回傳 void。但這個程式無法直接執行,我們需要使用 Microsoft 提供的 ilasm.exe(IL 組譯器)將其編譯為可執行檔。
在執行 ilasm 之前,請確保路徑已指向 .NET Framework SDK 的 bin 目錄:
set path=c:\progra~1\microsoft.net\frameworksdk\bin;%PATH%
接著執行編譯指令:
c:\il>ilasm /nologo /quiet a.il
編譯器會報錯:Error: No entry point declared for executable。因為 IL 檔案可以包含無數個函式,組譯器不知道從哪個函式開始執行。在 IL 中,首先被執行的函式稱為「進入點」,必須標記 .entrypoint 指令。
.method void vijay()
{
.entrypoint
ret
}
ret 指令代表函式結束,所有函式都必須以此結尾。重新編譯後雖然成功產生 a.exe,但執行時會拋出 BadImageFormatException。原因是缺少組件定義。必須加入 .assembly 指令:
.assembly mukhi {}
.method void vijay()
{
.entrypoint
ret
}
這是能成功編譯並執行的最小 IL 程式(雖然不做任何事)。.assembly 指令定義了部署單元的名稱,是 .NET 核心概念。
在 .NET 中,所有程式碼都封裝在類別中。使用 .class 指令定義類別,並可加上 private、auto、ansi 等屬性:
private:限制存取權限於當前類別。auto:類別記憶體配置由執行時期決定。ansi:處理受控與非受控程式碼間的字串封送(Unicode 轉 ANSI)。所有類別最終都隱含繼承自 System.Object,確保型別系統一致性。
IL 採用堆疊架構傳遞參數。呼叫 System.Console.WriteLine 時,必須指定完整簽章:
.method void vijay()
{
.entrypoint
ldstr "hell"
call void System.Console::WriteLine(class System.String)
ret
}
ldstr:將字串載入評估堆疊。call:呼叫方法,需指定回傳型別、命名空間、類別、方法名稱及參數型別。這與高階語言最大的不同在於:IL 呼叫時必須完整描述所有型別資訊,執行時期才會驗證有效性。
觀察 C# 編譯器產生的 IL,會發現方法帶有 public hidebysig static 等屬性:
public:公開存取。hidebysig:隱藏基類中同名同簽章的方法。static:屬於類別而非實例,進入點方法必須為 static。il managed:標示為受控程式碼(稍後深入)。完整的 IL 檔案包含模組定義、子系統規格、組件參考等中繼資料:
.module:定義模組名稱(邏輯單元)。.subsystem:指定執行環境(2=Console, 3=Windows GUI)。.corflags:CLR 標誌(1=IL 產生的 EXE, 64=Library)。.assembly extern:參考外部組件(如 mscorlib)。.ver:四段式版本號(主:次:建置:修訂)。.hash、.originator:強式名稱相關的雜湊與公開金鑰資訊。.ctor 是建構函式的 IL 名稱。搭配 specialname、rtspecialname、instance 屬性:
instance:非靜態方法,需透過實例呼叫。ldarg.0:載入 this 參數(第 0 個引數)。call instance void [mscorlib]System.Object::.ctor()。.maxstack 指定方法執行時評估堆疊的最大深度。
使用 csc.exe 編譯 C# 程式,再用 ildasm.exe 反組譯為 IL,可發現產出的 IL 結構與手寫高度一致。同理,VB.NET 編譯後的 IL 也幾乎相同(差異在於 VB 會產生 Module 對應的類別與 _vbProject 進入點包裝)。這有力證明了 .NET 的語言中立性:無論使用何種語言,最終都轉換為統一的 IL,語言差異僅止於語法層面。
本章介紹了 IL 的核心概念:方法定義、進入點、組件與模組結構、堆疊型參數傳遞、類別繼承、中繼資料指令。掌握這些基礎,有助於深入理解 .NET 執行模型、除錯優化以及跨語言互操作。