C++【CLR via C#】CSC将源代码编译成托管模块

      下图展示了编译源代码文件的过程。如图所示,可用支持 CLR
的其它一样种语言创建源代码文件。然后,用一个遥相呼应之编译器检查语法和剖析源代码。无论选用哪一个编译器,结果都是一个托管模块(managedmodule)。托管模块是一个正规的
32 位 Microsoft Windows 可移栽执行体(PE32)文件 6 ,或者是一个正经的 64
位Windows 可移栽执行体(PE32+)文件,它们都亟需 CLR
才能够尽。顺便说一样句,托管的先后集总是利用了 Windows
的数量实施保护(Data Execution
Prevention,DEP)和地址空间布局随机化(Address SpaceLayout
Randomization,ASLR);这点儿个作用旨在增进全体体系的安全性。

C++ 1

托管模块的一些

   PE32 或 PE32+头:专业 Windows PE
文件头,类似于“公共对象文件格式(Common Object File
Format,COFF)”头。如果是腔用 PE32 格式, 文件能在Windows的 32 位或
64 位版本及运行。如果此腔用 PE32+格式,文件只能以 Windows 的 64
位版本及运行。这个腔还标识了文件类型,包括 GUI,CUI 或者
DLL,并带有一个时光标记来指出文件的变动时间。对于只有含 IL
代码的模块,PE32(+)头的大多数信息会于忽略。对于富含本地
CPU代码的模块,这个腔包含了跟本地 CPU 代码有关的音信

  CLR 头:包含使这模块成为一个托管模块的音(可由于 CLR
和一些实用程序进行说明)。头着含有了欲的 CLR 版本,一些
flag,托管模块入口计(Main 方法)的 MethodDef 元数据
token,以及模块的头条数据、资源、强名称、一些 flag
以及另外未极端重要的数额项的岗位/大小

  元数据:每个托管模块都富含元数据表。主要有少种类型的申:一栽档次的表描述源代码中定义的花色及分子,另一样栽类型的表描述源代码引用的种和分子

  IL(中间语言)代码:编译器编译源代码时变的代码。在运转时,CLR
将 IL 编译成本地 CPU指令。

      本地代码编译器(native code compilers)生成的是面向特定 CPU
架构(比如 x86,x64 或 IA64)的代码。相反,每个面向 CLR
的编译器生成的且是 IL(中间语言)代码。IL 代码有时称托管代码,因为 CLR
要保管它的推行。

    除了生成 IL,面向 CLR
的每个编译器还要以每个托管模块中生成完全的首届数据。简单地游说,元数据(metadata)是同等组数据表。其中部分数据表描述了模块中定义之情节,比如类型及其成员。还有局部头数据表描述了托管模块引用的内容,比如导入的型及其成员。元数据是一对镇技术的超集。这些一直技术包括
COM 的“类型库(Type Library)”和“接口定义语言(Interface Definition
Language,IDL)”文件。要留心的是,CLR
元数据远比她完整。另外,和类型库及 IDL 不同,元数据连接跟富含 IL
代码的文件涉及。事实上,元数据连接坐和代码相同之 EXE/DLL
文件被,这如二者密不可分。由于编译器同时生成元数据以及代码,把它绑定一起,并坐最终生成的托管模块,所以最先数据及其讲述的
IL 代码永远不会见错过共。元数据来强用处,下面就列举部分。

  *  编译时,元数据消除了针对本地
C/C++头和库房文件之需求,因为当背负落实项目/成员的 IL
代码文件中,已含和援的色/成员有关的任何信。编译器可一直打托管模块读取元数据。

  *  Microsoft Visual Studio
使用初数据助你勾勒代码。它的“智能感知(IntelliSense)”技术可解析元数据,指出一个档次提供了怎样方法、属性、事件与字段。如果是一个主意,还会指出方法要什么参数。

  *  CLR
的代码验证过程用初数据确保代码只实行“类型安全”的操作。(稍后便会见说话到说明。)。

  *
元数据允许用一个目标的字段序列化到一个舅存块中,将该发送给其他一样玉机器,然后反序列化,在远距离机器及重建对象的状态。

  * 
元数据允许垃圾回收器跟踪对象的生存期。垃圾回收器能判断任何对象的品种,并自首批数据掌握死目标中的什么字段引用了别对象

用托管模块合并成程序集

  CLR
