前面提到, 托管的程序集包含着metadata和中间语言(IL), IL是一个独立于CPU的机器语言, 是微软与几家外部商业和学术的语言/编译器作者协商之后开发的. IL是比绝大多数CPU机器语言高级的语言, IL能够访问和操作对象类型, 能够创建和初始化对象, 调用对象的虚函数, 能直接操作数组元素, 它甚至还能为错误处理抛出和捕获异常. 你可以把IL想象为一个面向对象的机器语言.
通常, 开发者编写高级语言, 例如C#,C++/CLI, 或者VB. 为这些高级语言工作的编译器将产生IL. 然而, 像其它机器语言, IL可以用汇编语言来编写, 微软确实提供了一个IL汇编器, ILAsm.exe, 还提供了一个IL反汇编器ILDasm.exe.
记住任何层次的语言很可能只暴漏出CLR提供的一部分功能. 然而, IL汇编语言允许开发者访问所有的CLR功能. 因此, 如果你选择的编程语言隐藏了部分CLR的功能, 你可以用IL汇编语言编写那部分代码, 或者利用其他具有你需要的CLR功能的编程语言.
获得CLR提供的功能的唯一方式是阅读CLR文档, 在本书, 我尽力关注CLR功能, 以及如何被C#语言暴露的(或没有暴露). 我怀疑其他书籍或者文章只是通过一种语言来阐述CLR的功能, 使得大多数开发者理解CLR只提供了那种语言所对应的那部分功能. 只要你的语言允许你完成你想做的事情, 那么这种模糊的看法也不是件坏事.
重要: 我认为这种方便在语言之间进行切换和切换的能力是CLR一个很强大的功能. 不幸的是, 我也认为开发者将会更加忽视这个特性. 像C#和VB这样的编程语言对于执行I/O操作是非常优秀的语言. APL对于执行高级工程或商务计算是非常优秀的语言. 通过CLR, 你可以用C#编写你的应用程序的I/O操作部分, 用APL编写工程计算的部分. CLR提供了这些语言之间集成的层次, 这是前所未有的技术, 这使得混合语言编程值得许多开发项目考虑.
为了执行一个函数方法, 它的IL必须被转换成native CPU指令, 这是CLR JIT(just-in-time)编译器的工作.
下图给出了一个函数方法第一次被调用所发生的事情:
在执行Main函数之前, CLR检测被Main的代码所引用的所有类型, 这导致CLR分配内部的数据结构用于管理被应用类型的访问. 在上图中, Main函数引用一个类型, Console, 导致CLR分类一个内部结构, 这个内部数据结构为Console类型定义的每个方法包含一个条目, 每个条目包含方法实现的地址. 当初始化这个结构时, CLR设置这些条目到一个内部的没有文档描述的函数(CLR内部), 我们称这个函数为JIPCompiler.
当Main函数第一次调用WriteLine时, JITCompiler函数被调用, JIPCompiler函数负责编译一个函数的IL为native CPU指令. 因为IL是在”just in time”时编译的, 这个CLR组件也常称为JITter或者JIT Compiler.
注意: 如果应用程序运行在x86版本的Windows或者WoW64上, JIT Compiler将产生x86指令, 如果应用程序以64位应用程序运行在x64或者IA64版本的Windows上, JIT Compiler将产生x64或者IA64指令.
在调用时, JITCompiler函数知道正在调用的是什么函数, 知道这个函数的类型. JITCompiler函数然后在程序集的metadata中搜索被调用方法的IL. 然后JITCompiler验证和编译IL代码为native CPU指令, native CPU指令被保存在动态分类的内存块中. 然后JITCompiler返回到被调用函数的条目上(在CLR创建的类型的内部数据结构), 并替换那个引用为包含刚才编译为native CPU指令的内存块的地址. 最后, JITCompiler函数跳到内存块的代码处, 这个代码就是函数WriteLine的实现(接受String参数的版本). 当这个代码返回时, 它返回到Main中, 然后继续正常执行.
Main现在第二次调用WriteLine函数, 这次, WriteLine的代码已经被验证和编译了, 因此调用直接转到内存块, 完全跳过了JITCompiler函数, 当WriteLine函数执行完之后, 它就返回到Main函数. 下图给出了第二次调用WriteLine函数的过程:
函数只有在第一次调用时有性能上的损失, 随后的调用是全速执行native代码, 因为不需要对native代码进行验证和编译.
JIT compiler将native CPU指令存储在动态内存中, 这意味着被编译的代码在程序终止时将被抛弃, 因此如果你再次运行应用程序, 或者同时运行两个应用程序实例(在两个不同的操作系统进程), JIT compiler将再次将IL编译为native指令.
对大多数应用程序来说, 由JIT编译所导致的性能下降不是很重要, 多数应用程序常常是重复调用相同的函数, 这些函数在应用程序执行时只有一次性能上的下降, 容易看出花在函数内部的时间比调用的时间长的多.
你应该也注意到CLR的JIT编译器优化了native代码, 这类似于非托管的C++编译器在后端所作的工作, 它可能需要更多的时间来产生优化的代码, 但是优化过的代码比没有被优化的代码执行的更快.