c# IL 入门

2023-10-24 06:11
文章标签 c# 入门 .net netcore il

本文主要是介绍c# IL 入门,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

看下面这个例子:

using System;

using System.Collections.Generic;

 

namespace ConsoleApplication3

{

    class Program

    {

        delegate void Printer();

        //代理相当于一个类型

        static void Print1()

        {

            Console.WriteLine("print1");

        }

        static void Print2()

        {

            Console.WriteLine("print2");

        }

        static void Print3()

        {

            Console.WriteLine("print3");

        }

 

        static void Main(string[] args)

        {

            Printer p = Print1;

            //方法到一个兼容委托类型的隐式转换

            //相当于复制构造函数,然而c#是没有什么隐式类型转换这种说法的

            p += Print2;//不管你加不加这后面的两个方法,Printer依然生成继承 MulticastDelegate 的类

            p += Print3;

            p.Invoke();

        }

    }

}


首先说一个概念,托管代码:

托管代码就是Visual Basic .NETC#编译器编译出来的代码。编译器把代码编译成中间语言(IL),而不是能直接在你的电脑上运行的机器码。IL是独立于CPU且面向对象的指令集

中间语言IL被封装在一个叫程序集 (assembly)的文件中,程序集中包含了描述你所创建的类,方法和属性(例如安全需求)的所有元数据。


用IL DASM 反汇编这个程序:

 

 

 

 

 

工具界面上面的一些标识的含义:

 


所以结合IL我们可以看出来:

这里表示的就是Printer类的详细信息

 


逐步分析:

.class auto ansi sealed nested private Printer

       extends [mscorlib]System.MulticastDelegate

{

} // end of class Printer

 

.class 表示Program是一个类。并且它继承自程序集—mscorlib的System. MulticastDelegate类

 

Auto 程序的加载是由CLR来管理内存的

CLR的核心功能:内存管理,程序集加载,安全性,异常处理,线程同步等等。

CLR是公共语言运行库(Common Language Runtime)和Java虚拟机一样也是一个运行时环境

 

ansi,是为了在没有托管代码与托管代码之间实现无缝转换。这里主要指C、C++代码等

 

sealed 不可被继承 nested 嵌套类

 

 

再来看看更上层的Program类的详细信息:

.class private auto ansi beforefieldinit ConsoleApplication3.Program

       extends [mscorlib]System.Object

{

} // end of class ConsoleApplication3.Program

 

 

 

Beforefieldinit是用来标记运行库(CLR)可以在静态字段方法生成后的任意时刻,来加载构造函数,否则CLR就需要在一个精准的时间加载构造函数

 

接下来看一下复制构造函数

.method public hidebysig specialname rtspecialname

        instance void  .ctor(object 'object',

                             native int 'method') runtime managed

{

} // end of method Printer::.ctor

 

Hidebysig表示当把此类作为基类,存在派生类时,此方法不被继承,同上构造函数

 

cil managed:表示其中为IL代码,指示编译器编译为托管代码(上面写过的概念)

 

 

再来看普通构造函数:

.method public hidebysig specialname rtspecialname

        instance void  .ctor() cil managed

{

  // 代码大小       8 (0x8)

  .maxstack  8

  IL_0000:  ldarg.0

  IL_0001:  call       instance void [mscorlib]System.Object::.ctor()

  IL_0006:  nop

  IL_0007:  ret

} // end of method Program::.ctor

 

.maxstack:表示调用构造函数.otor期间的评估堆栈(Evaluation Stack)

 

IL_0000:标记代码行开头


ldarg.0:表示转载第一个成员参数,在这里其实是这个对象的this指针的引用


callcall一般用于调用静态方法,因为静态方法是在编译期就确定的。而这里的构造函数.ctor()也是在编译期就制定的。

而另一指令callvirt则表示调用实例方法,它是在运行时确定的,因为如前述,当调用方法的继承关系时,就要比较基类与派生类的同名函数的实现方法(virtualnew),以确定调用的函数所属的Method Table

 

call与callvirt: call主要用来调用静态方法,callvirt则用来调用普通方法和需要运行时绑定的方法(也就是用instance标记的实例方法)。不过也存在特殊情况,那就是call去调用虚方法,比如在密封类中的虚方法因为一定不可能会被重写因此使用call可提高性能。为什么会提高性能呢?不知道你是否还记得创建一个对象去调用这个对象的方法时,我们经常会判断这个对象是否为null,如果这个对象为null时去调用方法则会报错。之所以出现这种情况是因为callvirt在调用方法时会进行类型检测,此外判断是否有子类方法覆盖的情况从而动态绑定方法,而采用call则直接去调用了。另外当调用基类的虚方法时,比如调用object.ToString方法就是采用call方法,如果采用callvirt的话因为有可能要查看子类(一直查看到最后一个继承父类的子类)是否有重写方法,从而降低了性能。不过说到底call用来调用静态方法,而callvirt调用与对象关联的动态方法的核心思想是可以肯定的,那些采用call的特殊情况都是因为在这种情况下根本不需要动态绑定方法而是可以直接使用的


