上一篇介紹了 IL 指令的分類以及參數載入指令,該載入指令以ld開頭,將參數載入到棧中,以便於後續執行操作命令。本篇開始介紹參數存儲指令,其指令以st開頭,將棧中的數據,存儲到指定的變數中,以方便後續使用。 ...
前言:
上一篇介紹了 IL 指令的分類以及參數載入指令,該載入指令以ld開頭,將參數載入到棧中,以便於後續執行操作命令。
本篇開始介紹參數存儲指令,其指令以st開頭,將棧中的數據,存儲到指定的變數中,以方便後續使用。
參數存儲指令介紹:
在 IL 中,除了參數存儲指令 starg
和 stloc
之外,還有其他一些以 "st" 開頭的指令,如 stfld
和 stsfld
,它們也用於存儲值到特定位置。以下是所有的參數存儲指令以及它們的用途:
-
starg index
:將計算堆棧頂部的值存儲到方法的參數中,參數索引由後續位元組指定。 -
stloc index
:將計算堆棧頂部的值存儲到方法的局部變數中,局部變數索引由後續位元組指定。 -
stfld field
:將計算堆棧頂部的值存儲到對象的欄位中,欄位由元數據標識指定。 -
stsfld field
:用於將值存儲到靜態欄位(static field)中。靜態欄位是屬於類本身而不是類的實例的欄位,它們在整個應用程式生命周期內只有一份拷貝,被所有實例共用。
這些指令都是用於在 IL 中進行值的存儲操作,用途包括更新方法參數、修改局部變數值、設置對象欄位值以及修改數組元素。它們在方法體中起到了關鍵的作用,用於實現各種數據操作和賦值操作。
1、存儲指令:starg
-
starg.s index
:將計算堆棧頂部的值存儲到方法的參數中,參數索引由單位元組指定(適用於參數索引小於 256 的情況)。 -
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 指令:
該指令的使用示例,我們在下一篇章節創建數組對象指令中進行演示。
總結:
相比於參數載入指令的類型複雜度,參數存儲指令則相對簡約許多。
總的來說,參數存儲指令的重要性在於它為動態生成代碼提供了強大的參數處理能力,讓開發者可以更加靈活地操作方法的參數,實現更加複雜和多樣化的編程邏輯。
通過合理運用參數存儲指令,我們可以實現更加高效、智能和靈活的動態代碼生成過程。
版權聲明:本文原創發表於 博客園,作者為 路過秋天 本文歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則視為侵權。 |
個人微信公眾號 |
Donation(掃碼支持作者):支付寶: |
Donation(掃碼支持作者):微信: |