翼度科技»论坛 编程开发 .net 查看内容

.NET Emit 入门教程:第六部分:IL 指令:3:详解 ILGenerator 指令方法:

2

主题

2

帖子

6

积分

新手上路

Rank: 1

积分
6
前言:

在上一篇中,我们介绍了 ILGenerator 辅助方法。
本篇,将详细介绍指令方法,并详细介绍指令的相关用法。
在接下来的教程,关于IL指令部分,会将指令分为以下几个分类进行讲解:
  1. 1、参数加载指令:ld 开头的指令,单词为:load argument
  2. 2、参数存储指令:st 开头的指令,单词为:store
  3. 3、创建实例指令: new 开头的指令。
  4. 4、方法调用指令:call 开头的指令。
  5. 5、分支条件指令:br 开头的指令,单词为 break
  6. 6、类型转换指令:cast 或 conv 开头的指令,单词为:convert
  7. 7、运算操作指令:add/sub/mul/div/rem ,加减乘除取余。
  8. 8、其它指令
复制代码
下面开始介绍第一部分,参数加载指令:
参数加载指令:

参数加载指令用于在方法中加载参数到操作数栈中,为后续的操作做准备。
当涉及 CIL(Common Intermediate Language)指令时,以 "ld" 开头的指令通常用于加载数据到操作数栈中。
以下是一些常见的以 "ld" 开头的参数加载指令及其简要说明:

  • ldarg: 将指定索引位置的参数加载到操作数栈中。用于将方法的参数加载到操作数栈中,以便在方法中进行操作或传递给其他方法。
  • ldarga: 将指定索引位置的参数的地址加载到操作数栈中。通常用于获取参数的地址,以便在方法中对参数进行引用传递。
  • ldc_X: 将常量加载到操作数栈中。其中 X 可以是 I(整数)、I4(32 位整数)、I8(64 位整数)、R4(单精度浮点数)、R8(双精度浮点数)、I4_M1(-1 的特殊表示)、I4_0、I4_1、I4_2、I4_3、I4_4、I4_5(特殊整数常量)等。
  • ldloc: 将指定索引位置的本地变量加载到操作数栈中。用于将方法内部的局部变量加载到操作数栈中,以进行后续的操作或传递给其他方法。
  • ldloca: 将指定索引位置的本地变量的地址加载到操作数栈中。通常用于获取局部变量的地址,以便在方法中对局部变量进行引用传递。
  • ldfld: 将对象的字段值加载到操作数栈中。用于加载对象的字段值,以便在方法中进行操作或传递给其他方法。
  • 其它:。
这些指令提供了丰富的功能,使得在方法内部能够方便地处理参数、常量、本地变量和对象字段值。通过合理使用这些指令,可以实现对数据的灵活操作和处理。
参数加载指令:短格式和长格式

在 IL(Intermediate Language)中,有一些指令支持短格式和长格式的表示(以 “_S” 结尾代表 short 短指令,默认对应无 _S结尾的即代表长格式指令)。
短格式指令通常用于跳转到相对较近的位置,而长格式指令则可以用于跳转到较远的位置。
在实际应用中,由于 IL 的灵活性和可扩展性,编译器会根据需要自动选择合适的指令格式。
这样可以根据具体的跳转距离来选择最有效的指令格式,从而使生成的代码更加高效。
总的来说,短格式和长格式的区别在于其编码的范围,短格式指令编码的范围较小,适用于相对较近的跳转位置,而长格式指令则可以覆盖更大的跳转范围。
这种设计可以使得 IL 代码在执行时更加高效。
1、从方法传参中加载:ldarg 加载参数值

ldarg: 将指定索引位置的参数加载到操作数栈中,该参数为 Load Argument(加载参数)的简写。
该参数的作用:是从指定的方法传参中获得参数值,并将该值加载到操作数栈中。
OpCodes一共提供了5个相关的指令:
  1. Ldarg_0:0索引参数。
  2. Ldarg_1:1索引参数
  3. Ldarg_2:2索引参数
  4. Ldarg_3:3索引参数
  5. Ldarg:使用自定义索引
