前言:
上一篇介绍了 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 指令:
该指令的使用示例,我们在下一篇章节创建数组对象指令中进行演示。
总结:
相比于参数加载指令的类型复杂度,参数存储指令则相对简约许多。
总的来说,参数存储指令的重要性在于它为动态生成代码提供了强大的参数处理能力,让开发者可以更加灵活地操作方法的参数,实现更加复杂和多样化的编程逻辑。
通过合理运用参数存储指令,我们可以实现更加高效、智能和灵活的动态代码生成过程。
评论区