[你必须知道的.NET]第二十六回:认识元数据和IL(下)

2024-01-02 00:58

本文主要是介绍[你必须知道的.NET]第二十六回:认识元数据和IL(下),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

说在,开篇之前
书接上回: 
第二十四回:认识元数据和IL(上), 第二十五回:认识元数据和IL(中) 

我们继续。 

终于到了,说说元数据和IL在JIT编译时的角色了,虽然两个回合的铺垫未免铺张,但是却丝毫不为过,因为只有充分的认知才有足够的体会,技术也是如此。那么,我们就开始沿着方法调用的轨迹,追随元数据和IL在那个神秘瞬间所贡献的力量吧。  

                                                                                      www.anytao.com

 

 

5 元数据和IL在JIT编译时

CLR最终执行的只有本地机器码,所以JIT编译的作用是在运行时将IL代码解析为机器码执行。对于JIT编译,我们会以专门的篇幅来全面了解,本文只将目光关注于元数据和IL在程序执行时的作用和参与细节。首先,IL是基于栈执行的,执行方法调用时,方法参数、局部变量还有返回值等被分配于栈上,并执行其调用过程,既然是关注JIT编译时,因此我们自然而然将关注方法的执行,因为JIT编译是以执行方法调用而触发的。

首先,对本文开始的代码加点新料:

// Release : code04, 2009/02/24                    
// Author  : Anytao, http://www.anytao.com
// List    : Base.cs
public class Base
{public void M(){Console.WriteLine("M in Base");}public virtual void N(){Console.WriteLine("N in Base");}
}

还有:

// Release : code05, 2009/02/24                    
// Author  : Anytao, http://www.anytao.com
// List    : Three.cs
public class Three : Base
{private static int ID { get; set; }public override void N(){//Something new in ThreeConsole.WriteLine("N in Three");}public void M(){Console.WriteLine("M in Three");M1();}public void M1(){Console.WriteLine("M1 in Three");}
}

还有执行代码:

static void Main(string[] args)
{Base three = new Three();three.M();three.N();
}

小窥方法表

以该例而言,执行Main方法调用时,同时伴随着对于Three实例的创建,和相应类型信息的加载。我们先将类型信息创建的秘密放在以后的内容中,好留点悬念在未来发挥,哈哈。然而,类型加载一定是在实例创建之前完成的,也就是我们常常提起的方法表创建。类型加载是由class loader负责执行的,其过程简言之就是从元数据表中获取相应的类型信息,创建方法表(包含CORINFO_CLASS_STRUCT结构),其结构主要包括非虚方法表和虚方法表,按照继承的虚方法、新引入的虚方法、实例方法和静态方法的顺序排列,以类Three类型为例其CORINFO_CLASS_STRUCT结构可以表示为:

Note: 在本例中Three没有定义任何静态方法,其方法表中父类方法N已有子类覆写,同时因为有静态成员存在的原因,CLR会自动创建类型构造器,详细情况可参考《你必须知道的.NET》1.2节 “什么是继承”。

我们可以同过加载SOS调试来了解相应的方法表信息:

  • 在three.N()调用处打好断点,来查看该时刻的dump信息,就像一个内存快照,发现多少东西就看摄影师的水准。
  • 然后,通过dumpheap加载类型信息,获取方法表地址(0x002a354c),
!dumpheap -type Three
Address       MT     Size
01d332c4 002a354c       12     
total 1 objects
Statistics:MT    Count    TotalSize Class Name
002a354c        1           12 Anytao.Insidenet.MetadataIL.Three
  • 并根据MT地址,以dumpmt查看相关的MethodDesc信息,
!dumpmt -md 002a354c
EEClass: 002a15b4
Module: 002a2f2c
Name: Anytao.Insidenet.MetadataIL.Three
mdToken: 02000003  (E:\anytao\Today\OnWriting\MetadataIL\Anytao.Insidenet.MetadataIL\Anytao.Insidenet.MetadataIL\bin\Debug\Anytao.Insidenet.MetadataIL.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 10
--------------------------------------
MethodDesc TableEntry MethodDesc      JIT Name
6f756a70   6f5d1328   PreJIT System.Object.ToString()
6f756a90   6f5d1330   PreJIT System.Object.Equals(System.Object)
6f756b00   6f5d1360   PreJIT System.Object.GetHashCode()
6f7c7460   6f5d1384   PreJIT System.Object.Finalize()
002ac0b8   002a3514     NONE Anytao.Insidenet.MetadataIL.Three.N()
002ac0d0   002a3540      JIT Anytao.Insidenet.MetadataIL.Three..ctor()
002ac0a8   002a34f4     NONE Anytao.Insidenet.MetadataIL.Three.get_ID()
002ac0b0   002a3504     NONE Anytao.Insidenet.MetadataIL.Three.set_ID(Int32)
002ac0c0   002a3520     NONE Anytao.Insidenet.MetadataIL.Three.M()
002ac0c8   002a3530     NONE Anytao.Insidenet.MetadataIL.Three.M1()

经过简单的Dump,方法表的信息和我们图示的信息相差无几,细心的观众可能会发现Dump信息中并不包含Three::cctor(),那么你答对了。图示的cctor是我基于为Three实现了类型构造器(静态构造函数)而特别加入的,而代码中dump的方法表并没有把类型构造器包含在内,这是个小粗心,希望细心的您看得够透。

执行细则

具体的执行过程为为:

  • class loader从TypeDef元数据表加载相关元数据信息,包括当前类型,继承层次的所有父类和实现的接口元数据,根据这些信息建立CORINFO_CLASS_STRUCT结构:

当然,对class loader,我们可以进行一点知识救急:

 

上课啦:class loader

Classic Loader是CLR提供的基本组件之一,作用正像其名称所宣扬的那样,load一个Class给CLR,class loader将Metadata和IL从PE文件中取出,并加载到运行时内存,简单的说就是我们下面要介绍的全过程缩影。

当然,如果你总是对CLR的Classic Loader耿耿于怀,不能释然。那么,我们也可以参考MSDN的资料来实现自定义的Classic Loader[How to: Write a Class Loader],希望其中能提供灵光一现的思考。

                                                                                      www.anytao.com

 

  • 加载之后,方法执行之前的CORINFO_CLASS_STRUCT中所有的方法表槽都保存了方法应该执行的行为逻辑,这些信息保存在被称为方法描述(MethodsDesc)的结构中,而MethodDesc则被初始化为指向IL代码,同时还包含一个指向触发JIT编译的PreJitStub地址,如下:

上述所有方法描述都指向各自的IL代码地址和JIT编译器,在此我们仅仅以N()方法为例来进行说明,详细的情况可以参考MSDN相关内容。

  • 简单的说,任何方法第一次执行时都会首先触发执行JIT编译,JIT的主要工作就是将IL代码翻译为Native Code,并插入指向Native Code的jmp指令地址覆盖原来的Call JIT Compiler指令:

  • 当该方法再次被执行时,因为MethodDesc中保存了机器码地址,以后的执行将不会执行JIT编译过程而直接执行x86(X64)机器码,实现整个执行过程。

纵观整个JIT编译的全过程,其细节的实现远比我们这里呈现的复杂,在粗略的步骤中我们大致了解了元数据和IL在整个过程中的作用、角色和关系,对了解CLR运行机制而言,适当的选择是明智的,如果有更多的心思探索,那么就在以后的岁月中由简及繁吧,但是相信这一定是一次美妙的旅程。

6 结论

Metadata描述了静态的结构,而IL阐释了动态的执行,这一静一动承载了太多的技术奥秘。

当这篇文章行将结束的时候,我发现牵一发而动全身,由此引入的新问题接踵而至,方法调用、程序集、程序域、CLR加载过程在元数据和IL的分析中若隐若现,也驱使我投入注意在后面的《你必须知道的.NET》中,将这些内容一一过招。由此才能在复杂的概念和本质之余,由点及面的对所有内容综合把握,形成全面的了解和一条线贯穿的认识,那么未来Anytao将要继续分享还有:

系列预告
  • 程序集和模块
  • 程序域
  • CLR加载过程
  • JIT编译
  • 方法调用
  • 反射种种
  • 其他…

                                                                                      www.anytao.com

限于繁忙的原因,我无法给出一个清晰的时间表,但力图每次的内容都给您出足够的收获,如果你对.NET始终心怀兴致,那么敬请期待《你必须知道的.NET》更多精彩。 


支持anytao的创业产品Worktile
Worktile,新一代简单好用、体验极致的团队协同、项目管理工具,让你和你的团队随时随地一起工作。完全免费,现在就去了解一下吧。
https://worktile.com

  • 《你必须知道的.NET》第3章 “一切从IL开始”
  • DonBox,《.NET本质论》
  • http://www.sloppycode.net/articles/inside-net-assemblies-and-metadata.aspx
  • http://www.codeproject.com/KB/dotnet/dotnetformat.aspx

温故知新

这篇关于[你必须知道的.NET]第二十六回:认识元数据和IL(下)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Python将大量遥感数据的值缩放指定倍数的方法(推荐)

《Python将大量遥感数据的值缩放指定倍数的方法(推荐)》本文介绍基于Python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处理,并将所得处理后数据保存为新的遥感影像... 本文介绍基于python中的gdal模块,批量读取大量多波段遥感影像文件,分别对各波段数据加以数值处

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

Oracle数据库使用 listagg去重删除重复数据的方法汇总

《Oracle数据库使用listagg去重删除重复数据的方法汇总》文章介绍了在Oracle数据库中使用LISTAGG和XMLAGG函数进行字符串聚合并去重的方法,包括去重聚合、使用XML解析和CLO... 目录案例表第一种:使用wm_concat() + distinct去重聚合第二种:使用listagg,

Python实现将实体类列表数据导出到Excel文件

《Python实现将实体类列表数据导出到Excel文件》在数据处理和报告生成中,将实体类的列表数据导出到Excel文件是一项常见任务,Python提供了多种库来实现这一目标,下面就来跟随小编一起学习一... 目录一、环境准备二、定义实体类三、创建实体类列表四、将实体类列表转换为DataFrame五、导出Da

Python实现数据清洗的18种方法

《Python实现数据清洗的18种方法》本文主要介绍了Python实现数据清洗的18种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录1. 去除字符串两边空格2. 转换数据类型3. 大小写转换4. 移除列表中的重复元素5. 快速统

Python数据处理之导入导出Excel数据方式

《Python数据处理之导入导出Excel数据方式》Python是Excel数据处理的绝佳工具,通过Pandas和Openpyxl等库可以实现数据的导入、导出和自动化处理,从基础的数据读取和清洗到复杂... 目录python导入导出Excel数据开启数据之旅:为什么Python是Excel数据处理的最佳拍档

在Pandas中进行数据重命名的方法示例

《在Pandas中进行数据重命名的方法示例》Pandas作为Python中最流行的数据处理库,提供了强大的数据操作功能,其中数据重命名是常见且基础的操作之一,本文将通过简洁明了的讲解和丰富的代码示例,... 目录一、引言二、Pandas rename方法简介三、列名重命名3.1 使用字典进行列名重命名3.编

Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南

《Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南》在日常数据处理工作中,我们经常需要将不同Excel文档中的数据整合到一个新的DataFrame中,以便进行进一步... 目录一、准备工作二、读取Excel文件三、数据叠加四、处理重复数据(可选)五、保存新DataFram