1.5 托管执行和CLI
处理器不能直接解释程序集。程序集用的是另一种语言,即公共中间语言(Common Intermediate Language,CIL),或称中间语言(IL)[1]。C#编译器将C#源代码文件转换成中间语言。为了将CIL代码转换成处理器能理解的机器码,还要完成一个额外的步骤(通常在运行时进行)。该步骤涉及C#程序执行的一个重要元素:VES(Virtual Execution System,虚拟执行系统)。VES也称为运行时(runtime)。它根据需要编译CIL代码,这个过程称为即时编译或JIT编译(just-in-time compilation)。如代码在像“运行时”这样的一个“代理”的上下文中执行,就称为托管代码(managed code),在“运行时”的控制下执行的过程则称为托管执行(managed execution)。之所以称为“托管”,是因为“运行时”管理着诸如内存分配、安全性和JIT编译等方面,从而控制了主要的程序行为。执行时不需要“运行时”的代码称为本机代码(native code)或非托管代码(unmanaged code)。
注意 “运行时”既可能指“程序执行的时候”,也可能指“虚拟执行系统”。为明确起见,本书用“执行时”表示“程序执行的时候”,用“运行时”表示负责管理C#程序执行的代理[2]。
“运行时”规范包含在一个包容面更广的规范中,即CLI(Common Language Infrastructure,公共语言基础结构)规范[3]。作为国际标准,CLI包含了以下几方面的规范。
·VES或“运行时”。
·CIL。
·支持语言互操作性的类型系统,称为CTS(Common Type System,公共类型系统)。
·编写通过CLI兼容语言访问的库的指导原则(这部分内容见公共语言规范(Common Language Specification,CLS))。
·使各种服务能被CLI识别的元数据(包括程序集的布局或文件格式规范)。
在“运行时”执行引擎的上下文中运行,程序员不需要直接写代码就能使用几种服务和功能,包括:
·语言互操作性:不同源语言间的互操作性。语言编译器将每种源语言转换成相同中间语言(CIL)来实现这种互操作性。
·类型安全:检查类型间转换,确保兼容的类型才能相互转换。这有助于防范缓冲区溢出(这是产生安全隐患的主要原因)。
·代码访问安全性:程序集开发者的代码有权在计算机上执行的证明。
·垃圾回收:一种内存管理机制,自动释放“运行时”为数据分配的空间。
·平台可移植性:同一程序集可在多种操作系统上运行。要实现这一点,一个显而易见的限制就是不能使用平台特有的库。所以平台依赖问题需单独解决。
·BCL(基类库):提供开发者能(在所有.NET框架中)依赖的大型代码库,使其不必亲自写这些代码。
注意 本节只是简单介绍了CLI,目的是让你熟悉C#程序的执行环境。此外,本节还提及了本书后面会用到的一些术语。第24章会专门探讨CLI及其与C#开发者的关系。虽然那一章在本书的最后,但其内容实际并不依赖之前的任何一章。所以,要想多了解一下CLI,随时都可以直接翻阅那一章。
CIL和ILDASM
前面说过,C#编译器将C#代码转换成CIL代码而不是机器码。处理器只理解机器码,所以CIL代码必须先转换成机器码才能由处理器执行。可用CIL反汇编程序将程序集解构为CIL。通常使用Microsoft特有的文件名ILDASM来称呼这种CIL反汇编程序(ILDASM是IL Disassembler的简称),它能对程序集执行反汇编,提取C#编译器生成的CIL。
反汇编.NET程序集的结果比机器码更易理解。许多开发者害怕即使别人没有拿到源代码,程序也容易被反汇编并曝光其算法。其实无论是否基于CLI,任何程序防止反编译唯一安全的方法就是禁止访问编译好的程序(例如只在网站上存放程序,不把它分发到用户机器)。但假如目的只是减小别人获得源代码的可能性,可考虑使用一些混淆器(obfuscator)产品。这种产品会读取IL代码,转换成一种功能不变但更难理解的形式。这可以防止普通开发者访问代码,使程序集难以被反编译成容易理解的代码。除非程序需要对算法进行高级安全防护,否则混淆器足以确保安全。
高级主题:HelloWorld.exe的CIL输出
在不同CLI实现中,使用CIL反汇编程序的命令也有所区别。如果是.NET Core,可以访问http://itl.tc/ildasm了解详情。代码清单1.20展示了运行ILDASM创建的CIL代码。
代码清单1.20 示例CIL输出
最开头是清单(manifest)信息。其中不仅包括被反编译的模块的全名(HelloWorld.dll),还包括它依赖的所有模块和程序集及其版本信息。
基于这样的CIL代码清单,最有趣的可能就是能相对比较容易地理解程序所做的事情,这比阅读并理解机器码(汇编程序)容易多了。上述代码出现了对System.Console.WriteLine()的显式引用。CIL代码清单包含许多外围信息,如果开发者想要理解C#模块(或任何基于CLI的程序)的内部工作原理,但又拿不到源代码,只要作者没有使用混淆器,理解这样的CIL代码清单还是比较容易的。事实上,一些免费工具(比如Red Gate Reflector、ILSpy、JustDecompile、dotPeek和CodeReflect)都能将CIL自动反编译成C#[4]。
[1] CIL的第三种说法是Microsoft IL (MSIL)。本书用CIL一词,因其是CLI标准所采纳的。C#程序员交流时经常使用IL一词,因为他们都假定IL是指CIL而不是其他中间语言。
[2] “运行时”(runtime)作为名词使用时一律添加引号。——译者注
[3] Miller, J., and S.Ragsdale.2004.The Common Language Infrastructure Annotated Standard.Boston: Addison-Wesley.
[4] 注意反汇编(disassemble)和反编译(decompile)的区别。反汇编得到的是汇编代码,反编译得到的是所用语言的源代码。——译者注