C#托管执行过程

[来源] 达内    [编辑] 达内   [时间]2012-10-23

为获得公共语言运行时 (CLR) 提供的优点,必须使用一个或多个针对运行时的语言编译器,如 Visual Basic、C#、Visual C++、F# 或许多第三方编译器(如 Eiffel、Perl 或 COBOL 编译器)中的一个

 

为获得公共语言运行时 (CLR) 提供的优点,必须使用一个或多个针对运行时的语言编译器,如 Visual Basic、C#、Visual C++、F# 或许多第三方编译器(如 Eiffel、Perl 或 COBOL 编译器)中的一个。

由于运行时是一个多语言执行环境,因此它支持各种数据类型和语言功能。  您所用的语言编译器将确定可用的运行时功能,而您将使用这些功能设计代码。  编译器(而不是运行时)建立代码必须使用的语法。  如果您的组件必须完全能够被用其他语言编写的组件使用,您的组件的导出类型必须只公开公共语言规范 (CLS) 中包括的语言功能。   可以使用CLSCompliantAttribute 特性以确保代码符合 CLS。  有关更多信息,请参见 编写符合 CLS 的代码

返回页首

 

当编译为托管代码时,编译器将源代码翻译为 Microsoft 中间语言 (MSIL),这是一组可以有效地转换为本机代码且独立于 CPU 的指令。  MSIL 包括用于加载、存储和初始化对象以及对对象调用方法的指令,还包括用于算术和逻辑运算、控制流、直接内存存取、异常处理和其他操作的指令。  要使代码可运行,必须先将 MSIL 转换为特定于 CPU 的代码,这通常是通过实时 (JIT) 编译器来完成的。   由于公共语言运行时为它支持的每种计算机结构都提供了一种或多种 JIT 编译器,因此同一组 MSIL 可以在所支持的任何结构上 JIT 编译和运行。

当编译器产生 MSIL 时,它也产生元数据。 元数据描述代码中的类型,包括每种类型的定义、每种类型的成员的签名、代码引用的成员和运行时在执行时使用的其他数据。 MSIL 和元数据包含在一个可移植可执行 (PE) 文件中,此文件基于并扩展过去用于可执行内容的已发布 Microsoft PE 和通用对象文件格式 (COFF)。  这种文件格式包含 MSIL 或本机代码以及元数据,使得操作系统能够识别公共语言运行时映像。  文件中的元数据以及 MSIL 的存在使代码能够描述自身,这意味着不再需要类型库或接口定义语言 (IDL)。  运行时在执行过程中根据需要从该文件中查找并提取元数据。

返回页首

 

运行 Microsoft 中间语言 (MSIL) 之前,必须先根据公共语言运行时将其编译为适合目标计算机体系结构的本机代码。  .NET Framework 提供了两种方式来执行此类转换:

  • .NET Framework 实时 (JIT) 编译器。

  • .NET Framework Ngen.exe(本机映像生成器)

k5532s8a.collapse_all(zh-cn,VS.110).gif使用 JIT 编译器进行编译

在应用程序运行时,JIT 编译可以在加载和执行程序集内容的过程中根据需要将 MSIL 转换为本机代码。 由于公共语言运行时为所支持的每种 CPU 体系结构都提供了一个 JIT 编译器,因此开发人员可以生成一组可在具有不同的计算机体系结构的不同计算机进行 JIT 编译和运行的 MSIL 程序集。  但是,如果托管代码调用特定于平台的本机 API 或特定于平台的类库,则将只能在该操作系统上运行。

JIT 编译考虑了在执行过程中某些代码可能永远不会被调用的可能性。 它不是耗费时间和内存将 PE 文件中的所有 MSIL 都转换为本机代码,而是在执行期间根据需要转换 MSIL 并将生成的本机代码存储在内存中,以供该进程上下文中的后续调用访问。  在加载并初始化类型时,加载程序将创建存根 (stub) 并将其附加到该类型的每个方法中。  当首次调用某个方法时,存根 (stub) 会将控制权交给 JIT 编译器,后者会将该方法的 MSIL 转换为本机代码,并修改存根 (stub) 以使其直接指向生成的本机代码。  这样,对 JIT 编译的方法的后续调用将直接转到该本机代码。

