当运行一个.NET应用程序时,OS会先建立一个进程,检查PE文件,执行PE文件中的.text段中的code, 在托管程序编译时,.text段里面增加了一条JMP_CorExeMain或JMP_CorDllMain指令(根据是exe文件还是dll文件不同)。若是非托管程序,就进入到入口函数了,若是托管程序就会跳入到另一个函数中。这个函数在一个叫做MSCorEE.dll的动态链接库文件中,当安装了.net框架时就会被复制在系统目录下。系统会根据托管程序PE文件中的信息找到这个DLL,然后通过MSCorEE.dll的PE文件信息找到这个_CorExeMain函数的入口地址,然后修改刚才的JMP指令要跳转的地址,从而将控制跳转到了_CorExeMain这个函数里面去。然后,在这个函数里面,CLR被启动了,并做了若干的初始化工作,在加载CLR时会创建一个默认的AppDomain,它是CLR的运行单元,程序的Main方法就是在这里执行,这个默认的AppDomain是唯一且不能被卸载的,当该进程消灭时,默认AppDomain才会随之消失。加载后,再通过托管程序的CLR表头找到托管程序的入口地址,并将控制跳转到这里,于是托管程序开始运行。
托管程序编译的结果是IL中间代码,且IL代码是由CLR实施编译的。实际上,IL中的方法并不是每次被调用时都会被JIT Complier重新编译一次,而是采用“LazyLoad”,只有在第一次被调用的时候才会被编译。即时编译器保存有一个映射表。当调用一个方法时,即时编译器如果发现在这个映射表中没有标记这个方法,就会将这个方法的IL代码编译成CPU指令,然后分配在一个内存空间上,然后在这个映射表中记录下这个方法名和方法入口对应的内存地址,然后通过JMP指令跳转到函数中去。当下次再产生对这个方法的调用时,即时编译器因为已经知道了这个方法对应的内存地址,因此就会直接通过JMP指令跳转,而不会再次编译这段代码。