实际不与模块并干活。相反,它是与次集齐坐班的。程序集(assembly)是一个虚无的概念,初家往往非常不便把握它的精粹。首先,程序集是一个或多单模块/资源文件的逻辑性分组。其次,程序集是录取、安全性及版本控制的最为小单元。取决于你对此编译器或工具的取舍,既好生成单文件程序集,
为足以变动多文本程序集。在 CLR 的世界中,程序集相当给一个“组件”。

  下图有助于了解程序集。在即时幅图中,一些托管模块和资源(或数)文件准备及由一个家伙处理。该工具转单独一个
PE32(+)文件来表示文件的逻辑性分组。实际产生的政工是,这个
PE32(+)文件包含一个名为也“清单”(manifest)的数据块。清单是出于正数据表构成的另一样种植集合。这些发明描述了整合程序集的文件,由程序集中之文书贯彻的明导出的种
7 ,以及和程序集关联在一块儿的资源或数据文件。

C++ 2

默认是由于编译器将变的托管模块转换成程序集。换言之,C#编译器生成含有清单的一个托管模块。清单指出程序集才由一个文件构成。

加载公共语言运行时

  你转移的每个程序集既可以是一个而实施应用程序,也可是一个
DLL(其中蕴涵一组由可执行程序使用的类)。当然,最终是由于 CLR
管理这些程序集中之代码的实施。这表示必须于目标机器上安好.NETFramework。

  C#编译器生成的次第集要么包含一个 PE32 头,要么包含一个
PE32+头。除此之外,编译器还会在头被指定要求啊 CPU 架构(如果利用默认值
anycpu,则非显著指定)。Microsoft发布了 SDK 命令行实用程序 DumpBin.exe
和 CorFlags.exe,可用它检查编译器生成的托管模块所措的信息。

尽顺序集的代码

    为了推行一个办法,首先要管其的 IL 转换成本地 CPU 指令。这是 CLR 的
JIT (just-in-time
或者“即经常”)编译器的天职。下图展示了一个智首不好调动用时发生的事务。

C++ 3

     就于 Main 方法执行前,CLR 会检测出 Main
的代码引用的有所种类。这造成 CLR
分配一个里数据结构,它用于管理针对所引用的档次的访。在图被,Main
方法引用了一个 Console 类型,这造成
CLR分配一个内部结构。在是里面数据结构中,Console
类型定义的每个方法还来一个应和之笔录项 10
。每个记录项都容纳了一个地方,根据是地点即可找到方法的实现。对之结构进行初始化时,CLR
将每个记录项都安成(指向)包含在 CLR
内部的一个未文档化的函数。我以之函数称为 JITCompiler。

  JITCompiler
函数被调用时,它掌握如果调用的凡何人方法,以及实际是什么类型定义了该方法。然后,JITCompiler
会在概念(该型的)程序集的初数据被追寻被调用的不二法门的
IL。接着,JITCompiler 验证 IL 代码,并将 IL 代码编译成本地 CPU
指令。本地 CPU 指令给封存至一个动态分配的内存块中。然后,JITCompiler返回
CLR
为品种创建的里边数据结构,找到与被调用的主意对应之那同样长条记下,修改最初对
JITCompiler 的援,让它们本针对内存块(其中富含了才编译好之地头 CPU
指令)的地方。最后,JITCompiler 函数超过反到外存块中之代码。这些代码正是
WriteLine 方法(获取单个 String
参数的老本)的现实性实现。这些代码执行完毕并赶回时,会回到到 Main
中之代码,并和过去相同继续执行。现在,Main 要第二赖调整用
WriteLine。这同样潮,由于曾针对性 WriteLine 的代码进行了印证和编译,所以会见
一直实施外存块中的代码,完全超越了 JITCompiler 函数。WriteLine
方法执行了后,会重返回 Main。
    下图展示了第二次等调动用 WriteLine 时产生的事情。

 

C++ 4

  一个法才生于首浅调整用时才见面促成一部分性能损失。以后对拖欠方式的享有调用都归因于本地代码的样式快速运转,无需再次验证