ret:表示执行完毕,返回

 

 

 

 

print1():最简单的一个函数

static void Print1(){Console.WriteLine("print1"); }

 

 

.method private hidebysig static void  Print1() cil managed

{

  // 代码大小       13 (0xd)

  .maxstack  8

  IL_0000:  nop

  IL_0001:  ldstr      "print1"

  IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)

  IL_000b:  nop

  IL_000c:  ret

} // end of method Program::Print1

 

ldstr:表示将字符串压栈,是用来把一个字符串加载到内存或评估堆栈中。在我们使用这些变量之前,是需要把这些变量加载到评估堆栈(evaluation stack )中去的,实际上是将字符串引用加载到栈中而不是用newobj

 

.NET运行时任何有意义的操作都是在堆栈上完成的,而不是直接操作寄存器。这就为.NET跨平台打下了基础

IL中压栈通常以ld开头,出栈则以st开头

 

 

最后是main函数:

.method private hidebysig static void  Main(string[] args) cil managed

{

  .entrypoint   //程序入口

  // 代码大小       70 (0x46)

  .maxstack  3  //计算堆栈大小

  .locals init ([0] class ConsoleApplication3.Program/Printer p)

  IL_0000:  nop

  IL_0001:  ldnull //将空引用(O 类型)推送到计算堆栈上

  IL_0002:  ldftn      void ConsoleApplication3.Program::Print1()

                    //将指向实现特定方法的本机代码的非托管指针(native int 类型)推送到计算堆栈上。

 

  IL_0008:  newobj     instance void ConsoleApplication3.Program/Printer::.ctor(object, native int)

                    //构造Printer

                    // C#中使用new创建一个对象时则在IL中对应的是newobj,另外还有值类型也是可以通过new来创建的,不过在IL中它对应的则是initobj

                    // newobj用来创建一个对象,首先会分配这个对象所需的内存,接着初始化对象附加成员同步索引块和类型对象指针然后再执行构造函数进行初始化并返回对象引用。initobj则是完成栈上已经分配好的内存的初始化工作,将值类型置0引用类型置null即可。

 

  IL_000d:  stloc.0 //把计算堆栈顶部的值放到调用堆栈索引0处

 IL_000e:  ldloc.0      //把调用堆栈索引为0处的值复制到计算堆栈

 

  IL_000f:  ldnull

  IL_0010:  ldftn      void ConsoleApplication3.Program::Print2()

  IL_0016:  newobj     instance void ConsoleApplication3.Program/Printer::.ctor(object, native int)

                                //又构造了一个Printer

 

  IL_001b:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,                                                                                          class [mscorlib]System.Delegate)

            //调用System.Delegate::Combine,把两个System.Delegate合并

            //源码里面的 “+=”就是在这里实现的

 

  IL_0020:  castclass  ConsoleApplication3.Program/Printer

            //尝试将引用传递的对象转换为指定的类

  IL_0025:  stloc.0

  IL_0026:  ldloc.0

  IL_0027:  ldnull

  IL_0028:  ldftn      void ConsoleApplication3.Program::Print3()

  IL_002e:  newobj     instance void ConsoleApplication3.Program/Printer::.ctor(object,

                                                                                native int)

  IL_0033:  call       class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,

                                                                                          class [mscorlib]System.Delegate)

  IL_0038:  castclass  ConsoleApplication3.Program/Printer

  IL_003d:  stloc.0

  IL_003e:  ldloc.0

 

  IL_003f:  callvirt   instance void ConsoleApplication3.Program/Printer::Invoke()

        // 对对象调用后期绑定方法,并且将返回值推送到计算堆栈上

 

  IL_0044:  nop

  IL_0045:  ret

} // end of method Program::Main

 

 

 

 

 

 

 

 

 

总结一下常用的IL指令:

.entrypoint:指令表示CLR加载程序时,是首先从.entrypoint开始的,即从Main方法作为程序的入口函数

stloc.X:把计算堆栈顶部的值放到调用堆栈索引为X

ldloc.X:把调用堆栈X处的值复制到计算堆栈
.newobj 用于创建引用类型的对象;
.ldstr
:用于创建String对象变量;
.newarr
:用于创建数组型对象;
.box
:在值类型转换为引用类型的对象时,将值类型拷贝至托管堆上分配内存。

