.NET Emit 入門教程:第六部分:IL 指令:2:詳解 ILGenerator 輔助方法

路过秋天發表於2024-03-28

前言:

經過前面幾大部分的學習,已經掌握了 Emit 的前因後果,今天來詳細講解 Emit 中 IL 的部分內容。

如前文所講,透過 DynamicMethod(或 MethodBuilder)可獲得 ILGenerator 這個用於編寫 IL 指令的類,並用它來編寫 IL 指令。

本篇主要講解 ILGenerator 的介紹,以及主要的輔助方法,詳細的指令方法,則拆分到下一篇介紹。

下面就開邕它的介紹吧:

1、ILGenerator 介紹

ILGenerator 是.NET 中的一個重要元件,用於動態生成 Intermediate Language(IL)程式碼。

透過ILGenerator,開發人員可以在執行時建立和修改方法體內的IL指令,實現動態方法的生成和最佳化。

ILGenerator 提供了一組方法,允許程式設計師發出各種IL指令,包括載入、儲存、運算、流程控制等操作,從而實現對方法體邏輯的靈活控制。

在 .NET 開發中,ILGenerator 通常與 DynamicMethod 類結合使用。

透過 DynamicMethod 建立動態方法物件,然後使用 ILGenerator 在其中生成IL程式碼。

這種結合使開發人員能夠在執行時動態生成高效的程式碼,應用於一些需要動態生成程式碼的場景,如動態代理、AOP等。

ILGenerator 的靈活性和強大功能為.NET開發提供了更多可能性和自定義性。

2、ILGenerator 簡單示例:

先看一下之前文章提到的程式碼:

 DynamicMethod dynamicMethod = new DynamicMethod("MyMethod", typeof(void), null);
 ILGenerator il = dynamicMethod.GetILGenerator();
 il.EmitWriteLine("hello world!");
 il.Emit(OpCodes.Ret);

從示例程式碼使用了兩類方法:

指令方法:il.Emit(OpCodes.Ret)
輔助方法:il.EmitWriteLine("hello world!")

所有的輔助方法,都是基於指令方法的封裝,即用指令也可以實現該方法功能,

但用輔助方法,可以更簡單的呼叫,下面開始介紹輔助方法。

3、ILGenerator 輔助方法:EmitWriteLine

該方法封裝好的呼叫 WriteLine 輸出控制檯訊息,使用它可以簡單輸出控制檯方法,而不用編寫 Emit 指令方法。

如果用 Emit 指令,編寫是這樣的:

            var il = methodBuilder.GetILGenerator();

            il.Emit(OpCodes.Ldstr, "這是一個示例訊息");
            il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));

            il.Emit(OpCodes.Ret);

實現效果對應程式碼:

4、ILGenerator 輔助方法:異常處理 try catch finally

輔助方法中,提供了關於常用的 try catch finally 的封裝方法,可以幫助我們更簡單的編寫IL方法:

看示例程式碼:

 var il = methodBuilder.GetILGenerator();


 il.BeginExceptionBlock();// 開始 try
 il.EmitWriteLine("hello world!");

 il.BeginCatchBlock(typeof(Exception));// 開始 catch
 il.EmitWriteLine("hello world on error!");

 il.BeginFinallyBlock();// 開始 finally
 il.EmitWriteLine("hello world on finally!");

 il.EndExceptionBlock();// 結束


 il.Emit(OpCodes.Ldarg_0);
 il.Emit(OpCodes.Ret);

參照對應生成的程式碼:

5、ILGenerator 輔助方法:異常丟擲

輔助方法中,也提供了一個丟擲異常的方法,示例程式碼:

var il = methodBuilder.GetILGenerator();

il.ThrowException(typeof(Exception));

il.Emit(OpCodes.Ret);

檢視對應生成:

但是該輔助方法只能生成丟擲異常,沒有提供異常的引數。

如果需要更詳細的異常丟擲,則需要使用指令的方法:

指令方法如:

var il = methodBuilder.GetILGenerator();

il.Emit(OpCodes.Ldstr, "這是一個示例異常訊息");

// 建立一個新的 Exception 例項
ConstructorInfo ctor = typeof(Exception).GetConstructor(new Type[] { typeof(string) });
il.Emit(OpCodes.Newobj, ctor);

// 使用 ThrowException 方法引發異常
il.Emit(OpCodes.Throw);


