1.1、将源代码编译成托管模块
1.CLR(Common Language Runtime)面向多种编程语言,提供内存管理、程序集加载、安全性、异常处理和线程安全等核心功能。
2.过程:源代码文件–> 编译器检查语法和分析源代码–>托管模块(managed module)
3.托管模块组成
(1) PE32或PE32+头——Windows PE文件头,其中PE32+只能在WIN64上运行。
标识了文件类型(GUI、CUI、DLL)
包含一个时间标记(文件的生成时间)
如果是包含本机(native)CPU代码的模块,这个头含与本机CPU代码有关的信息
(2) CLR头
包含要求的CLR版本
一些flag标志
托管模块Main方法的MethodDef元数据token
模块的元数据、资源、强名称、一些标志以及一些不重要的数据项的位置/大小
(3) 元数据——数据表的集合
源代码中定义的类型和成员的元数据表、以及引用的类型元数据表
(4) IL代码——编译器编译源代码生成的代码
在运行时,CLR(需要安装.NET Framework)将IL编译成本机CPU指令
特殊:Microsoft的C++编译器默认生成包含 native 的exe/dll 文件,可以通过指定/CLR命令行开关生成包含托管代码的模块。
1.2、将托管模块合并成程序集
Assembly:重用、安全性、版本控制的最小单元。程序集是自描述的,在程序集的模块中包含了引用的程序集的相关信息(包括版本号),不需要在注册表或Active Directory Domain Services中保存额外信息。所以与非托管组件代码相比,程序集更容易部署。
1.3、加载公共语言运行时
检查系统目录中是否存在 mscoree.dll 以确定安装了.NET Framework:
Windows通过检查exe文件头,决定创建32位进程还是64位进程,随后在进程地址空间加载相应版本的MSCorEE.dll,然后进程的主线程调用MSCorEE.dll中定义的一个方法初始化CLR,加载EXE程序集,最后调用其入口方法(Main),托管程序启动并运行。
如果是非托管引用程序调用 LoadLibrary 加载托管程序集,Windows会自动加载并初始化CLR以处理程序集中的代码。这个时候进程已经启动并运行了,可能会限制程序集的可用性。
1.4、执行程序集的代码
托管程序集包含元数据和 IL,IL是面向对象的机器语言。Microsoft提供了IL汇编器:ILAsm.exe
和IL反汇编器:ILDasm.exe
CLR是如何执行程序集的代码的?
在Main方法执行之前,CLR会检测出Main的代码引用的所有类型。这导致CLR分配一个内部数据结构来管理对引用类型的访问。
如图,Main方法引用了 Console 类,导致CLR分配一个内部结构。在这个数据结构中,Console类定义的每个方法都有一个对应的entry,每个entry 都含有一个地址,根据此地址可以找到方法的实现。对这个结构初始化时,CLR将每个 entry 设置成 指向包含在CLR内部的一个未编档函数(称为JITCompliler)
Main函数首次调用WriteLine时,JITCompiler函数会被调用,此时,JIT知道它要定义的是哪个类型定义的哪个方法。然后,JIT会在定义该类型的程序集的元数据中查找被调用方法的IL。通过验证(verification)后,JIT将方法的IL代码编译成本机CPU指令(x86、x64、ARM等)并保存到动态分配的内存块中。然后,JIT回到CLR为类型创建的内部数据结构中,修改entry 对JITCompliler的引用,使其指向内存块。最后,JIT跳转执行内存块中的代码,执行完毕后回到Main函数,继续执行Main函数。第二次执行该方法时则会跳过JIT直接执行本机代码。
注意:CLR的JIT编译器会对本机代码进行优化,受 /optimize和 /debug 两个C#编译器开关影响。