CLR via C#笔记——第一章

CLR执行模型

《CLR via C#》第一章:CLR的执行模型

源代码编译成托管模块

CLR-Common Language Runtime-公共语言运行时,可由多种编程语言使用的运行时。
核心功能包括内存管理,程序集加载,安全性,异常处理,线程同步等,可由面向CLR的所有语言使用。
CLR不关心开发人员使用哪一种语言编写源代码。无论使用哪个编译器,结果都是托管模块
托管模块:标准的Microsoft Windows可以指执行体(Portable Executable,PE32,PE32+)文件,需要CLR才能执行。托管程序集总是利用Windows的数据执行保护(Data Execution Prevention,DEP)和地址空间布局随机化(Address Space Layout Randomization,ASLR),旨在增强整体系统的安全性。

托管模块的各个部分:
PE32或PE32+头:标准Windows PE文件头,类似于公共对象文件格式头,PE32能在32位或64位版本上运行,PE32+则只能在64位版本运行。标识了文件类型,包括GUI,CUI或者DLL,并包含一个时间标记指出生成时间。对于只包含IL代码的模块,大多数信息会被忽略。对于包含本机native CPU代码的模块,还包含与本机CPU代码相关的信息。
CLR头:包含使这个模块成为托管模块的信息,包括要求的CLR版本,一些标志,托管模块入口方法的MethodDef元数据token及模块的元数据,资源,强名称等。
元数据:每个托管模块都包含元数据表。一种表描述源代码中定义的类型和成员,一种表描述源代码引用的类型和成员。
IL(中间语言)代码:编译器编译源代码时生成的代码。运行时,CLR将IL编译成本机CPU指令。
IL代码又称为托管代码,CLR管理它的执行。

元数据是包括COM的类型库(Type Library)和接口定义语言(Interface Definition Language,IDL)文件等老技术的超集。元数据总是与包含IL代码的文件关联。由于编译器同时生成元数据和代码,绑定并嵌入最终生成的托管模块,元数据和它描述的IL代码永远不会失去同步。

元数据的用途包括但不限于:

  • 避免了编译时对原生C/C++头和库文件的需求。
  • Microsoft Visual Studio的智能感知(IntelliSense)技术用元数据帮助写代码,即代码提示。
  • 允许将对象的字段序列化到内存块,在远程机器上反序列化重建对象状态。
  • 允许垃圾回收器跟踪对象生存期。
    MicroSoft的C++编译器默认生成包含非托管(native)代码的EXE/DLL模块,并在运行时操作非托管数据(native内存),这些模块不需要CLR即可运行。它是唯一允许同事写托管和非托管代码的编译器。

托管模块合并成程序集

CLR和程序集(assembly)一起工作。程序集是重用、安全性及版本控制的最小单位。
程序集使得一组文件可作为一个单独的实体来对待。
程序集模块中包含与引用的程序集有关的信息,使之能够自描述。CLR能判断程序集直接以来对象而不需要额外信息。这使得程序集和非托管组件相比更容易部署。

加载公共语言运行时

每个程序集既可以是可执行应用程序也可以是DLL。

执行程序集的代码

IL是与CPU无关的机器语言。
CLR为了执行方法必须把方法的IL转换成本机CPU指令,该指责由JIT编译器完成。
callmethodfirsttime.png
callmethodsecondtime.png
方法仅在首次调用时会有一些性能损失,同时CLR的JIT编译器会对本机代码进行优化。
只有打开/debg开关,编译器才会生成Program Database(PDB)文件,帮助调试器查找局部变量并将IL指令映射到源代码。JIT编译器会记录每条IL指令所生成的本季代码,可利用Visual Studio将调试器连接到进程进行调试。
非托管代码针对具体CPU平编译,一旦调用就能直接执行。
托管代码编译分为两个阶段,编译器编译源代码生成IL代码,IL代码运行时编译成本机CPU指令。
托管代码相较于非得托管代码的优势:

  • JIT编译器能判断应用程序运行的CPU,生成相应的可利用其特性的特殊指令本机代码。非托管应用程序通常针对最小功能集合的CPU来编译。
  • JIT编译器能判断一个特定测试在其机器上是否总是失败,从而不为其生成CPU指令,最终代码更小,执行更快。
  • CLR可评估代码的执行,并将IL重新编译成本机代码。
    .Net Framework SDK提供了NGen.exe工具,可以将程序集所有IL代码预编译为本机代码,避免运行时编译。但是生成的代码不会像JIT那样进行高度优化。

