运行 Microsoft 中间语言 (MSIL) 之前,必须先根据公共语言运行库将其编译为适合目标计算机体系结构的本机代码。.NET Framework 提供了两种方式来执行此类转换:
使用实时编译器进行编译
在应用程序运行时,JIT 编译可以在加载和执行程序集内容的过程中根据需要将 MSIL 转换为本机代码。由于公共语言运行库为所支持的每种
CPU 体系结构都提供了一个 JIT 编译器,因此开发人员可以生成一组可在具有不同的计算机体系结构的不同计算机进行 JIT 编译和运行的
MSIL 程序集。但是,如果托管代码调用特定于平台的本机 API 或特定于平台的类库,则将只能在特定的操作系统上运行。
JIT 编译考虑了在执行过程中某些代码可能永远不会被调用的事实。它不是耗费时间和内存将可移植可执行 (PE) 文件中的所有 MSIL
都转换为本机代码,而是在执行期间根据需要转换 MSIL
并将生成的本机代码存储在内存中,以供该进程上下文中的后续调用访问。在加载并初始化类型时,加载程序将创建存根 (stub)
并将其附加到该类型的每个方法中。当首次调用某个方法时,存根 (stub) 会将控制权交给 JIT 编译器,后者会将该方法的 MSIL
转换为本机代码,并修改存根 (stub) 以使其直接指向生成的本机代码。这样,对 JIT 编译的方法的后续调用将直接转到该本机代码。
使用 NGen.exe 的安装时代码生成
由于 JIT 编译器会在调用程序集中定义的单个方法时将该程序集的 MSIL
转换为本机代码,因而必定会对运行时的性能造成影响。在大多数情况下,这种性能影响是可以接受的。更为重要的是,由 JIT
编译器生成的代码会绑定到触发编译的进程上。它无法在多个进程之间进行共享。为了能在多个应用程序调用或共享一组程序集的多个进程之间共享生成的代码,公
共语言运行库支持一种提前编译模式。此提前编译模式使用本机映像生成器 (Ngen.exe) 将 MSIL 程序集转换为本机代码,其作用与 JIT 编译器极为相似。但是,Ngen.exe 的操作与 JIT 编译器的操作有三点不同:
-
它在应用程序运行之前而不是运行过程中执行从 MSIL 到本机代码的转换。
-
它一次编译一个整个的程序集,而不是一次编译一个方法。
-
它将本机映像缓存中生成的代码以文件的形式持久保存在磁盘上。
代码验证
在将 MSIL 编译为本机代码的过程中,MSIL 代码必须通过验证过程,除非管理员已经建立了允许代码跳过验证的安全策略。验证过程检查
MSIL
和元数据以确定代码是否是类型安全的,这意味着它仅访问它已被授权访问的内存位置。类型安全帮助将对象彼此隔离,因而可以保护它们免遭无意或恶意的破坏。
它还提供了对代码可以可靠地强制安全限制的保证。
运行库使用下列条件来验证代码是否为类型安全:
-
对类型的引用与被引用的类型严格兼容。
-
在对象上只调用正确定义的操作。
-
标识与声称的要求一致。
验证过程中检查 MSIL
代码,尝试确认该代码只能通过正确定义的类型访问内存位置和调用方法。例如,代码不允许以超出内存范围的方式来访问对象。另外,验证过程检查代码以确定
MSIL 是否已正确生成,这是因为不正确的 MSIL
会导致违反类型安全规则。验证过程通过正确定义的类型安全代码集,并且它只通过类型安全的代码。然而,由于验证过程存在一些限制,某些类型安全代码可能无
法通过验证,而某些语言在设计上并不产生可验证的类型安全代码。如果安全策略要求提供类型安全代码,而该代码不能通过验证,则在运行该代码时将引发异常。