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

这篇具有很好参考价值的文章主要介绍了.NET Emit 入门教程:第六部分:IL 指令:3:详解 ILGenerator 指令方法:参数加载指令。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:

在上一篇中,我们介绍了 ILGenerator 辅助方法。

本篇,将详细介绍指令方法,并详细介绍指令的相关用法。

在接下来的教程,关于IL指令部分,会将指令分为以下几个分类进行讲解:

1、参数加载指令:ld 开头的指令,单词为:load argument

2、参数存储指令:st 开头的指令,单词为:store

3、创建实例指令: new 开头的指令。

4、方法调用指令:call 开头的指令。

5、分支条件指令:br 开头的指令,单词为 break

6、类型转换指令:cast 或 conv 开头的指令,单词为:convert

7、运算操作指令:add/sub/mul/div/rem ,加减乘除取余。

8、其它指令

下面开始介绍第一部分,参数加载指令:

参数加载指令:

参数加载指令用于在方法中加载参数到操作数栈中,为后续的操作做准备。

当涉及 CIL(Common Intermediate Language)指令时,以 "ld" 开头的指令通常用于加载数据到操作数栈中。

以下是一些常见的以 "ld" 开头的参数加载指令及其简要说明:

  1. ldarg: 将指定索引位置的参数加载到操作数栈中。用于将方法的参数加载到操作数栈中,以便在方法中进行操作或传递给其他方法。

  2. ldarga: 将指定索引位置的参数的地址加载到操作数栈中。通常用于获取参数的地址,以便在方法中对参数进行引用传递。

  3. 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(特殊整数常量)等。

  4. ldloc: 将指定索引位置的本地变量加载到操作数栈中。用于将方法内部的局部变量加载到操作数栈中,以进行后续的操作或传递给其他方法。

  5. ldloca: 将指定索引位置的本地变量的地址加载到操作数栈中。通常用于获取局部变量的地址,以便在方法中对局部变量进行引用传递。

  6. ldfld: 将对象的字段值加载到操作数栈中。用于加载对象的字段值,以便在方法中进行操作或传递给其他方法。

  7. 其它:。

这些指令提供了丰富的功能,使得在方法内部能够方便地处理参数、常量、本地变量和对象字段值。通过合理使用这些指令,可以实现对数据的灵活操作和处理。

参数加载指令:短格式和长格式

在 IL(Intermediate Language)中,有一些指令支持短格式和长格式的表示(以 “_S” 结尾代表 short 短指令,默认对应无 _S结尾的即代表长格式指令)。

短格式指令通常用于跳转到相对较近的位置,而长格式指令则可以用于跳转到较远的位置。

在实际应用中,由于 IL 的灵活性和可扩展性,编译器会根据需要自动选择合适的指令格式。

这样可以根据具体的跳转距离来选择最有效的指令格式,从而使生成的代码更加高效。

总的来说,短格式和长格式的区别在于其编码的范围,短格式指令编码的范围较小,适用于相对较近的跳转位置,而长格式指令则可以覆盖更大的跳转范围。

这种设计可以使得 IL 代码在执行时更加高效。

1、从方法传参中加载:ldarg 加载参数值

ldarg: 将指定索引位置的参数加载到操作数栈中,该参数为 Load Argument(加载参数)的简写。

该参数的作用:是从指定的方法传参中获得参数值,并将该值加载到操作数栈中。

OpCodes一共提供了5个相关的指令:

Ldarg_0:0索引参数。
Ldarg_1:1索引参数
Ldarg_2:2索引参数
Ldarg_3:3索引参数
Ldarg:使用自定义索引

示例代码:

ILGenerator il = methodBuilder.GetILGenerator();
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg,2);//这里使用自定义索引
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);

对应生成代码:

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

在本段示例代码中:

Ldarg_0:代表 this 当前对象。

Ldarg_1:代码参数a

Ldarg_2:代码参数b

Ldarg_3:无。

需要注意的是:

示例中定义的是实例方法,因此 Ldarg_0 代表 this 对象,而通过 DynamicMethod 创建的方法,默认则是静态方法,Ldarg_0 则会代表参数a。

2、从方法传参中加载:ldarga 加载参数引用地址

ldarga: 将指定索引位置的参数的地址加载到操作数栈中,该参数为 Load Argument Address(加载参数地址)的简写。

该参数的作用:是从指定的方法传参中获得参数的地址,并将该地址加载到操作数栈中。

OpCodes一共提供了2个相关的指令:

Ldarga:需要指定索引值。
Ldarga_S:需要指定索引值

需要注意的是,在 C# 中除了操作 ref 或  out 会涉及到引用地址,其它操作操作引用地址(操作指针)都需要在unsafe方法下。

否则强行操作,会出现以下异常,例如:

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

也可能是以下异常:

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

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" 开头的操作码指令,开发人员可以方便地加载各种类型的常量数据到操作数栈中,为程序的运行和计算提供必要的数据支持。

下面来一个简单的示例:

ILGenerator il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldc_I4, 9999);
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
il.Emit(OpCodes.Ret);

