第一章 CLR的执行模型

来源:互联网 发布:施里芬计划 知乎 编辑:程序博客网 时间:2024/04/27 16:59

本章概述

本章概述了 .NET Framework 是如何设计的,介绍了 Framework包含的一些新技术,本章还要展示如何将源代码生成一个应用程序,或者生成为一组可重新分发的组件(文件)。最后结束应用程序是如何执行的。

1.1 将源代码编译成托管堆

  首先,看看什么是CLR
CLR 即 公用语言运行时(Common Language Runtime),是一个可由多种编程语言使用的"运行时"。在有些文档中将CLR翻译成“公共语言运行库”,其实是一个概念。

CLR的核心功能(比如内存管理、程序集加载、安全性、异常处理和线程同步)可由面向 CLR 的所有语言使用。

托管模块:是一个标准的32位 Microsoft Windows可移植执行体(PE32)文件,或者是一个标准的64位Windows可移植执行体(PE32+)文件。它们都是需要CLR才能执行。(如图)

元数据 : 是一组数据表。其中一些数据表描述了模块中定义的内容,比如类型及其成员。

元数据用途:

1.   编译时,元数据消除了对本地C/C++头和库文件的需求,因为在负责实现类型/成员的IL代码文件中,已包含和引用的类型/成员有关的全部信息,编译器可直接从托管模块中读取元数据

2.   Microsoft Visual Studio 使用元数据帮助你写代码。它的“智能感知”(IntelliSense)技术可以分析元数据,指出一个类提供了哪些方法、属性、事件、字段。

3.   CLR 的代码验证过程使用元数据确保代码只执行“类型安全”的操作。

4.   元数据允许一个对象的字段序列化到一个内存块中,将其发送给另一台机器,并从元数据知道那个对象中的哪些字段引用了其他对象。

5.   元数据允许垃圾回收器跟踪对象的生存期。垃圾回收器能判断任何对象的类型,并从元数据知道那个对象中的哪些字段引用了其他对象。

1.2 将托管模块合并成程序集

CLR  实际不和模块一起工作。相反,他是和程序集一起工作的。
程序集(Assembly):是一个或多个模块/资源文件的逻辑性分组。其次,程序集是重用、安全性以及版本控制的最小单元。在CLR的世界里,程序集相当于一个“组件”。(如图:将托管代码合并成程序集)
     
默认情况下:编译器实际会把生成的托管模块转换成程序集。即C#编译器生成含有清单的一个托管模块。
在程序集的模块中,还包含与引用的程序集有关的信息(包括它们的版本号),这些信息使程序集能够自描述,而且不需要注册表中保存额外的信息。由于不需要额外信息,所以部署起来要容易的多。

1.3  执行程序集的代码

如前所说,托管程序集同时包含元数据和IL。IL是与CPU无关的机器语言。IL比大多数CPU机器语言都要高级,IL能访问和操作对象类型。并提供了指令来创建和初始化对象、调用对象上的虚方法以及直接操作数据元素。它甚至提供了抛出和捕捉异常的指令来来实现错误处理。可将IL视为一种面向对象的机器语言。
Microsoft 提供了一个名为ILAsm.exe的IL汇编器和一个名为ILDasm.exe的IL反汇编器。
下面我们看一个方法首次调用时发生的事情(如下图所示)

就在Main方法执行之前,CLR会检测出Main的代码引用的所有类型。这导致CLR分配一个内部数据结构,它用于管理对所有引用的类型的访问,在上图中,Main方法引用了一个Console类型,这导致CLR分配一个内存结构。在这个内部数据结构中,Console类型定义的每个方法都有一个对应的记录项。每个记录项都容纳了一个地址,根据此地址即可找到方法的实现。对这个结构进行初始化时,CLR将每个记录项都设置成(指向)包含在CLR内部的一个未文档化的函数。我们将这个函数称为JITCompiler。
Main 方法首次调用 WriteLine时,JITCompiler函数会被调用。该函数负责将一个方法的IL代码编译成本地CPU指令。由于IL是"即时"编译的,所以通常将CLR的这个组件称为JITer或者JIT编译器。
JITCompiler 函数被调用时,它知道要调用哪个方法。然后会在定义该类型的程序集的元数据中查找被调用方法的IL。接着JITCompiler会验证IL代码,并将IL代码编译成本地CPU指令。本地CPU指令被保存到一个动态分配的内存块中。
注意:假如Main 要第二次调用WriteLine时,因为已经对WriteLine代码进行了验证和编译,所以会直接执行内存块中的代码。完全跳过JITCompiler函数。WriteLine执行完毕后,会再次返回Main.一个方法只有在首次调用时才会造成一些性能损失。以后调用都以本地代码的形式全速运行,无需重复IL验证并编译成本地代码。

1.3.1 IL和验证

IL是基于栈的,这意味者它的所有指令都要将操作数压入(push)一个执行栈,并从栈弹出(Pop)结果。由于IL没有提供操作寄存器的指令,所以人们可以很容易的创建新的语言和编译器,生成面向CLR的代码。

