.NET Emit 入門教程:第六部分:IL 指令:4:詳解 ILGenerator 指令方法:引數儲存指令

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

前言:

上一篇介紹了 IL 指令的分類以及引數載入指令,該載入指令以ld開頭,將引數載入到棧中,以便於後續執行操作命令。

本篇開始介紹引數儲存指令,其指令以st開頭,將棧中的資料,儲存到指定的變數中,以方便後續使用。

引數儲存指令介紹:

在 IL 中,除了引數儲存指令 stargstloc 之外,還有其他一些以 "st" 開頭的指令,如 stfldstsfld,它們也用於儲存值到特定位置。以下是所有的引數儲存指令以及它們的用途:

  1. starg index:將計算堆疊頂部的值儲存到方法的引數中,引數索引由後續位元組指定。

  2. stloc index:將計算堆疊頂部的值儲存到方法的區域性變數中,區域性變數索引由後續位元組指定。

  3. stfld field:將計算堆疊頂部的值儲存到物件的欄位中,欄位由後設資料標識指定。

  4. stsfld field:用於將值儲存到靜態欄位(static field)中。靜態欄位是屬於類本身而不是類的例項的欄位,它們在整個應用程式生命週期內只有一份複製,被所有例項共享。

這些指令都是用於在 IL 中進行值的儲存操作,用途包括更新方法引數、修改區域性變數值、設定物件欄位值以及修改陣列元素。它們在方法體中起到了關鍵的作用,用於實現各種資料操作和賦值操作。

1、儲存指令:starg

  1. starg.s index:將計算堆疊頂部的值儲存到方法的引數中,引數索引由單位元組指定(適用於引數索引小於 256 的情況)。

  2. starg index:將計算堆疊頂部的值儲存到方法的引數中,引數索引由後續位元組指定(適用於引數索引大於等於 256 的情況)。

該指令為:store argument 儲存引數的簡寫。

示例程式碼:

var dynamicMethod = new DynamicMethod("GetValue", typeof(object), new[] { typeof(string) }, typeof(AssMethodIL_ST));

var ilGen = dynamicMethod.GetILGenerator();

// 使用 starg 指令將方法引數值傳遞給區域性變數
ilGen.Emit(OpCodes.Ldstr,"abc");
ilGen.Emit(OpCodes.Starg, 0); // 將方法的第一個引數值傳遞給區域性變數

// 返回區域性變數的值
ilGen.Emit(OpCodes.Ldarg_0); // 載入第一個引數(message)
ilGen.Emit(OpCodes.Ret);     // 返回該值

該示例的程式碼,主要體現在對引數重新賦值,對應的方法原型:

public static object GetValue(string arg)
{
    arg = "abc";
    return arg;
}

2、儲存指令:stloc

stloc index:將計算堆疊頂部的值儲存到方法的區域性變數中,區域性變數索引由後續位元組指定。

該指令為:store local 儲存本地(變數)的簡寫。

該方法需要配合輔助變數使用,這個在上一篇輔助方法中有介紹到,這裡重溫一下上上篇的輔助方法,定義變數的內容:

該系列指令中,還有stloc_0、stloc_1、stloc_2、stloc_3,代表定義的第N個臨時變數。

該變數的定義在反編繹 IL 中可以對照 .locals init 內容:

3、儲存指令:stfld

stfld field:將計算堆疊頂部的值儲存到物件的欄位中,欄位由後設資料標識指定。

該引數為:store filed 儲存欄位的簡寫,其常用於給欄位變數賦值。

下面舉一個給成員變數賦值的示例:

Entity entity = new Entity();

FieldInfo idInfo = typeof(Entity).GetField("ID");


var dynamicMethod = new DynamicMethod("SetValue", typeof(void), new[] { typeof(Entity), typeof(int) }, typeof(AssMethodIL_ST));

var ilGen = dynamicMethod.GetILGenerator();

ilGen.DeclareLocal(typeof(Entity));

ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Ldarg_1);
ilGen.Emit(OpCodes.Stfld, idInfo); 
ilGen.Emit(OpCodes.Ret);  

dynamicMethod.Invoke(null, new object[] { entity, 111 });

Console.WriteLine(entity.ID);
Console.Read();

執行結果:

方法原型對照:

小說明:

stfld 指令是成員變數的賦值,而我們常用的屬性賦值【get】或取值【set】,對應的是方法呼叫,因此屬性的相關操作會在方法呼叫指令一文中再述。

4、儲存指令:stsfld

該引數為:store static filed 儲存靜態欄位的簡寫,其常用於給靜態欄位變數賦值。

示例程式碼:

public class Entity { public int ID; public static int ID2; }
public static void D3()
{
    Entity entity = new Entity();

    FieldInfo idInfo = typeof(Entity).GetField("ID2", BindingFlags.Static| BindingFlags.Public);


    var dynamicMethod = new DynamicMethod("SetValue", typeof(void), new[] { typeof(int) }, typeof(AssMethodIL_ST));

    var ilGen = dynamicMethod.GetILGenerator();

    ilGen.DeclareLocal(typeof(Entity));

    ilGen.Emit(OpCodes.Ldarg_0);
   
    ilGen.Emit(OpCodes.Stsfld, idInfo); // 載入第一個引數(message)
    ilGen.Emit(OpCodes.Ret);     // 返回該值

    dynamicMethod.Invoke(null, new object[] { 222 });

    Console.WriteLine(Entity.ID2);
    Console.Read();
}

執行結果:

5、陣列儲存指令:Stelem、Stelem_Ref

當涉及到對陣列進行賦值時,可使用該指令:

如果是值型別陣列,用 Stelem 指令:

如果是引用型別陣列,用 Stelem_Ref 指令:

該指令的使用示例,我們在下一篇章節建立陣列物件指令中進行演示。

總結:

相比於引數載入指令的型別複雜度,引數儲存指令則相對簡約許多。

總的來說,引數儲存指令的重要性在於它為動態生成程式碼提供了強大的引數處理能力,讓開發者可以更加靈活地操作方法的引數,實現更加複雜和多樣化的程式設計邏輯。

透過合理運用引數儲存指令,我們可以實現更加高效、智慧和靈活的動態程式碼生成過程。

相關文章