公共语言运行时(CLR),是一个可由多种编程语言使用的“运行时”。包括,内存管理,程序集加载,安全性,异常处理和线程同步,它们可以由面向CLR的所有语言使用。在运行时,CLR根本不关心开发人员用哪一种语言来写源代码。
微软已经创建了好几个面向CLR的语言编译器,包括,C++/CLI,C#,Visual Basic,F#,Iron Python,Iron Ruby以及一个中间语言(IL)汇编器。除了微软,另一些公司,学院和大学也创建了自己的编译器,它们也能面向CLR来生成代码。
无论使用哪一种编译器,结果都是一个托管模块,它是一个标准的PE32文件或PE32+文件。托管模块由4部分组成,PE32或PE32+头,CLR头,元数据,中间语言(IL)代码。
本地代码编译器生成的是面向特定CPU架构的代码,而每个面向CLR的编译器生成的都是IL代码。IL代码有时称为托管代码,因为CLR要管理它的执行。
高级语言通常只公开了CLR所有功能的一个子集。然而,IL汇编语言允许开发人员访问CLR的所有功能。
为了执行包含托管代码或者托管数据的模块,最终用户必须在自己的计算机上安装CLR(.NET Framework的一部分)。C++编译器比较特殊,只有它才允许开发人员同时写托管和非托管代码,并生成到同一个模块中。
C++编译器默认生成包含非托管(本地)代码的EXE/DLL模块,并在运行时操纵非托管数据(本地内存),这些模块不需要CLR即可运行。然而,制定一个/CLR
命令行开关后,C++编译器就能生成包含托管代码的模块。当然,最终用户必须安装CLR才能执行这种代码。
只有C++编译器允许开发人员在源代码中同时定义托管和非托管数据类型,允许开发人员在托管代码中使用他们现有的本地C/C++代码,并在逐渐习惯之后开始使用托管类型。
程序集(Assembly)是一个或多个模块或资源文件的逻辑分组。在CLR的世界中,程序集相当于一个“组件”。
CLR操作的逻辑单元就是程序集,在程序集的所有文件中,有一个文件容纳了清单(manifest)。清单是一组元数据表的集合,表中主要包含了作为程序集的组成部分的那些文件的名称。此外,它们还描述了程序集的版本,语言文化,发布者,公开导出类型以及构成程序集的所有文件。
CLR总是首先加载包含“清单”元数据表的文件,再根据这个“清单”来获取程序集中的其他文件的名称。
程序集是可以由多个文件构成的,一些是含有元数据的PE文件,另一些是.gif或.jpg这样的资源文件。为了便于理解,可将程序集视为一个逻辑EXE或DLL。程序集将可重用类型的逻辑表示与物理表示分开了,程序集是进行重用,版本控制和应用安全性设置的一个基本单元。
遗憾的是,不能直接从Visual studio集成开发环境中创建多文件程序集。要创建多文件程序集,只能使用命令行工具。
命令行创建多文件程序集的例子:
假定有两个源代码文件,RUT.cs包含不常用的类型,FUT.cs包含常用的类型。
csc /t:module RUT.cs
C#编译器将编译RUT.cs,创建名为RUT.netmodule的标准PE文件,但是CLR不能单独加载它。
csc /out:JeffTypes.dll /t:library /addmodule:RUT.netmodule FUT.cs
C#编译器将编译FUT.cs,来生成JeffTypes.dll。由于指定了/t:library
开关,所以生成的JeffTypes.dll是包含了清单元数据表的一个PE文件。/addmodule:RUT.netmodule
开关告诉编译器,RUT.netmodule这个文件应被视为程序集的一部分。
名称空间完全独立于程序集,一个程序集中可以有不同的名称空间,同一个名称空间也可以分布在不同的程序集上。尽管程序集包含相同的名称空间,但没有相同的类名。
在开发期间,添加对程序集的引用,使之包含在程序集引用中,该类型的程序集就可以用于编译器。运行期间,只要实例化了一种类型的程序集,或者使用了该类型的一个方法,就会加载所引用的程序集。
除了这种自动操作之外,还可以通过编程加载程序集。