IL指令还是“无类型”的。

1.3.2 不安全的代码

C# 编译器默认生成的是安全的(safe)代码,这种代码是否安全是可以验证的。然而C#编译器也允许开发人员写不安全的(unsafe)代码。不安全的代码允许直接操作内存地址。并可操作这些地址处的字节。这是一个非常强大的功能,通常只有在与非托管代码进行互操作,或者在提升对效率要求极高的算法性能的时候,才需要这样做。

使用不安全的代码会带来一个重大风险,这种代码可能会破坏数据结构,危害安全性,甚至可能造成新的安全漏洞。因此C#编译器要求包含不安全代码的方法都要用unsafe关键字标记。除此之外,C#编译器还要求使用/unsafe开关来编译源代码。

1.4 本地代码生成器:NGen.exe

NGen.exe可以在一个应用程序安装到用户的计算机上时,将IL代码编译成本地代码。

NGen.exe能在一下两种情况下发挥重要作用:

1.  加快引用程序的启动速度, 运行NGen.exe 能加快启动速度,因为代码已经编译成本地代码,运行时不需要在进行编译。

2.  减小应用程序的工作集,  如果一个程序集会同时加载到多个进程中,对该程序集运行MGen.exe  可减少应用程序的工作集。NGen.exe会将IL编译成本地代码。

更多信息参考官方文档,这里就不赘述了.http://msdn.microsoft.com/zh-cn/library/6t9t5wcf.aspx

1.5 Framework 类库

.Net Framework 中包含了Framework类库(Framework Class Library,FCL)。FCL是一组DLL程序集的统称,其中含有数千个类型定义,每个类型都公开一些功能。

我们可以利用这些程序集创建的一部分应用程序。比如(web 服务、web窗体应用程序、Windows应用程序、windows服务、组件库等等)

1.6 通用类型系统(CTS)

CLR完全是围绕类型展开的,这一点到现在为止应该是很明显了。通用类型,用一种编程语言写的代码能与用另一种语言写的代码沟通,由于类型是CLR的根本,所以Microsoft 指定了一个正式的规范,即“通用类型系统”(Common Type System,CTS),它描述了类型的定义和行为。

CTS 规范规定,一个类型可以包含零个或者多个成员。比如 (字段、方法、属性、事件等),除此之外,CTS还为类型继承、徐方法、对象生存期等定义了相应的规则。其实没有必要专门学习CTS规则,因为你选择的语言会采用你熟悉的方式公开它自己的语言语法和类型规则。

CTS另一个规则:所有类型最终必须从预定义的System.Object类型继承。可以看出Object是System命名空间中定义的一个类型名称。Object是其他所有类型的根,因此每一个实例都有一组最基本的行为。

a. 比较两个实例的相等型

b. 获取实例的哈希码

c. 查询一个实例的真正类型

d. 执行实例的浅(按位)拷贝

f. 获取实例对象的当前状态的一个字符串表示。

1.7 公共语言规范(CLS)

我们看一下CTS的规则:在CLR中,一个类型的每个成员要么是一个字段(数据),要么是一个方法(行为),这意味着每一种编程语言都必须能访问字段和调用方法。为了方便的进行编程,语言通常提供了额外的抽象,对这些常见的编程模式进行简化。例如,数组、属性、索引器、委托、事件、构造器、析构函数、操作符重载等概念。

如下一个类

using System;namespace ConsoleApplication1{    internal sealed class Test    {        // 构造器        public Test() { }        // 析构器        ~Test() { }        // 操作符重载        public static Boolean operator ==(Test t1, Test t2)         {            return true;        }        // 重载"=="必须重载"!="        public static Boolean operator !=(Test t1, Test t2)        {            return false;        }        // 一个操作符的重载        public static Test operator +(Test t1, Test t2)        {            return null;        }        // 属性        public String Aproperty        {            get { return null; }            set { }        }        // 索引器        public String this[Int32 x]        {            get { return null; }            set { }        }        // 事件        event EventHandler AnEvent;    }}


编译器编译上述代码会得到一个类型,其中包含大量的字段和方法。可以使用反汇编工具IL(ILDasm.exe)来检查最终生成的托管模块。

下面看看IL中显示的Test类型字段和方法。

分别简单说下Test的字段和方法,由于比较多,我就随便说几个重要的。

AnEvent  字段  事件,字段名是AnEvent,类型是System.EventHandler,从上图可以看出这是唯一的一个字段。

.ctor  类型为方法,构造器.红色的正方快代表方法。

add_AnEvent方法 是 AnEvent的add访问器方法

get_Aproperty方法是 Aproperty属性的get访问器,对应还有set_Aproperty是set访问器。

op_Addition 方法是+操作符.

------------------------------------------------------------------------------------------------------------------

注:CLR的执行模型基本就写完了,热烈欢迎大家讨论,转载和分享。

原创粉丝点击