托管代码,何为托管代码?简单来说就是需要.net framework环境支持,需要CLR支持,当源代码被对应语言的编译器编译为托管代码后,接下来由CLR来对托管代码进行再次编译。ClR的核心功能包括 内存关联,程序集加载,安全性,异常处理和线程同步等,这些功能可由面向CLR所有的编程语言使用,在运行时,CLR不关心开发人员所使用的是哪一种语言来写源代码,这方便了开发人员在选择开发语言的时候,有更多的选择,使开发人员不在关注各语言之间差异的细节
托管模块以及程序集
各语言对应各自的编译器,我们可以将编译器理解为语法检查器,负责检查源代码,确保我们写的代码合法并有意义,编译后的为一个托管模块。托管模块是一个标准的32位Microsoft Windows可移植体执行文件,或者是一个标准的64位Windows可移植执行体(PE32+)文件,他们都需要CLR才能执行,托管的程序集总是利用Windows 的数据执行保护和地址空间布局随机化,这两个功能旨在增强系统的安全性,无论选择哪种编程语言,经过面向CLR的编译器编译后,生成的托管模块是相同的。
托管模块包括PE32(+)头、CLR头、元数据和IL(中间语言)代码
PE32(+):这个头中标识了文件类型,包括GUI、CUI或者DLL,并且包含了一个时间标记来表明文件生成的时间,对于只包括IL代码的模块,PE32(+)头的大多数信息会被忽视,对于包括本地CPU代码的模块,这个头包含了与本地CPU代码有关的信息
CLR头:头中包含了需要的CLR版本,一些flag,托管模块入口方法(Main)的MethodDef元数据标记,以及模块的元数据、资源、强名称、一些flag以及其他不太重要的数据项的位置大小
元数据:每个托管模块都包含元数据表,主要有两种类型的表:1.一种类型的表描述源代码中定义的类型和成员2.另一种类型的表描述源代码引用的类型和成员
IL(中间语言)代码:编译器编译源代码时候生成的代码,在运行时,CLR会将IL编译成本地CPU指令
元数据总是与包含的IL代码密不可分,事实上,元数据总是嵌入和代码相同的DLL\EXE文件中,由于编译器同时生成元数据和代码,把他们绑定一起,并嵌入最终生成的托管代码,所以无需担心元数据和IL代码的同步性
元数据的用途
1. 编译时,元数据消除了对本地C/C++头和库文件的需求,因为在负责实现类型/成员的IL代码文件中,已包含和引用的类型/成员有关的全部信息。编译器可以直接从托管代码中读取元数据
2. Visual Studio的智能提示,可以解析元数据,指出一个类型提供了哪些方法、属性、事件和字段。如果是一个方法还能提示该方法需要什么参数,这里就是反射与元数据的关系了吧
3. 元数据允许将一个对象的字段序列化到一个内存块中,将其发送给另一台电脑,然后反序列化,在远程机器上重建对象状态
4. 元数据允许垃圾回收启跟踪对象的生存期。垃圾回收器能判断任何对象的类型,并从元数据知道那个对象中的哪些字段引用的其他对象
5. 程序集是一个抽象的概念,它是由一个或多个模块/资源文件的逻辑性分组,其次程序集是重用、安全性以及版本控制的最小单元。
6. 加载公共语言运行时和执行程序集代码流程
加载公共语言运行时和执行程序集代码
1.Windows 检查好PE32文件头后,决定创建32位,64位还是WOW64进程之后,会在进程的地址空间中加载MSCoreEE.dll的x86、x64或IA64版本,然后进程的主线程调用MSCoreEE.dll中定义的一个方法,这个方法初始化了CLR,加载EXE程序集,然后调用其入口方法(Main),
2.在Main方法执行之前,CLR会检测出Main的代码引用的所有类型,CLR会分配一个内部数据结构,它用于管理对所有引用的类型访问,在内部数据结构中,代码所引用的类型定义的每一个方法都会找到一个对应的记录项,每个记录项都容纳了一个地址,根据此地址即可找到方法的实现(对于这个结构进行初始化时,CLR将每个记录项都设置成包含在CLR内部的一个未文档话的函数,我们将这个函数称为JITCompiler).
3.JITCompiler函数被调用时,它知道要调用的是哪个方法,以及具体的是什么类型定义了方法,然后JITCompiler会在定义该类型的程序集的元数据中查找被调用该的方法的IL,接着,JITCompiler验证IL代码,并将IL代码编译成本地CPU指令。本地CPU指令被保存到一个动态分配的内存块中,然后JITCompiler返回CLR为类型创建的内部数据结构,找到与被调用的方法对应的那条记录,修改最初对JITCompiler的引用,让它现在指向内存块(其中包含了刚才编译好的本地CPU指令)的地址。
4.最后JITCompiler函数跳转到内存块中的代码,这些代码是被调用的方法的具体实现,当代码执行完毕后并返回时,会返回到Main代码中,并向往常一样继续执行。
当再次调用该方法时,由于已经对该方法的代码进行了验证和编译,所以会直接执行内存块中的代码,跳过JITCompiler函数。一个方法只有在首次调用时才会产生一些性能损失,以后对该方法的调用都是以本地代码的形式全速运行,无需重新验证IL并编译成本地代码
JIT编译器将本地CPU指令存储到动态内存中,一旦应用程序终止,所有的编译好的代码都会被丢弃,所以,如果将来再次运行应用程序或者同时启动应用程序的两个实例,JIT编译器必须重新将IL编译成本地指令
JIT编译器在将IL转换为本地指令的过程中,会根据执行环境对生成的本地指令进行优化,所以没必要在意JITCompiler造成的性能损失,可以这样认为不同环境(软件或者硬件)生成的cpu指令是不同的
IL和验证,IL最大的优势不在于它对底层CPU的抽象,它最大的优势应该是增加了程序的健壮性和安全性,在将IL转换为本地指令过程中,CLR会执行一个名为验证的过程。这个过程中会检查高级IL代码,确定代码所做的一切都是安全的,例如,会验证每个调用的方法是否都有正确数量的参数、正确的类型,每个方法的返回值都得到了正确的使用,每个方法都有返回语句,等等。。。
本地代码生成器(NGen.exe)
使用NGen工具,可以在一个应用程序安装到用户的计算机上时,将本地IL代码编译成本地代码,相当于是一个预编译的过程,这样就不要在运行时编译IL代码,这有助于提升应用程序的性能,Ngen.exe在以下两种情况能够发挥重要作用
1. 加快应用程序的启动速度,由于代码已经被编译成本地代码,故运行时不需要时间对IL编译
2. 减少应用程序的工作集,如果一个应用程序同时加载到多个进程中,对该程序集运行NGen.exe,则可以减少应用程序的工作集。NGen.exe会将IL编译成本地代码,并将这些代码保存到一个独立的文件中,这个文件通过内存映射的方式,同时映射到多个进程地址空间中,使代码得到了共享,避免每个进程都需要一份单独的代码拷贝
NGen.exe存在的问题
1. 没有知识产权保护 2. NGen生成的文件可能失去同步 3. 较差的执行时性能
原文:http://www.cnblogs.com/mengao89/p/3682715.html