运行后输出:

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

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

ldloc: 将指定索引位置的本地变量加载到操作数栈中。用于将方法内部的局部变量加载到操作数栈中,以进行后续的操作或传递给其他方法。

该参数为:load local 加载本地变量的简写。

该方法需要配合辅助变量使用,这个在上一篇辅助方法中有介绍到,这里重温一下上一篇的辅助方法,定义变量的内容:

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

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

ldloca: 将指定索引位置的本地变量的地址加载到操作数栈中。通常用于获取局部变量的地址,以便在方法中对局部变量进行引用传递。

该参数为:load local address 加载本地(变量、引用)地址的简写。

在C#中,操作引用地址,只能是 ref 或 out 两种方式,而操作指针(也是引用地址)则需要在unsafe 方法下才可以。

下面演示一个通过 ldloca 参数返回 out 参数的示例:

Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add("a", "aaa");
var dicType = typeof(Dictionary<string, string>);
MethodInfo getValue = dicType.GetMethod("TryGetValue");


var method = new DynamicMethod("GetValue", typeof(object), new[] { typeof(Dictionary<string, string>), typeof(string) }, typeof(Dictionary<string, string>));//
var il = method.GetILGenerator();
var outText = il.DeclareLocal(typeof(string));

il.Emit(OpCodes.Ldarg_0); // Load the dic object onto the stack
il.Emit(OpCodes.Ldarg_1);//设置字段名。
il.Emit(OpCodes.Ldloca_S, outText);// 使用地址变量来接收: out 值
il.Emit(OpCodes.Callvirt, getValue);//bool a=dic.tryGetValue(...,out value)
il.Emit(OpCodes.Pop);//不需要执行的bool返回值

il.Emit(OpCodes.Ldloc_0);//加载 out 变量的值。
il.Emit(OpCodes.Ret); // Return the value

var func = (Func<Dictionary<string, string>, string, object>)method.CreateDelegate(typeof(Func<Dictionary<string, string>, string, object>));

object result = func(dic, "a");

Console.WriteLine(result);

运行结果:

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

说明:

对于 out 参数,加载的是地址参数,用 loca 地址指令,返回的是值,用 ldloc 值指令。

6、对像成员变量值加载:ldfld 参加载对象成员变量值

ldfld: 将对象的字段值加载到操作数栈中。用于加载对象的字段值,以便在方法中进行操作或传递给其他方法,该参数为:load filed 加载字段的简写。

ldsfld:该指令用于操作静态成员变量,该参数为:load static filed 加载静态字段的简写。

下面给出一个示例,读取实例类员变量的值:

        private static void DMethod2()
        {
            MyEntity myEntity = new MyEntity() { ID = 111, Name = "hello" };
            FieldInfo idInfo = typeof(MyEntity).GetField("ID");


            var method = new DynamicMethod("GetterFunc", typeof(object), new[] { typeof(object) }, typeof(MyEntity));
            var il = method.GetILGenerator();

            il.Emit(OpCodes.Ldarg_0); // Load the input object onto the stack
            il.Emit(OpCodes.Ldfld, idInfo); // 加载 Id 成员变量的值到堆栈
            if (idInfo.FieldType.IsValueType)
            {
                il.Emit(OpCodes.Box, idInfo.FieldType); // Box the value type
            }

            il.Emit(OpCodes.Ret); // Return the value

            var func = (Func<object, object>)method.CreateDelegate(typeof(Func<object, object>));

            object result = func(myEntity);

            Console.WriteLine(result);

        }
        class MyEntity
        {
            public int ID;
            public string Name;

        }

运行结果:

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

注意事项:

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

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

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

7、其它常用型值加载:ldstr、ldnull、ldtoken

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

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

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

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

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

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

生成的示例代码:

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

8、数组取值指令:Ldelem_Ix 系列指令与 Ldelem_Ref 指令

值类型数组,用:Ldelem_IX 系列指令

ILGenerator il = methodBuilder.GetILGenerator();

//创建数组
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldc_I4,0);
il.Emit(OpCodes.Ldelem_I4);
il.Emit(OpCodes.Ret); // 返回该值

生成的示例代码:

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

对其引用类型数组,可以用:Ldelem_Ref 指令:

ILGenerator il = methodBuilder.GetILGenerator();

//创建数组
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldc_I4,0);
il.Emit(OpCodes.Ldelem_Ref);
il.Emit(OpCodes.Ret);     // 返回该值

总结:

本篇教程深入探讨了 ILGenerator 中的参数加载指令,通过详细解释Ldarg、Ldarga、Ldloc和Ldloca 等各种指令的使用,

读者能够清晰地认识到Ld指令用于加载参数或本地变量到堆栈,而St指令用于将值从堆栈存储到参数或本地变量中。

这些指令为动态方法的生成提供了基础,帮助开发者更好地掌握IL代码的生成和调试。

下一篇,将继续介绍存储指令部分。 文章来源地址https://www.toymoban.com/news/detail-844115.html

