[你必须知道的.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

相关文章

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

C#利用Free Spire.XLS for .NET复制Excel工作表

《C#利用FreeSpire.XLSfor.NET复制Excel工作表》在日常的.NET开发中,我们经常需要操作Excel文件,本文将详细介绍C#如何使用FreeSpire.XLSfor.NET... 目录1. 环境准备2. 核心功能3. android示例代码3.1 在同一工作簿内复制工作表3.2 在不同

C#使用iText获取PDF的trailer数据的代码示例

《C#使用iText获取PDF的trailer数据的代码示例》开发程序debug的时候,看到了PDF有个trailer数据,挺有意思,于是考虑用代码把它读出来,那么就用到我们常用的iText框架了,所... 目录引言iText 核心概念C# 代码示例步骤 1: 确保已安装 iText步骤 2: C# 代码程

Pandas处理缺失数据的方式汇总

《Pandas处理缺失数据的方式汇总》许多教程中的数据与现实世界中的数据有很大不同,现实世界中的数据很少是干净且同质的,本文我们将讨论处理缺失数据的一些常规注意事项,了解Pandas如何表示缺失数据,... 目录缺失数据约定的权衡Pandas 中的缺失数据None 作为哨兵值NaN:缺失的数值数据Panda

C++中处理文本数据char与string的终极对比指南

《C++中处理文本数据char与string的终极对比指南》在C++编程中char和string是两种用于处理字符数据的类型,但它们在使用方式和功能上有显著的不同,:本文主要介绍C++中处理文本数... 目录1. 基本定义与本质2. 内存管理3. 操作与功能4. 性能特点5. 使用场景6. 相互转换核心区别

python库pydantic数据验证和设置管理库的用途

《python库pydantic数据验证和设置管理库的用途》pydantic是一个用于数据验证和设置管理的Python库,它主要利用Python类型注解来定义数据模型的结构和验证规则,本文给大家介绍p... 目录主要特点和用途:Field数值验证参数总结pydantic 是一个让你能够 confidentl

JAVA实现亿级千万级数据顺序导出的示例代码

《JAVA实现亿级千万级数据顺序导出的示例代码》本文主要介绍了JAVA实现亿级千万级数据顺序导出的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 前提:主要考虑控制内存占用空间,避免出现同时导出,导致主程序OOM问题。实现思路:A.启用线程池

在.NET项目中嵌入Python代码的实践指南

《在.NET项目中嵌入Python代码的实践指南》在现代开发中,.NET与Python的协作需求日益增长,从机器学习模型集成到科学计算,从脚本自动化到数据分析,然而,传统的解决方案(如HTTPAPI或... 目录一、CSnakes vs python.NET:为何选择 CSnakes?二、环境准备:从 Py

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性