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

前言:

上一篇介紹了 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 指令:

 該指令的使用示例,我們在下一篇章節創建數組對象指令中進行演示。

總結:

相比於參數加載指令的類型複雜度,參數存儲指令則相對簡約許多。

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章