复制代码
示例代码:
  1. ILGenerator il = methodBuilder.GetILGenerator();
  2. il.Emit(OpCodes.Ldarg_1);
  3. il.Emit(OpCodes.Ldarg,<strong>2</strong>);//这里使用自定义索引
  4. il.Emit(OpCodes.Add);
  5. il.Emit(OpCodes.Ret);
复制代码
对应生成代码:

在本段示例代码中:
  1. Ldarg_0:代表 this 当前对象。
  2. Ldarg_1:代码参数a
  3. Ldarg_2:代码参数b
  4. Ldarg_3:无。
复制代码
需要注意的是:
  1. 示例中定义的是实例方法,因此 Ldarg_0 代表 this 对象,而通过 DynamicMethod 创建的方法,默认则是静态方法,Ldarg_0 则会代表参数a。
复制代码
2、从方法传参中加载:ldarga 加载参数引用地址

ldarga: 将指定索引位置的参数的地址加载到操作数栈中,该参数为 Load Argument Address(加载参数地址)的简写。
该参数的作用:是从指定的方法传参中获得参数的地址,并将该地址加载到操作数栈中。
OpCodes一共提供了2个相关的指令:
  1. Ldarga:需要指定索引值。
  2. Ldarga_S:需要指定索引值
复制代码
需要注意的是,在 C# 中除了操作 ref 或  out 会涉及到引用地址,其它操作操作引用地址(操作指针)都需要在unsafe方法下。
否则强行操作,会出现以下异常,例如:

也可能是以下异常:

3、数字类型值加载:ldc_X 加载参数值

在 Common Intermediate Language(CIL)中,以 "ldc" 开头的操作码指令主要用于将常量加载到操作数栈中。
这些指令在.NET平台的程序集中起着重要的作用,用于处理常量数据的加载和操作。
以下是几种以 "ldc" 开头的常见操作码指令及其分类和用途:
1. ldc_i4 / ldc_i4_s


  • 分类: 整数常量加载指令
  • 用途: 将 32 位整数常量加载到操作数栈中。ldc_i4 指令用于加载常量值介于 -2^31 到 2^31-1 之间的整数,而 ldc_i4_s 则用于加载介于 -128 到 127 之间的整数常量。
2. ldc_i8


  • 分类: 长整数常量加载指令
  • 用途: 将 64 位长整数常量加载到操作数栈中。适用于加载大于 Int32 范围的整数常量。
3. ldc_r4 / ldc_r8


  • 分类: 浮点数常量加载指令
  • 用途: 将单精度浮点数(float)或双精度浮点数(double)常量加载到操作数栈中。ldc.r4 用于加载单精度浮点数常量,而 ldc.r8 用于加载双精度浮点数常量。
4. ldc_i4_m1 / ldc_i4_0 / ldc_i4_1 / ldc_i4_2 / ....../idc_i4_8


  • 分类: 特殊整数常量加载指令
  • 用途: 分别用于加载特定的整数常量值,例如 -1、0、1、2、......、6、7、8 等。
通过合理使用这些以 "ldc" 开头的操作码指令,开发人员可以方便地加载各种类型的常量数据到操作数栈中,为程序的运行和计算提供必要的数据支持。
下面来一个简单的示例:
  1. ILGenerator il = dynamicMethod.GetILGenerator();
  2. il.Emit(OpCodes.Ldc_I4, 9999);
  3. il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
  4. il.Emit(OpCodes.Ret);
复制代码
运行后输出:

4、局部变量加载:ldloc 参加变量值

ldloc: 将指定索引位置的本地变量加载到操作数栈中。用于将方法内部的局部变量加载到操作数栈中,以进行后续的操作或传递给其他方法。
该参数为:load local 加载本地变量的简写。
该方法需要配合辅助变量使用,这个在上一篇辅助方法中有介绍到,这里重温一下上一篇的辅助方法,定义变量的内容:

5、局部变量加载:ldloca 参数变量值引用地址