IL 并将其编译成本地代码。JIT 编译器将地面 CPU
指令存储到动态内存中。一旦应用程序终止,编译好之代码也会叫扔。所以,如果前再度运行应用程序,或者同时开动应用程序的星星个实例(使用有限单不等的操作系统进程),JIT
编译器必须再次用 IL 编译成本地下令。对于大多数应用程序,因 JIT
编译造成的习性损失并无鲜明。大多数应用程序都见面频繁调用相同之点子。
以应用程序运行期间,这些主意就会针对性造成一次性的熏陶。另外,在术中花费的日大有或比花在调用方法上的辰差不多得差不多。还要小心的凡,CLR
的 JIT 编译器会指向当地代码进行优化,这仿佛于非托管
C++编译器的后端所开的劳作。同样地,可能只要消费比多的工夫来变化优化的代码。但是,和尚未优化时比,代码在优化以后将得到重新漂亮之属性。

       有两个 C#编译器开关会影响代码的优化:/optimize
和/debug。下面总结了这些开关对 C#编译器生成
的 IL 代码的质量之影响,以及针对 JIT 编译器生成的地面代码的质之震慑。

C++ 5

    
虽然如此说很不便被人口认,但许多丁(包括自)都觉着托管应用程序的性实际上超过了非托管应用程序。有过多因而我们对斯深信不疑。例如,当
JIT 编译器在运作时以 IL
代码编译成本地代码时,编译器对实践环境的认识比非托管编译器更加深切。下面罗列了托管代码相较于非托管代码的优势:

  1、JIT 编译器能判断应用程序是否运行在一个 Intel Pentium 4 CPU
上,并转移对应的本地代码来使Pentium 4
支持之任何例外指令。相反,非托管应用程序通常是对有无比小作用集聚的 CPU
编译的,不见面采用能升迁应用程序性能的非常指令。

  2、JIT
编译器能断定一个一定的测试在其运行的机械及是否连败。例如,假得一个法包含以下代码:
if (numberOfCPUs > 1) {

}
     如果主机才生一个 CPU,JIT 编译器不见面吧上述代码生成任何 CPU
指令。在这种场面下,本地代码用对主机进行优化,最终之代码变得还小,执行得重新快。

  3、应用程序运行时,CLR 可以评估代码的实行,并将 IL
重新编译成本地代码。重新编译的代码可以更组织,根据刚才考察到之执行模式,减少非科学的分预测。虽然眼前版本的
CLR 还非克不辱使命即一点,但他日底版也许就得了。

     除了这些理由,还有另外一部分理由而我们相信在实行效率上,未来底托管代码会比较目前的非托管代码更优良。大多数托管应用程序目前之习性都相当对,将来还乐观更加提升。

IL  和验证

  IL
是冲栈的。这意味它的持有指令都要用操作数压入(push)一个执行栈,并由栈弹出(pop)结果。由于
IL
没有供操作寄存器的授命,所以人们得以生易地创造新的言语及编译器,生成面向
CLR 的代码。

      IL 指令或“无类型”(typeless)的。例如,IL 提供了一个 add
指令,它的用意是以抑制入栈的最后两单操作数加至齐。add 指令不分 32 位和
64 位版本。add
指令执行时,它判断栈中的操作数的种,并执行恰当的操作。

      我个人觉得,IL 最可怜之优势并无在于它对根 CPU 的悬空。IL
提供的极酷的优势在于应用程序的健壮性 11 和安全性。将 IL 编译成本地 CPU
指令时,CLR 会执行一个叫做也求证(verification)的过程。这个过程会检讨高级
IL
代码,确定代码所做的一切都是安全的。例如,验证会核实调用的每个方法都发生不易数量的参数,传于每个方法的每个参数都独具无可争辩的品类,每个方法的归来值都获了科学的施用,每个方法还有一个回到语句,等等。在托管模块的首批数据中,包含了如果出于验证过程用的备办法及类型信息。

当地代码生成器:NGen.exe

     使用.NET Framework 配套提供的 NGen.exe
工具,可以以一个应用程序安装及用户的微处理器达经常,将
IL代码编译成本地代码。由于代码在装时一度编译好,所以 CLR 的 JIT
编译器不待以运转时编译 IL 代码,这促进提升应用程序的性能。NGen.exe
能于有限栽状态下发表举足轻重作用:

  1、加快 应用程序的开行速度 运行 NGen.exe
能加速启动速度,因为代码已经编译成本地代码,运行时莫待再次消费时编译。

  2、减多少应用程序的劳作集 13
如果一个主次会而加载到差不多独过程面临,对该程序集运行
NGen.exe可减多少应用程序的做事集(working set)。NGen.exe 会将 IL
编译成本地代码,并以这些代码保存到一个独的文本被。这个文件可以经过“内存映射”的措施,同时射到多单过程地址空间被,使代码得到了共享,避免每个过程都待平等客单独的代码拷贝。