初读CLR Via C# 之类型基础(二)程序运行时的关系
在这篇博文中,可能要说的东西比较难,笔者本来准备前天就进行更新的,但是反复的斟酌一直找不到合适的语句去表述,也可能笔者自己理解的也不是很透彻,所以,在这篇博文中,有不对的地方,望广大网友指出,并给予纠正,大家共同进步嘛,下面开始。
在我们写完一个程序模块的时候,在提交给PM进行检验的时候,自己都会先运行一下找一些BUG进行测试,并改正。但是,在我们运行调试模块的时候,CLR到底是怎么样进行加载程序的呢?
当CLR开始加载一个进程之后,在进程中会有很多线程,当一个线程创建的时候,CLR会自动分配1MB的堆栈空间,并把当前方法所需要的参数、方法内部定义的局部变量进行储存,并负责传递下一个方法的参数。当JIT编译该方法为本地CPU指令前,CLR要确保该方法中定义的所有程序集都加载到当前的APPDomain中,并将该方法中的所有局部变量进行一个初始化的操作(将变量初始化为null或者0)然后,CLR利用程序集的元数据来提取类型的信息。说到这里,就又引出一些知识,CLR是如何知道这些类型对象里面有什么方法和属性的呢?在我们进行编译的时候,没一个类型都对应了三张表,在下只介绍元数据定义表,因为篇幅太长
元数据定义表:
在编译器编译源代码时,代码中的任何一样都会在这个表中插入一条相应的记录。该表中包含:
1、 ModuleDef:包含模块的标示记录,包括文件名和扩展名,并由GUID生成的一个ID;
2、 TypeDef:包含模块中任意一个定义的类型名称、基类、标示(public\private等),以及指向MethodDef、FieldDef、ParamDef、PropertyDef以及EventDef的索引;
3、 MethodDef:包含模块中定义的方法的名称、标示(private public等)以及指向ParamDef的索引;
4、 FieldDef:包含模块中定义的每个字段的名称、标示(public\private等)、类型;
5、 ParamDef:包含模块中定义的每个参数的名称、标示(in\out\ref)、类型;
6、 PropertyDef:包含模块中定义的每个属性的名称、标示、类型;
7、 EventDef:包含模块中定义的每个时间的名称和标识
这就是元数据定义表的大概的一个组成部分。还有其他两张表,在这里就不一一介绍了。
笔者认为CLR就是通过这张表中,找到对象引用需要的所有程序集的元数据的并进行加载、压入到堆栈空间中并执行的。那么具体执行的是怎么一回事呢?由于笔者怕自己的理解有误,画出的图更不敢粘贴在博文中,怕误导,所以拿出原书中的图,笔者将在图下写出笔者的见解,望各位见谅!首先我们假设有两个类,如下:
www.zzzyk.com
internal class Employee
{
public Int32 GetYearsEmploye()
{
return -1;
}
public virtual String GenProcessReport()
{
return string.Empty;
}
public static Employee LockUp(String name)
{
return null;
}
}
internal class Manager : Employee
{
public override string GenProcessReport()
{
return base.GenProcessReport();
}
}
假设当Windows进程启动之后,CLR也已经加载需要的程序集元数据并且也对堆栈进行了初始化并对当前线程分配了1MB的堆栈空间。如图:
大家看到的线程堆栈中有阴影的部分,我们可以理解为该线程已经执行了部分代码,马上要开始执行M3方法。根据CLR的运行机制,首先需要JIT将M3方法编译成本地的CPU指令,与此同时,CLR要确保APPDomain中已经加载了M3方法中的所有类型。然后通过元数据定义表找到M3需要的所有相关信息,并在堆上面构建一个数据表来表示Employee、Manager类型的对象,如图:
这个时候,CLR确定方法所需要的所有类型都已经创建好之后,并M3方法的代码编译完成之后,准许执行代码。在执行代码的过程中,CLR都会自动初始化对象的成员,并初始化对象实例的字段为null或者为0,这些都做完之后,才会执行我们在代码中所写的构造函数(在上一篇文章中,我给大家介绍了如何创建对象实例)如图:
上图表示的是已经调用了Employee的构造器,并在堆中创建实例,类型对象指针返回对象内存地址。
那么接下来,我们代码需要执行一个静态方法为“Lookup”,当这个方法执行时,CLR会定位到静态方法所属的类型,然后通过JIT进行编译(如果JIT已经编译好则无需再次编译)。Lockup方易做图在内部再次创建一个新的Manager对象,返回该对象的地址,并由e局部变量进行保存。如图:
在这个,e保存的对象地址不再是之前的Manager对象的地址,这个时候的地址为Employee的对象地址,而之前的Manager对象不再有对象进行引用,等待GC进行回收,而且它成为GC回收的主要对象。在执行到year=e.GetYearsEmployed();方法的时候,CLR同样要找到该方法的记录项(笔者认为在元数据定义表中)然后JIT进行编译(如果已经编译过JIT无需再次编译)并返回数据给year变量,如图:
最后一步,当我们运行到e.GetProgressReport()这个方法的时候,CLR将根据方法查找相应的对象,这个时候e变量又指向Manager对象,因为在Employee对象中的GetProgressReport()虚方法的最终实现在Manager类中。CLR同样要找到该方法的记录项(笔者认为在元数据定义表中)然后JIT进行编译(如果已经编译过JIT无需再次编译)。
至此,关于程序运行时的关系,是笔者的见解,如有错误的地方,请各位园友之处,并给予纠正。谢谢!
作者 LouisLee
补充:软件开发 , C# ,