k5532s8a.collapse_all(zh-cn,VS.110).gif使用 NGen.exe 的安装时代码生成

由于 JIT 编译器会在调用程序集中定义的单个方法时将该程序集的 MSIL 转换为本机代码,因而必定会对运行时的性能产生不利影响。  在大多数情况下,这种性能降低是可以接受的。  更为重要的是,由 JIT 编译器生成的代码会绑定到触发编译的进程上。  它无法在多个进程之间进行共享。 为了能在多个应用程序调用或共享一组程序集的多个进程之间共享生成的代码,公共语言运行时支持一种提前编译模式。 此提前编译模式使用Ngen.exe(本机映像生成器) 将 MSIL 程序集转换为本机代码,其作用与 JIT 编译器极为相似。  但是,Ngen.exe 的操作与 JIT 编译器的操作有三点不同:

  • 它在应用程序运行之前而不是在应用程序运行过程中执行从 MSIL 到本机代码的转换。

  • 它一次编译一个整个程序集,而不是一次编译一个方法。

  • 它将本机映像缓存中生成的代码以文件的形式持久保存在磁盘上。

k5532s8a.collapse_all(zh-cn,VS.110).gif代码验证

在编译为本机代码的过程中,MSIL 代码必须通过验证过程,除非管理员已经建立了允许代码跳过验证的安全策略。  验证过程检查 MSIL 和元数据以确定代码是否是类型安全的,这意味着它仅访问已授权访问的内存位置。   类型安全帮助将对象彼此隔离,因而可以保护它们免遭无意或恶意的破坏。  它还提供了对代码可以可靠地强制安全限制的保证。

运行时使用下列条件来验证代码是否为类型安全:

  • 对类型的引用与被引用的类型严格兼容。

  • 在对象上只调用正确定义的操作。

  • 标识与声称的要求一致。

验证过程中检查 MSIL 代码,尝试确认该代码只能通过正确定义的类型访问内存位置和调用方法。  例如,代码不允许以超出内存范围的方式来访问对象。   另外,验证过程检查代码以确定 MSIL 是否已正确生成,这是因为不正确的 MSIL 会导致违反类型安全规则。  验证过程通过正确定义的类型安全代码集,并且它只通过类型安全的代码。  然而,由于验证过程存在一些限制,某些类型安全代码可能无法通过验证,而某些语言在设计上并不产生可验证的类型安全代码。   如果安全策略要求提供类型安全代码,而该代码不能通过验证,则在运行该代码时将引发异常。

返回页首

 

公共语言运行时提供使托管代码执行能够发生以及可在执行期间使用的各种服务的基础结构。  在运行方法之前,必须先将其编译为特定于处理器的代码。  当首次调用已经为其生成 MSIL 的每个方法,然后运行该方法时,该方法将是 JIT 编译的。  下次运行该方法时,将运行现有的 JIT 编译的本机代码。   这种进行 JIT 编译然后运行代码的过程一直重复到执行完成时为止。

在执行过程中,托管代码接收若干服务,这些服务涉及垃圾回收,安全性,与非托管代码的互操作性,跨语言调试支持,增强的部署,以及版本控制支持等。

在 Microsoft Windows XP 和 Windows Vista 中,操作系统加载程序通过检查 COFF 头中的某个位来检查托管模块。  所设置的位表示托管模块。 如果加载程序检测到托管模块,它将加载 mscoree.dll,当加载和卸载托管模块映像时,_CorValidateImage   _CorImageUnloading  将通知加载程序。  _CorValidateImage  执行下列操作:

  1. 确保代码是有效的托管代码。

  2. 将映像中的入口点更改为运行时中的入口点。

在 64 位的 Windows 上,_CorValidateImage 会修改内存中的映像,将其从 PE32 格式转换为 PE32+ 格式。

资源下载