.assembly:指令告诉编译器,我们准备去用一个外部的类库(不是我们自己写的,而是提前编译好的

 

 

 

对于流程控制,主要是br、brture和brfalse这3条指令,其中br是直接进行跳转,brture和brture则是进行判断再进行跳转。

具体内容参考:https://www.cnblogs.com/fangyz/p/5547433.html

 

 

 

一个像exe这样的程序集,结构如下图:

 

一个程序集是有多个托管模块组成的,一个模块可以理解为一个类或者多个类一起编译后生成的程序集

 

程序集清单指的是描述程序集的相关信息,PE文件头描述PE文件的文件类型、创建时间等。CLR头描述CLR版本、CPU信息等,它告诉系统这是一个.NET程序集

 

元数据用来描述类、方法、参数、属性等数据,.NET中每个模块包含44个元数据表,主要包括定义表、引用表、指针表和堆。定义表包括类定义表、方法表等,引用表描述引用到类型或方法之间的映射记录,指针表里存放着方法指针、参数指针等。元数据表就相当于一个数据库,多张表之间有类似于主外键之间的关系

 

我们调用一个方法表中的方法,这个方法会指向一个触发JIT编译器地址和方法对应的IL地址,于是JIT编译器便将这个方法指向的IL编译成本地代码。生成本地代码后这个方法将会有一条引用指向本地代码首地址,这样下次调用这个方法的时候将直接执行指向的本地代码

 

 

 

 


 

 

 

 

 

IL指令大全

名称

这篇关于c# IL 入门的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/273220

相关文章

C#如何去掉文件夹或文件名非法字符

《C#如何去掉文件夹或文件名非法字符》:本文主要介绍C#如何去掉文件夹或文件名非法字符的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#去掉文件夹或文件名非法字符net类库提供了非法字符的数组这里还有个小窍门总结C#去掉文件夹或文件名非法字符实现有输入字

C#之List集合去重复对象的实现方法

《C#之List集合去重复对象的实现方法》:本文主要介绍C#之List集合去重复对象的实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C# List集合去重复对象方法1、测试数据2、测试数据3、知识点补充总结C# List集合去重复对象方法1、测试数据

C#实现将Office文档(Word/Excel/PDF/PPT)转为Markdown格式

《C#实现将Office文档(Word/Excel/PDF/PPT)转为Markdown格式》Markdown凭借简洁的语法、优良的可读性,以及对版本控制系统的高度兼容性,逐渐成为最受欢迎的文档格式... 目录为什么要将文档转换为 Markdown 格式使用工具将 Word 文档转换为 Markdown(.

Java调用C#动态库的三种方法详解

《Java调用C#动态库的三种方法详解》在这个多语言编程的时代,Java和C#就像两位才华横溢的舞者,各自在不同的舞台上展现着独特的魅力,然而,当它们携手合作时,又会碰撞出怎样绚丽的火花呢?今天,我们... 目录方法1:C++/CLI搭建桥梁——Java ↔ C# 的“翻译官”步骤1:创建C#类库(.NET

C#代码实现解析WTGPS和BD数据

《C#代码实现解析WTGPS和BD数据》在现代的导航与定位应用中,准确解析GPS和北斗(BD)等卫星定位数据至关重要,本文将使用C#语言实现解析WTGPS和BD数据,需要的可以了解下... 目录一、代码结构概览1. 核心解析方法2. 位置信息解析3. 经纬度转换方法4. 日期和时间戳解析5. 辅助方法二、L

Python中OpenCV与Matplotlib的图像操作入门指南

《Python中OpenCV与Matplotlib的图像操作入门指南》:本文主要介绍Python中OpenCV与Matplotlib的图像操作指南,本文通过实例代码给大家介绍的非常详细,对大家的学... 目录一、环境准备二、图像的基本操作1. 图像读取、显示与保存 使用OpenCV操作2. 像素级操作3.

使用C#删除Excel表格中的重复行数据的代码详解

《使用C#删除Excel表格中的重复行数据的代码详解》重复行是指在Excel表格中完全相同的多行数据,删除这些重复行至关重要,因为它们不仅会干扰数据分析,还可能导致错误的决策和结论,所以本文给大家介绍... 目录简介使用工具C# 删除Excel工作表中的重复行语法工作原理实现代码C# 删除指定Excel单元

C#使用MQTTnet实现服务端与客户端的通讯的示例

《C#使用MQTTnet实现服务端与客户端的通讯的示例》本文主要介绍了C#使用MQTTnet实现服务端与客户端的通讯的示例,包括协议特性、连接管理、QoS机制和安全策略,具有一定的参考价值,感兴趣的可... 目录一、MQTT 协议简介二、MQTT 协议核心特性三、MQTTNET 库的核心功能四、服务端(BR

C#继承之里氏替换原则分析

《C#继承之里氏替换原则分析》:本文主要介绍C#继承之里氏替换原则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#里氏替换原则一.概念二.语法表现三.类型检查与转换总结C#里氏替换原则一.概念里氏替换原则是面向对象设计的基本原则之一:核心思想:所有引py

C#实现访问远程硬盘的图文教程

《C#实现访问远程硬盘的图文教程》在现实场景中,我们经常用到远程桌面功能,而在某些场景下,我们需要使用类似的远程硬盘功能,这样能非常方便地操作对方电脑磁盘的目录、以及传送文件,这次我们将给出一个完整的... 目录引言一. 远程硬盘功能展示二. 远程硬盘代码实现1. 底层业务通信实现2. UI 实现三. De