CLR的执行模型
一、.将源代码编译成托管模块
简单来说C#编译器(CSC.exe)将源代码生成托管模块,如果项目只有一个托管模块,没有资源(
或数据)文件,那么程序集就是托管模块,而且在生成过程中不需要采取任何额外的步骤。但是,如果是一
系列文件(例如多个托管模块,n个资源文件)合并成一个程序集中,就需要用到了程序集连接器(Al.exe) 托管模块分四部分 1.PE32或PE32+头 2.CLR头 3.元数据 4.IL(中间语言)代码
二、.将托管模块合并成程序集
程序集链接器(AL.exe) 将托管模块合并成程序集
三、.加载公共语言运行时、 涉及工具: *工具CLRVer.exe P8 查询一台机器上安装的所有CLR版本。还能列出机器中正在运行的
进程使用的CLR版本号 当然,以上步骤生成的程序集,最终都是由CLR管理这些程序集中的代码的执行。所以要加载
CLR。要知道是否已经安装.Net Framework ,只需要检查%SystemRoot%\\System32目录中的
MSCorEE.dll文件(具体是哪个目录,请详见P10)。存在该文件,表明已安装。 运行一个可执行文件时,windows会检查这个EXE文件的头, 1,判断应用程序需要的是32为地址空间,还是64位的。 2,检查头中嵌入的CPU架构信息,确保当前计算机的cpu是符合要求的。 检查好EXE文件头后,决定是创建32位、还是64位或WoW64
四、.执行程序集的代码
例如:控制台应用程序Main方法中只有两行代码 Console.WriteLine("Hello"); Console.WriteLine("World"); 1.CLR分配一个内部结构
在Main方法执行之前,CLR会检测Main的代码引用的所有类型。这导致CLR分配一个内部数据
结构,它用于管理对所有引用类型的访问。由于Main方法引用了一个Console类型,这导致了CLR分配一
个内部结构。在这个内部数据结构中,Console类型定义的每个方法都有一个对应的记录项。每个记录项都
容纳了一个地址,根据此地址即可找到方法的实现 2.对结构进行初始化 对这个结构进行初始化时,CLR将每个记录项都设置成(指向)包含在CLR内部的一个未文档化的
函数。这个函数就是JITCompiler,即即时编译器。 //JIT编译器的执行原理 这样,JITCompiler函数被调用时,它知道要调用的是哪个方法,以及具体是什么类型定义了该
方法。然后,JITCompiler会在定义(该类型的)程序集的元数据中查找被调用方法的IL。接着,JITCOmpiler验证IL代码,并将IL代码编译成本地的CPU指令。本地CPU指令被保存到一个动态分配的内存块中。然后,JITCompiler返回CLR为类型创建的内部数据结构,找到与被调用方法对应的那条记录,修改最初对JITCompiler的引用,让他现在指向内存块(其中包含了刚才编译好的本地CPU指令)的地址。最后,JITCompiler函数跳转到内存块中的代码。这些代码正是WriteLine方法(获取单个string参数的那个版本)的具体实现。这些代码执行完毕并返回时,会返回到Main中的代码,并像往常一样继续执行。 3.执行JITCompiler Main方法首次调用WriteLine时,JITCompiler函数会被调用。JITCompiler函数负责将一个方法的IL代码编译成本地CPU指令。 4.现在,Main要第二次调用WriteLine。 这一次,由于已对WriteLine的代码进行了验证和编译,所以会直接执行内存块中的代码,完全
跳过JITCompiler函数。WriteLine方法执行完毕后,会再次返回Main。
*JIT编译器将本地CPU指令存储到动态内存中。一旦应用程序终止,编译好的代码也会被丢弃。