CLR via C#, 4th -- 【CLR基础】 -- 第1章 CLR的执行模型
1.1将源代码编译成托管模块
公共语言运行时(Common Language Runtime,CLR)
CLR是一个可由多种编程语言使用的“运行时”。CLR的核心功能(比如内存管理、程序集加载、安全性、异常处理和线程同步)可由面向CLR的所有语言使用。
托管模块(managed module)
无论选择哪个编译器,结果都是托管模块。托管模块是标准的32位Microsoft Windows可移植执行体(PE32)文件,或者是标准的64位Windows可移植执行体(PE32+)文件,它们都需要CLR才能执行。
本机代码编译器(native code compilers)
本机代码编译器生成的是面向特定CPU架构(比如×86,X64或ARM)的代码。相反,每个面向CLR的编译器生成的都是IL(中间语言)代码。IL代码有时称为托管代码(managed code),因为CLR管理它的执行。
元数据(metadata)
除了生成IL,面向CLR的每个编译器还要在每个托管模块中生成完整的元数据(metadata)。
元数据简单地说就是一个数据表集合。一些数据表描述了模块中定义了什么(比如类型及其成员),另一些描述了模块引用了什么(比如导入的类型及其成员)。
元数据总是与包含IL代码的文件关联。事实上,元数据总是嵌入和代码相同的EXE/DLL文件中,这使两者密不可分。由于编译器同时生成元数据和代码,把它们绑定一起,并嵌入最终生成的托管模块,所以元数据和它描述的IL代码永远不会失去同步。
CLR目前作为.NET Framework的一部分提供。
在前面提到的所有Microsoft编译器中,C++编译器是独一无二的,只有它才允许开发人员同时写托管和非托管代码,并生成到同一个模块中。它也是唯一允许开发人员在源代码中同时定义托管和非托管数据类型的Microsoft编译器。
1.2将托管模块合并成程序集
程序集(assembly)
CLR实际不和模块工作。它和程序集工作。程序集(assembly)是抽象概念,首先,程序集是一个或多个模块/资源文件的逻辑性分组。其次,程序集是重用、安全性以及版本控制的最小单元。
在CLR的世界中,程序集相当于“组件”。
清单(manifest)
清单也是元数据表的集合。这些表描述了构成程序集的文件、程序集中的文件所实现的公开导出的类型以及与程序集关联的资源或数据文件。
编译器默认将生成的托管模块转换成程序集。也就是说,C#编译器生成的是含有清单的托管模块。清单指出程序集只由一个文件构成。所以,对于只有一个托管模块而且无资源(或数据)文件的项目,程序集就是托管模块,生成过程中无需执行任何额外的步骤。
1.3加载公共语言运行时
生成的每个程序集既可以是可执行应用程序EXE,也可以是DLL(其中含有一组由可执行程序使用的类型)。
要知道是否已安装.NET Framework,只需检查%SystemRoot%\System32目录中的MSCorEE.dll文件。存在该文件,表明.NET Framework已安装。
要了解安装了哪些版本的.NET Framework,请检查以下目录的子目录:
%SystemRoot%\Microsoft.NET\Framework
%SystemRoot%\Microsoft.NET\Framework64
C#编译器提供了一个/platform命令行开关选项。这个开关允许指定代码只在一个特定版本的Windows上运行。
1.4执行程序集的代码
托管程序集同时包含元数据和IL,可将IL视为一种面向对象的机器语言。
IL也能使用汇编语言编写,Microsoft甚至专门提供了名为ILAsm.exe的IL汇编器和名为ILDasm.exe的IL反汇编器。
为了执行方法,首先必须把方法的IL转换成本机(native)CPU指令。这是CLR的JIT(just-in-time或者“即时”)编译器的职责。

方法仅在首次调用时才会有一些性能损失。以后对该方法的所有调用都以本机代码的形式全速运行,无需重新验证IL并把它编译成本机代码。