ldloca: 将指定索引位置的本地变量的地址加载到操作数栈中。通常用于获取局部变量的地址,以便在方法中对局部变量进行引用传递。
该参数为:load local address 加载本地(变量、引用)地址的简写。
在C#中,操作引用地址,只能是 ref 或 out 两种方式,而操作指针(也是引用地址)则需要在unsafe 方法下才可以。
下面演示一个通过 ldloca 参数返回 out 参数的示例:
  1. Dictionary<string, string> dic = new Dictionary<string, string>();
  2. dic.Add("a", "aaa");
  3. var dicType = typeof(Dictionary<string, string>);
  4. MethodInfo getValue = dicType.GetMethod("TryGetValue");
  5. var method = new DynamicMethod("GetValue", typeof(object), new[] { typeof(Dictionary<string, string>), typeof(string) }, typeof(Dictionary<string, string>));//
  6. var il = method.GetILGenerator();
  7. var outText = il.DeclareLocal(typeof(string));
  8. il.Emit(OpCodes.Ldarg_0); // Load the dic object onto the stack
  9. il.Emit(OpCodes.Ldarg_1);//设置字段名。
  10. il.Emit(OpCodes.Ldloca_S, outText);// 使用地址变量来接收: out 值
  11. il.Emit(OpCodes.Callvirt, getValue);//bool a=dic.tryGetValue(...,out value)
  12. il.Emit(OpCodes.Pop);//不需要执行的bool返回值
  13. il.Emit(OpCodes.Ldloc_0);//加载 out 变量的值。
  14. il.Emit(OpCodes.Ret); // Return the value
  15. var func = (Func<Dictionary<string, string>, string, object>)method.CreateDelegate(typeof(Func<Dictionary<string, string>, string, object>));
  16. object result = func(dic, "a");
  17. Console.WriteLine(result);
复制代码
运行结果:

说明:
  1. 对于 out 参数,加载的是地址参数,用 loca 地址指令,返回的是值,用 ldloc 值指令。
复制代码
6、对像成员变量值加载:ldfld 参加载对象成员变量值

ldfld: 将对象的字段值加载到操作数栈中。用于加载对象的字段值,以便在方法中进行操作或传递给其他方法。
该参数为:load filed 加载字段的简写。
下面给出一个示例,读取实例类员变量的值:
  1.         private static void DMethod2()
  2.         {
  3.             MyEntity myEntity = new MyEntity() { ID = 111, Name = "hello" };
  4.             FieldInfo idInfo = typeof(MyEntity).GetField("ID");
  5.             var method = new DynamicMethod("GetterFunc", typeof(object), new[] { typeof(object) }, typeof(MyEntity));
  6.             var il = method.GetILGenerator();
  7.             il.Emit(OpCodes.Ldarg_0); // Load the input object onto the stack
  8.             il.Emit(OpCodes.Ldfld, idInfo); // 加载 Id 成员变量的值到堆栈
  9.             if (idInfo.FieldType.IsValueType)
  10.             {
  11.                 il.Emit(OpCodes.Box, idInfo.FieldType); // Box the value type
  12.             }
  13.             il.Emit(OpCodes.Ret); // Return the value
  14.             var func = (Func<object, object>)method.CreateDelegate(typeof(Func<object, object>));
  15.             object result = func(myEntity);
  16.             Console.WriteLine(result);
  17.         }
  18.         class MyEntity
  19.         {
  20.             public int ID;
  21.             public string Name;
  22.         }
复制代码
运行结果:

注意事项:
1、在定义 DynamicMethod 方法时,最好手动指定 Owner 归属,即该动态方法归属哪个类型,否则在运行时可能会报以下错误: 

2、在操作实例成员变量(取值或赋值)时,通常需要加载两个参数,第一个是对象值,第二个是对象的成员变量值。
7、其它常用型值加载:ldstr、ldnull

ldstr:将常量字符串值加载到操作数栈中,示例如下:


ldnull:将null值加载到操作数栈中,示例如下:


ldtoken:通常适用于将运行时状态类型加载到操作数栈中,如将 Type 类型值压到栈中。


生成的示例代码:

总结:

本篇教程深入探讨了 ILGenerator 中的参数加载指令,通过详细解释Ldarg、Ldarga、Ldloc和Ldloca 等各种指令的使用,
读者能够清晰地认识到Ld指令用于加载参数或本地变量到堆栈,而St指令用于将值从堆栈存储到参数或本地变量中。
这些指令为动态方法的生成提供了基础,帮助开发者更好地掌握IL代码的生成和调试。
下一篇,将继续介绍存储指令部分。 

来源:https://www.cnblogs.com/cyq1162/p/18102455
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具