到了这里,关于.NET Emit 入门教程:第六部分:IL 指令:3:详解 ILGenerator 指令方法:参数加载指令的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • .NET Emit 入门教程:第六部分:IL 指令:9:详解 ILGenerator 指令方法:运算操作指令(指令篇结束)

    经过前面几篇的学习,我们了解到指令的大概分类,如: 参数加载指令,该加载指令以 Ld 开头,将参数加载到栈中,以便于后续执行操作命令。 参数存储指令,其指令以 St 开头,将栈中的数据,存储到指定的变量中,以方便后续使用。 创建实例指令,其指令以 New 开头,

    2024年04月22日
    浏览(41)
  • .NET Emit 入门教程:第三部分:构建模块(Module)

    在这一部分中,我们将深入讨论动态程序集中模块的概念以及如何构建和管理模块。 模块是动态程序集中的基本单位,它类似于一个独立的代码单元,可以包含类型、方法、字段等成员。 在动态程序集中,模块扮演着组织代码和实现代码复用的关键角色。 它们允许开发人员

    2024年03月22日
    浏览(59)
  • .NET Emit 入门教程:第四部分:构建类型(Type)

    在动态生成代码的过程中,构建类型(Type)是至关重要的一步。 通过使用 Emit 中的 TypeBuilder,我们可以定义和创建各种类型,包括类、结构体和接口。 本节将深入探讨如何使用 TypeBuilder 动态构建类型,并介绍其在实际应用中的重要性。 通过学习本系列之前的文章,我们可

    2024年03月25日
    浏览(37)
  • .NET Emit 入门教程:第二部分:构建动态程序集(追加构建静态程序集教程)

    在本部分中,我们将深入探讨如何使用C# Emit 来创建动态程序集。 动态程序集是在运行时生成的,为我们提供了一种灵活、动态地构建和加载程序集的能力。 程序集是.NET中的基本部署单位,它包含了可执行代码、资源、元数据等信息,是.NET应用程序的基本组成单元之一。

    2024年03月21日
    浏览(44)
  • 【Git 入门教程】第六节、Git高级操作

    Git是一种非常强大的分布式版本控制系统,可以帮助开发者轻松地管理和协调代码库。在本文中,我们将介绍一些Git高级操作,包括如何管理Git仓库、标签、子模块和忽略文件。 Git提供了许多命令来管理本地Git仓库。以下是一些常用的管理命令: 初始化一个新的Git仓库: 查

    2024年02月05日
    浏览(49)
  • Odoo17入门教程第六章 UI

    现在我们已经创建了新模型及其 相应的,是时候了 与用户界面交互。 在本章结束时,我们将创建几个菜单以访问默认列表 和窗体视图。 参考 :与此主题相关的文档可以在数据文件中找到。 在第五章:安全性-简介中,我们通过 CSV 文件添加了数据。CSV 当要加载的数据具有

    2024年04月23日
    浏览(48)
  • 掌握Linux指令和权限:一个入门教程

    语法格式 :ls [选项][目录或者文件] 功能 :对于目录,该命令列出该目录下的所有子目录与文件。对于文件,将列出文件名以及其他信息。 a 列出目录下的所有文件,包括以 . 开头的隐含文件。 -d 将目录象文件一样显示,而不是显示其下的文件。 如:ls –d 指定目录 -i 输出文

    2023年04月23日
    浏览(41)
  • C语言快速入门教程1快速入门 2指令 3条件选择

    C是一种编程语言,1972年由Dennis Ritchie在美国AT T的贝尔实验室开发。C语言变得很流行,因为它很简单,很容易使用。今天经常听到的一个观点是--\\\"C语言已经被C++、Python和Java等语言所取代,所以今天何必再去学习C语言\\\"。我很不赞同这种观点。这有几个原因。这些原因如下:

    2024年02月03日
    浏览(46)
  • C语言入门教程,C语言学习教程(第三部分:C语言变量和数据类型)二

    前面我们多次提到了字符串,字符串是多个字符的集合,它们由 \\\" \\\" 包围,例如 \\\"http://c.biancheng.net\\\" 、 \\\"C语言中文网\\\" 。字符串中的字符在内存中按照次序、紧挨着排列,整个字符串占用一块连续的内存。 当然,字符串也可以只包含一个字符,例如 \\\"A\\\" 、 \\\"6\\\" ;不过为了操作方

    2024年01月17日
    浏览(51)
  • ESP8266-01s入门:上报和下发数据控制单片机 AT指令讲解和上云 烧录AT固件与OneNET MQTT通信教程包含MQTT.fx1.7.1教程(微信小程序通信单片机前置任务)

    本项目教程总共分为四节 1.创建OneNET新版MQTT设备:为微信小程序与单片机通信打基础(微信小程序通信单片机前置任务) 2.(当前文章)ESP8266-01s入门:烧录AT固件与OneNET MQTT通信教程包含MQTT.fx1.7.1教程(微信小程序通信单片机前置任务) 3.物联网实践教程:微信小程序结合

    2024年02月04日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包