还要注意,CLR的JIT编译器会对本机代码进行优化,两个C#编译器开关会影响代码优化:/optimize和/debug。
使用/optimize-,在C#编译器生成的未优化IL代码中,将包含许多NOP(no-operation,空操作)指令,还包含许多跳转到下一行代码的分支指令。Visual Studio利用这些指令在调试期间提供“编辑并继续”(edit-and-continue)功能。相反,如果生成优化的IL代码,C#编译器会删除多余的NOP和分支指令。而在控制流程被优化之后,代码就难以在调试器中进行单步调试了。
在Visual Studio中新建C#项目时,项目的“调试”(Debug)配置指定的是/optimize-和/debug:full开关,而“发布”(Release)配置指定的是/optimize+和/debug:pdbonly开关。
1.4.1 IL和验证
IL基于栈。
IL指令还是“无类型”(typeless)的。例如,IL提供了add指令将压入栈的最后两个操作数加到一起。add指令不分32位和64位版本。
1.4.2不安全的代码
Microsoft C#编译器默认生成安全(safe)代码,这种代码的安全性可以验证。然而,Microsoft C#编译器也允许开发人员写不安全的(unsafe)代码。不安全的代码允许直接操作内存地址,并可操作这些地址处的字节。
C#编译器要求包含不安全代码的所有方法都用unsafe关键字标记。除此之外,C#编译器要求使用/unsafe编译器开关来编译源代码。
1.5本机代码生成器:NGen.exe
使用.NET Framework提供的NGen.exe工具,可以在应用程序安装到用户的计算机上时,将IL代码编译成本机代码。由于代码在安装时已经编译好,所以CLR的JIT编译器不需要在运行时编译IL代码,这有助于提升应用程序的性能。
1.提高应用程序的启动速度
运行NGen.exe能提高启动速度,因为代码已编译成本机代码,运行时不需要再花时间编译。
2.减小应用程序的工作集
如果一个程序集同时加载到多个进程中,对该程序集运行NGen.exe可减小应用程序的工作集。NGen.exe将IL编译成本机代码,并将这些代码保存到单独的文件中。该文件可以通过“内存映射”的方式,同时映射到多个进程地址空间中,使代码得到了共享,避免每个进程都需要一份单独的代码拷贝。
现在,每当CLR加载程序集文件,都会检查是否存在一个对应的、由NGen生成的本机文件。如果找不到本机文件,CLR就和往常一样对IL代码进行JIT编译。
NGen生成的文件有以下问题:
- 没有知识产权保护
- NGen生成的文件可能失去同步(可用更新(update)模式运行NGen.exe)
- 较差的执行时性能
1.6 Framework 类库
NET Framework 包含Framework 类库(Framework Class Library,FCL)。FCL是一组DLL程序集的统称,其中含有数千个类型定义,每个类型都公开了一些功能。
Web 服务(Web service)
基于HTML的Web窗体/MVC应用程序(网站)
“富”Windows GUI应用程序(WPF)
Windows 控制台应用程序
Windows 服务
数据库存储过程
Microsoft的SQL Server、IBM的DB2以及Oracle的数据库服务器允许开发人员用.NET Framework写存储过程。
组件库
.NET Framework允许生成独立程序集(组件),其中包含的类型可以轻松集成到前面提到的任何一种类型的应用程序中。
1.7通用类型系统
“通用类型系统”(Common Type System,CTS)
CTS有以下成员:
字段(Field)
作为对象状态一部分的数据变量。字段根据名称和类型来区分。
方法(Method)
针对对象执行操作的函数,通常会改变对象状态。方法有一个名称、一个签名以及一个或多个修饰符。签名指定参数数量(及其顺序);参数类型:方法是否有返回值:如果有返回值,还要指定返回值类型。
属性(Property)
对于调用者,属性看起来像是字段。但对于类型的实现者,属性看起来像是一个方法(或者两个方法{get;set;})。
事件(Event)
事件在对象以及其他相关对象之间实现了通知机制。
CTS 类型可见性规则以及类型成员的访问规则:
private
成员只能由同一个类(class)类型中的其他成员访问。
family
成员可由派生类型访问,不管那些类型是否在同一个程序集中。注意,许多语言(比如C++和C#)都用protected修饰符来标识family
family and assembly
成员可由派生类型访问,但这些派生类型必须在同一个程序集中定义。许多语言(比如C#和Visual Basic)都没有提供这种访问控制。当然,IL汇编语言不在此列。
assembly
成员可由同一个程序集中的任何代码访问。许多语言都用internal修饰符来标识assembly.
family or assembly
成员可由任何程序集中的派生类型访问。成员也可由同一个程序集中的任何类型访问。C#用protected internal修饰符标识family or assembly。
public
成员可由任何程序集中的任何代码访问。
所有类型最终必须从预定义的System.Object类型继承。可以看出,Object是System命名空间中定义的一个类型的名称。Object是其他所有类型的根,因而保证了每个类型实例都有一组最基本的行为。具体来说,System.Object类型允许做以下操作:
- 比较两个实例的相等性。
- 获取实例的哈希码。
- 查询一个实例的真正类型。
- 执行实例的浅(按位)拷贝。
- 获取实例对象当前状态的字符串表示。
1.8 公共语言规范
CLR则集成了所有语言,用一种语言创建由对象在另一种语言中,和用后者创建的对象具有相同地位。为此,Microsoft定义了“公共语言规范”(Common Language Specification, CLS)

1.9 与非托管代码的互操作性
CLR来提供了一些机制,允许在应用程序中同时包含托管和非托管代码。具体地说,CLR支持三种互操作情形:
- 托管代码能调用DLL中的非托管函数
- 托管代码可以使用现有COM组件(服务器)
- 非托管代码可以使用托管类型(服务器)
浙公网安备 33010602011771号