IL基于栈。
IL编译为本机CPU指令时,CLR执行名为验证(vertification)的过程,确保代码所作的一切是安全的。
Windows每个进程有自己的虚拟地址空间,每个进程放到独立的地址空间使之不会互相干扰将获得健壮性和稳定性。通过验证托管代码可确保代码不会不正确访问内存,使多个托管应用程序可以放到同一个Windows虚拟地址空间运行。(一个进程运行多个应用程序可减少进程数从而增加性能)

C#编译器默认生成安全代码,也允许开发人员写不安全代码。不安全代码可直接操作内存地址,通常只在与非托管代码进行交互或提升对效率要求极高的算法性能时才会这么做。
C#编译器要求所有包含不安全代码的方法都使用unsafe关键字,并使用/unsafe开关来编译源代码。
Microsoft提供了PEVerify.exe程序,可用于检查一个程序集中的所有方法并报告其中含有不安全代码的方法。

本机代码生成器NGen.exe

使用NGen.exe预编译可以:

  • 提高应用程序启动速度
  • 减少应用程序工具集(进程中已映射的物理内存部分,进程还有一部分虚拟内存,另外还有一部分内存在磁盘上的分页文件里)
    NGen生成的文件包含的问题:
  • 没有知识产权保护
  • 生成的文件可能失去同步(CLR版本,CPU类型,操作系统版本,安全性等变更)
  • 较差的执行时性能(无法像JIT编译器正对执行环境优化代码)

Framework类库

Framework类库(Framework Class Library,FCL)是一组DLL的程序集统称,可用于:

  • Web服务
  • 基于HTML的Web窗体/MVC应用程序
  • 富GUI应用程序
  • Windows控制台应用程序
  • Windows服务
  • 数据库存储过程
  • 组件库
    部分常规FCL命名空间
  • System - 包含所有基础类型
  • System.Data - 包含数据库通信及处理数据的类型
  • System.IO - 包含执行流I/O及浏览目录/文件的类型
  • System.Net - 包含进行低级网络通信和一些常用Internet协议协作的类型
  • System.Runtime.InteropServices - 允许托管代码访问非托管操作系统平台功能
  • System.Security - 包含保护数据和资源的类型
  • System.Text - 包含各种编码文本的类型
  • System.Threading - 包含异步操作和同步资源访问的类型
  • System.Xml - 包含用于处理XML架构和数据的类型

通用类型系统

通用类型系统(Common Type System,CTS)规范规定,一个类型可以包含零个或多个成员。

  • 字段(Field)
    作为对象状态一部分的数据变量。字段根据名称和类型来区分。
  • 方法(Method)
    针对对象执行操作的函数,通常会改变对象状态。方法有一个名称、一个签名以及一个或多个修饰符。签名制定参数数量及顺序;参数类型;方法是否有返回值;返回值类型。
  • 属性(Property)
    对于类型实现者,属性更像是一个方法(getter/setter)。属性允许创建只读或只写的“字段”。
  • 事件(Event)
    事件在对象以及其他相关对象之间实现了通知机制。

CTS还制定了类型可见性规则和类型成员的访问规则,使程序集为一个类型建立了可视边界。

  • private
    成员只能由同一类类型中的其他成员访问。
  • family
    成员可由派生类型访问,不管是否在同一个程序集中。C#中使用protected修饰符。
  • family and assembly
    成员可由派生类型访问,但必须在一个程序集中定义。许多语言没有提供这种访问控制。
  • assembly
    成员可由同一个程序集中的任何代码访问。许多语言使用internal修饰符。
  • family or assembly
    成员可由任何程序集中的派生类型访问,也可由同一个程序集中的任何类型访问。C#使用protected internal修饰符。
  • public
    成员可由任何程序集中的任何代码访问。

所有类型最终必须从预定义的System.Object类型继承。该类型允许做以下事情:

  • 比较两个实例的相等性。
  • 获取实例的哈希码。
  • 查询一个实例的真正类型。
  • 执行示例的浅拷贝。
  • 获取实例对象当前状态的字符串表示。

公共语言规范

公共语言规范(Common Language Specification,CLS)定义了一个最小功能集,只有支持这个功能集,生成的类型才能兼容由其他符合CLS,面向CLS的语言生成的组件。
CLSsample.png
CLS的规则:在CLR中,类型的每个成员要么是字段(数据),要么是方法(行为)。编译器在源代码中遇到任何东西(枚举、数组、索引器、委托、构造器、操作符重载等),都必须将其转换成字段和方法。

与非托管代码的互操作性

CLR支持的三种互操作情形:

  • 托管代码能调用DLL中的非托管函数。
  • 托管代码可以使用现有COM组件。(服务器)
  • 非托管代码可以使用托管类型。(服务器)