il.Emit(OpCodes.Ret);

生成的對應程式碼:

6、ILGenerator 輔助方法:定義變數

輔助方法中,有一個是用來輔助定義變數的。

但是它需要配置 Emit 指令使用,示例程式碼:

ILGenerator il = methodBuilder.GetILGenerator();

LocalBuilder local = il.DeclareLocal(typeof(string)); // 定義變數

il.Emit(OpCodes.Ldstr,"hello world"); // 載入字串
il.Emit(OpCodes.Stloc, local); // 將字串賦值給變數

il.Emit(OpCodes.Ldloc, local); //從變數中 載入值進棧
il.Emit(OpCodes.Ret);//返回(帶值)

對應生成的程式碼:

在這個示例中(為了舉例,多了中間的賦值取值的過程):

可以看出,定義的臨時變數,都是沒有名稱的,只有型別。

它可以用來臨時存值,需要用到的時候,再將值取出,對應兩個 Emit 指令:

賦值:il.Emit(OpCodes.Stloc, local); // 將字串賦值給變數
取值:il.Emit(OpCodes.Ldloc, local); //從變數中 載入值進棧

7、ILGenerator 輔助方法:定義標籤

標籤的定義,可以理解為跳轉,即現在不常用的 goto 語句所需的的標籤。

標籤的定義,在 if else 中, switch 中,for 迴圈中,都會常常使用到標籤。

標籤的使用分為3步,定義標籤、設定標籤、跳轉到標籤。

標籤定義:

Label label = il.DefineLabel();

設定標籤:

il.MarkLabel(label);

跳轉標籤:

在 IL(Intermediate Language)中,可以使用以下指令來跳轉到標籤(Label):

條件跳轉指令:

  1. beq:如果兩個值相等,則跳轉到指定的標籤。
  2. bge:如果第一個值大於或等於第二個值,則跳轉到指定的標籤。
  3. bgt:如果第一個值大於第二個值,則跳轉到指定的標籤。
  4. ble:如果第一個值小於或等於第二個值,則跳轉到指定的標籤。
  5. blt:如果第一個值小於第二個值,則跳轉到指定的標籤.
  6. bne.un:如果兩個無符號整數值不相等,則跳轉到指定的標籤。
  7. brtrue:如果值為 true,則跳轉到指定的標籤。
  8. brfalse:如果值為 false,則跳轉到指定的標籤。
  9. brtrue.s:如果值為 true,則跳轉到指定的標籤(短格式)。
  10. brfalse.s:如果值為 false,則跳轉到指定的標籤(短格式).

無條件跳轉指令:

  1. br:無條件跳轉到指定的標籤。
  2. br.s:短格式的無條件跳轉到指定的標籤。
  3. leave:無條件跳轉到 try、filter 或 finally 塊的末尾。
  4. leave.s:短格式的無條件跳轉到 try、filter 或 finally 塊的末尾.

比較跳轉指令:

  1. bgt.un:如果第一個無符號整數值大於第二個值,則跳轉到指定的標籤。
  2. bge.un:如果第一個無符號整數值大於或等於第二個值,則跳轉到指定的標籤。
  3. blt.un:如果第一個無符號整數值小於第二個值,則跳轉到指定的標籤。
  4. ble.un:如果第一個無符號整數值小於或等於第二個值,則跳轉到指定的標籤.

其他跳轉指令:

  1. switch:根據給定的索引值跳轉到不同的標籤。
  2. brnull:如果值為 null,則跳轉到指定的標籤。
  3. brinst:如果物件是類的例項,則跳轉到指定的標籤。

這些指令可以幫助控制流程,在特定條件下跳轉到指定的標籤位置執行相應的程式碼。

透過合理使用這些跳轉指令,可以實現複雜的邏輯控制和條件判斷。

總結:

這篇教程總結了.NET Emit 中關於 IL 指令的第六部分,著重介紹了 ILGenerator 輔助方法的詳細內容。

ILGenerator 是在動態生成程式集時用來生成 Intermediate Language(IL)指令的一個重要工具。

讀者透過本篇文章,可以迅速瞭解到該教程的主要內容和重點,更好地掌握 ILGenerator 輔助方法的使用及 IL 指令的生成過程。

下一篇,我們將重點講解 IL 的指令內容。

相關文章