1.1 将源代码编译成托管模块
2011-10-21 14:12 iRead 阅读(364) 评论(0) 编辑 收藏 举报决定将.NET Framework作为自己的开发平台之后,第一步是决定要生成什么类型的应用程序或组件。假定你已完成了这些次要的细节:一切都已设计好,规范已经写好,可以着手进行开发了。
现在,必须决定要使用哪一种编程语言。这通常是一个艰难的抉择,因为语言各有各的“本事”。例如,在非托管C/C++中,可对系统进行一些相当低级的控制。可完全按自己的想法管理内存,在必要时能够方便地创建线程。另一方面,如果使用Microsoft Visual Basic 6,可以快速生成UI应用程序,并可方便地控制COM对象和数据库。
事实上,在运行时,CLR根本不关心开发人员用哪一种语言来写源代码。这意味着在挑选编程语言时,应选择最容易表达自己意图的语言。可用任何编程语言来开发代码,只要编译器是面向CLR的就可以了。
既然如此,不同编程语言的优势何在呢?事实上,可以将编译器视为语法检查器和“正确代码”的分析器。它们检查源代码,确定你写的一切都有意义,然后输出对你的意图进行描述的代码。不同编程语言允许用不同的语法来开发。不要低估这个选择的价值。例如,对于数学或金融应用程序,使用APL语法来表达自己的意图,相较于使用Perl语法来表达同样的意图,可节省许多开发时间。
Microsoft已创建好了几个面向“运行时”的语言编译器,其中包括:C++/CLI、C#(发音是“C sharp”)、Visual Basic、F#(发音是“F sharp”)、Iron Python、Iron Ruby以及一个“中间语言”(Intermediate Language,IL)编译器。除了Microsoft,另一些公司、学院和大学也创建了自己的编译器,它们也能面向CLR来生成代码。我所知道的有针对下列语言的编译器:Ada,APL,Caml,COBOL,Eiffel,Forth,Fortran,Haskell,Lexico,LISP,LOGO,Lua,Mercury,ML,Mondrian,Oberon,Pascal,Perl,Php,Prolog,RPG,Scheme,Smalltalk和Tcl/Tk。
图1-1展示了编译源代码文件的过程。如图所示,可用支持CLR的任何一种语言创建源代码文件。然后,用一个对应的编译器检查语法和分析源代码。无论选用哪一个编译器,结果都是一个托管模块(Managed module)。托管模块是一个标准的32位Microsoft Windows可移植执行体(PE32)文件,或者是一个标准的64位Windows可移植执行体(PE32+)文件,它们都需要CLR才能执行。顺便说一句,托管的程序集总是利用了Windows的数据执行保护(Data Execution Prevention,DEP)和地址空间布局随机化(Address Space Layout Randomization,ASLR);这两个功能旨在增强整个系统的安全性。
图1-1 将源代码编译成托管模块
表1-1总结了一个托管模块的各个组成部分
表1-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代码)。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的代码验证过程使用元数据确保代码只执行”类型安全“的操作。(稍后会讲到验证。)
- 元数据允许将一个对象的字段序列化到一个内存块中,将其发送到另一台机器,然后反序列化,在远程机器上重建对象的状态。
- 元数据允许垃圾回收器跟踪对象的生存期。垃圾回收器能判断任何对象的类型,并从元数据知道那个对象中的哪些字段引用了其他对象。
第2章”生成、打包、部署和管理引用程序及类型“将更详细地描述元数据。
Microsoft的C#,Visual Basic,F#和IL汇编器总是生成包含托管代码(IL)和托管数据(垃圾收集的数据类型)的模块。为了执行包含托管代码以及/或者托管数据的模块,最终用户必须在自己的计算机上安装好CLR(目前作为.NET Framework的一部分提供)。这类似于为了运行MFC或者Visual Basic 6应用程序,用户必须安装Microsoft Foundation Class(MFC)库或者Visual Basic DLLs。
Microsft的C++编译器默认生成包含非托管(本地)代码的EXE/DLL模块,并在运行时操纵非托管数据(本地内存)。这些模块不需要CLR即可执行。然而,执行一个/CLR命令行开关,C++编译器就能生成包含托管代码的模块。当然,最用用户必须安装CLR才能执行这种代码。在前面提到的所有Microsoft编译器中,最特殊的就是C++编译器,因为只有它才允许开发人员同时写托管和非托管代码,并生成到一个模块中。它也是唯一允许开发人员在源代码中同时定义托管和非托管数据类型的Microsoft编译器。Microsoft C++编译器的灵活性是其他编译器无法比拟的,因为它允许开发人员在托管代码中使用它们现有的本地C/C++代码,并在逐渐习惯之后开始使用托管类型。