转载:http://www.tracefact.net/CLR-and-Framework/DotNet-Framework.aspx

.NET框架

三年前写的《.NET之美》的第六章,现在书名改为了《.NET专题解析》。

本书是一本讲解.NET技术的书籍,目标读者群也是在.NET框架(.NET Framework)下进行开发的程序员,因此我们无法回避的问题就是:什么是.NET框架?它包含了哪些内容?为开发程序提供了哪些支持?很多朋友对这 类个问题的第一反应可能是.NET框架所提供的庞大类库及编写代码所采用的C#语言,实际上远不止这些。

要描述.NET框架,自然会遇到与其相关的一系列专业的技术术语和缩写,相信大家已经见到过许多了,比如:CLI、CIL、CTS、CLS、 CLR、JIT、BCL、FCL、Module、Assembly 等,足以让很多人一头雾水、望而却步。笔者不会像字典一样按首字母排序对术语进行逐一解释,因为这样还是难以理解。我们还是从大家最熟悉的东西开始吧!

6.1 引子

设想一下:编写下面这样一个最简单的显示“Hello, World!”的控制台程序,并将该程序运行起来需要哪几个步骤呢?

using System;
 
class Program {
    static void Main(string[] args) {
        string text = "hello, world!";
        Console.WriteLine(text);
    }
}

这些步骤包括:打开Visual Studio,创建一个C#控制台应用程序项目(在这里将它命名为ConsoleApp),编写代码,编译程序然后运行。虽然这样的程序谁都会写,但是再多进行一下思考就会发现,尽管是一个很小的程序,但已经引入了.NET框架的几个重要方面。

如果创建一个VB.NET类型的项目,实现和上面C#项目完全一样的功能,那么编译后生成的文件有什么区别?

编写控制台应用程序,将字符输出到屏幕,需要调用Console.WriteLine()方法。这个Console类型从何而来呢?

生成的文件在系统中是如何运行起来的?其机制和使用传统VC++生成的可执行文件是否相同?

其实,上面每一个问题的答案都包含.NET框架所提供的支持,这里将它分为三个部分:

  • 对于编译后生成的文件格式和内容,.NET中存在着诸多规范。符合这些规范的程序语言,也叫做面向.NET的语言。编译后生成的文件都可以在.NET运行时下执行,这就是大家所熟知的.NET多语言支持。
  • 在开发阶段,.NET提供了一个庞大的类库,支持开发者快速开发各种应用程序,也支持程序语言设计者开发其语言编译器。
  • 在程序执行阶段,.NET提供了一个程序运行时的环境,这个运行时环境帮助我们管理内存、实时编译程序、进行安全检查、执行垃圾回收等。

接下来就针对上述内容开始为大家详细讲述。

6.2 CIL——公共中间语言

首先要了解的就是C#程序源码在编译之后会得到什么样的一个文件。大家知道,过去使用VC++生成的可执行文件,经过预编译、编译、汇编、链接几个
步骤后,最终生成的可执行文件中就已经包含了处理器的本地代码(Native
Code),支持它运行的只是操作系统和本地的机器指令集。那么采用C#编译器生成的文件又是什么呢?现在需要引入程序集这个概念:在.NET框架下,类
似C#这样的高级语言经过编译后生成的结果文件被称做程序集,其后缀名是.dll(类库)或.exe(可执行程序)。在引入这个概念之前,前面(上一节)
提到程序集时,都是用“文件”这个词来描述的。

程序集的定义只是给编译后生成的文件一个稍微正式一点的名称,对于解释“它是由什么构成的”这个问题并没有太大的帮助。为了进一步了解程序集,我们
再来做一个试验,使用VB.NET创建一个控制台应用程序项目(ConsoleAppVB),并生成一个程序集,代码功能和上面用C#创建的项目是一样的
的。

Module Program
Sub Main()
Dim text AsString = "hello, world !"
        Console.WriteLine(text)
EndSub
EndModule

现在,需要一个工具来查看这个程序集的内容,并且与C#项目生成的程序集进行对比。还好,微软已经提供了一个利器——IL DASM(IL
Disassembler,IL反汇编程序)来帮助开发者查看程序集的信息。如果安装了Visual Studio,IL DASM将会随同Visual
Studio一起安装。依次选择开始菜单→ Microsoft Visual Studio 2010 → Microsoft Windows
SDK Tools →IL 反汇编程序(IL DASM)可以启动IL DASM。

打开IL DASM后选择VB.NET项目生成的ConsoleAppVB.exe,可以看到如图6-1所示的界面。

图6-1 IL DASM 运行界面

这部分内容很多,会在下一章“程序集”中进行专门讲述,,这里暂且略过。展开图6-1中的ConsoleAppVB.Program类型,在
Main()方法上双击,会弹出另外一个窗口,显示图6-2中的代码,看上去有点像汇编语言。在这里可以看到熟悉的string
text变量声明及“hello, world !”。

图6-2 方法体的CIL语言描述(VB.NET)

接下来再打开C#项目生成的ConsoleApp.exe,进行同样的操作,在打开Main()方法后会发现其中的代码与图6-2中几乎完全一样,如图6-3所示

图6-3方法体的CIL语言描述(C#)

至此,可以得到一个初步的推断:不管是VB.NET还是是C#,编译之后的程序集都能够用IL DASM打开,因此它们生成的程序集的格式都是相同的;当程序所实现的功能相同时,程序集所包含的CIL代码也是类似的。

现在对上面程序集中所包含的类似汇编的语言做一下介绍,即是本节标题中的CIL(Common Intermediate
Language,公共中间语言)。CIL最初是随着.NET由微软一起发布的,因此之前也叫做MSIL(Microsoft Intermediate

Language),后来进行了标准化,之后便被称做CIL。在一些书或文章中,CIL也会简写为IL,其实都是指同样的东西。为了避免混淆,本书统一用
CIL这个缩写。

我们可以将上面的过程用图6-4来表示出来。

图6-4 源程序编译为了程序集

接下来再深入地分析一下,公共中间语言这个术语到底包含了哪几层含义。

  • 公共。因为不论是C#语言也好,VB.NET语言也好,C++/CLI语言也好,甚至是重新开发的一套以自己的名字缩写命名的语言,只要它期望运行的目标平台是.NET,在经过相应的编译器编译之后,所生成的程序集就是由CIL语言代码描述的。
  • 中间。这个词也是大有深意,为什么不叫公共机器语言(Common Machine Language),或者公共本地语言(Common
    Native
    Language)?因为这种语言只是比我们使用的高级语言,比如C#低级一点,并不是CPU可以直接执行的本地机器语言。这种语言还需要.NET运行时
    (.Net
    runtime)环境的支持,在执行之前,进行一个被称为Just-in-time(即时)的二次编译过程,才能转变成计算机可以识别的指令。关
    于.NET运行时,以及详细过程后面再介绍,现在只要知道,这个文件所包含的CIL代码并非机器可以直接执行的指令代码。
  • 语言。CIL不过是一种程序语言,只不过相对于C#来说,它是一种更低级语言。从图6-2
    的代码截图中,已经可以看到,CIL是一种基于堆栈的语言,同时,它提供了class、interface、继承、多态等诸多面向对象的语言特性,因此它
    又是完全面向对象的语言。如果愿意,甚至可以直接编写CIL代码,并且使用CIL的编译工具IL ASM(IL
    Assembler,IL汇编程序)来对它进行编译。只不过,和大多数低级语言一样,这种方式会使开发效率会变得很低。这里注意区别一下IL
    ASM和IL DASM,它们的拼写是不同的。

为了加深一下印象,我们来做一个试验:编写一段简单的CIL代码,并且使用IL ASM工具对其进行编译,得到和前面一样的ConsoleApp.exe程序。

1)打开记事本程序,输入下面的代码,然后将其保存在D:\ConsoleApp.il。

.assembly extern mscorlib{}
.assembly ConsoleApp{}
.module ConsoleApp.exe
.class public auto ansi Program extends System.Object
{
    .method public static void Main()
    {
        .entrypoint
        nop
        ldstr "Hello, World!"
        call void [mscorlib]System.Console::WriteLine(string)
        nop
        ret
    }
}

2)打开Visual Studio 2010命令行工具,输入:

D:\>ilasm ConsoleApp.il

3)成功后会看到ConsoleApp.exe程序,它的执行结果和上面用C#编写的完全一样。

由于程序集是由CIL语言所描述的,因此CIL也叫做程序集语言(Assembly
Language)。又因为.NET程序集需要由.NET运行时加载才能运行,可以视其为由.NET运行时进行管理的,所以CIL代码也叫做托管代码
(Managed Code)。相对的,不需要.NET运行时就可以执行的代码就叫做非托管代码(Unmanaged Code)。

好了,已经知道了CIL的存在,从现在开始,最好在头脑里建立起两个模型或两种视角:一种是基于C#或其他高级语言的源程序的视角,一种是基于
CIL中间语言的程序集视角。C#源程序在被编译为程序集以后,就独立于C#,因此程序集可以由其他种类的语言所调用;同时,因为程序集并没有包含本地机
器的指令,所以它与具体的机器类型也分隔开了,可以被装有.NET框架的任何机器运行。

6.3 BCL和FCL

6.3.1 BCL——基类库

我们先来看一个有意思的现象:再次打开前面创建的C#控制台项目(ConsoleApp),然后在解决方案面板下打开“引用”文件夹,如果用的是Visual Studio 2010,并且面向的目标框架是.NET 4.0版本,那么将会看到如图6-5所示的这些引用。

图6-5 解决方案中的“引用”文件夹

在创建项目时并没有做任何额外的操作,那么这些引用显然是在创建项目时自动添加的。为了方便初学者,这里稍微解释一下:要使用(实际上笔者觉得
Consume这个词表达的更贴切)其他开发者所设计的类型,就需要在项目中将该类型所在的程序集引用进来。现在看到的这些程序集引用,都是微软认为很常
用的,几乎是每个项目都会使用到的,所以在创建项目时自动添加了进来,免得开发者再手动进行添加。

但是在这里这些引用不利于我们理解一些内容,所以我们把这些引用全部删除掉,如图6-6所示,然后再次编译程序。

图6-6 删除掉所有的项目引用

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace ConsoleApp {
    classProgram {
        staticvoid Main(string[] args) {
            string text = "Hello, world!";
            Console.WriteLine(text);
        }
    }
}

可能有人会认为,在删掉这些引用之后,编译器将会毫不客气地提示编译错误:未能找到类型或命名空间“System”(是否缺少using指令或程序
集引用?)。可实际上,当编译并运行上面的代码时,程序会正确无误地执行。这是因为我们已经删掉了所有引用的程序集,只定义了一个Program类型,并
没有定义Console类型,所以此时要面对的第一个问题就是:Console类型从哪里来?

Visual
Studio提供了一个快捷的办法使我们可以快速查看类型:将光标定位在Console上,然后按下键盘上的F12,就可以看到Console的类型定
义。在Console类型定义的最上方,可以看到它所在的程序集地址:C:\Program Files\Reference
Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll。

#region 程序集 mscorlib.dll, v4.0.30319
// C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll
#endregion
using System.IO;
using System.Runtime.ConstrainedExecution;
using System.Security;
using System.Text;
 
namespace System {
    public static class Console {
        // 中间略
    }
}

可以看到Console类型来自于mscorlib.dll这个程序集。从上面的实验可以看出,不管我们是否引用mscorlib.dll程序集,
它总是会自动引用进来。这个程序集中所包含的类库,即是本节标题中的BCL(Base Class
Library,基类库)。从名字就可以看出来,这个类库包含的都是些最基本的类型,其本身已经与CIL语言融为一提了,为CIL语言提供基础的编程支
持,以至于该类库已经成为了CLI标准的一部分(后面会介绍,因此也可以说BCL中的类型就是CIL语言的类型,所有面向CIL的语言都能够使用它们。我
们可以使用对象浏览器(Visual
Studio菜单→视图→对象浏览器)来查看mscorlib.dll程序集中都包含了哪些命名空间和类型,如图6-7所示。

图6-7 mscorlib.dll中包含的命名空间

可以看到该程序集下包含的主要是System命名空间,稍微细心一点的读者会发现,在新建项目的时候,还包含了System.dll程序集,并且其中所包含的类型与mscorlib中的类型十分相似。

图6-8 System 程序集

图6-9 System.dll中包含的命名空间

这又是怎么回事呢?实际上,只要点开System命名空间就会发现,mscorlib.dll的System命名空间下面定义的类型和System.dll的System命名空间下面定义的类型完全不同,它们之间并没有冲突之处。

现在就明白了:BCL提供了像Console这样的类型来支持开发者编写类似控制台这样的程序。

既然已经思考了这么多,不妨再深入一下,思考这样一个问题:写下的这条语句string text = “hello, world
!”,其中的string从哪里来?从直觉来看,string在Visual
Studio中以深蓝色呈现,属于C#的关键字,那么它应该是C#提供的内置类型。可是,当我们将光标移动到string上并按下F12时,转到
string的定义时,看到的却是下面这样的内容:

#region 程序集 mscorlib.dll, v4.0.30319
// C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll
#endregion
 
using System.Collections;
using System.Collections.Generic;
// 为了节约篇幅,省略了一些using
 
namespace System {
    public sealed class String : IComparable, ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string> {
    // 省略定义
    }
}

注意最上方的程序集地址,再次看到了mscorlib.dll,并且String类型与Console类型一样,同位于System命名空间下。由
此可见,C#的关键字string,不过是BCL中System.String类型的一个别名而已。类似地,VB.NET中的String关键字也是
BCL中的System.String类型的别名。因此,在.NET框架中,语言从本质上来说没有太大的区别,更多的区别是在语法方面。从上面的例子也可
以看出,C#和VB.NET的很多语言能力并不是自己的,而是从CIL“借”过来的这样做也保证了在不同语言中相应类型的行为是一致的。

表6-1列出了几个典型的,不同语言关键字与CIL类型的对应关系。笔者觉得理解重于记忆,所以这里只列出了几个。要了解其他基础类型时,只要将光标移动到类型上,然后再按下F12键就可以了。

表6-1不同语言关键字与CIL类型的对应关系

CIL 类型 C# 关键字 VB.NET关键字
System.Byte byte Byte
Sytem.Int16 short Short
System.Int64 int Integer

从表6-1可以看出,.NET同时也对语言开发者提供支持.如你需要设计一款语言,那么在开发编译器时将语言的关键字映射为CIL中的类型就可以了,也就是说,对自己语言中的一些特殊符号(关键字)进行映射处理,就好像C#中的关键字int和string一样。

大家可能听说过这样一种特殊的类型——基元类型(Primitive
Type)。实际上,讲到这里大家应该已经明白了,那些由编译器直接支持,将语言本身的关键字类型转换为CIL类型的,就叫做基元类型。显然,上面的
byte、int、string都是基元类型。而C#中并没有一个关键字去映射Console,所以我们认为Console只是普通的类类型(Class
Type)。

6.3.2 FCL——框架类库

作为一名.NET程序员,每天都要打交道的就是FCL了(Framework Class
Library,框架类库)。在上一节中介绍了BCL,它是FCL的一个子集。BCL中包含了与编译器及CIL语言关系紧密的核心类型,以及常见开发任务
中都会使用到的类型。而FCL包含的内容极多,仅服务于一种应用场景的子类库就足够写一本书了,这里仅简单对它进行介绍。

从功能上来看,可以将FCL框架类库划分成以下几层。

  • 最内一层,由BCL的大部分组成,主要作用是对.NET框架、.NET运行时及CIL语言本身进行支持,例如基元类型、集合类型、线程处理、应用程序域、运行时、安全性、互操作等。
  • 中间一层,包含了对操作系统功能的封装,例如文件系统、网络连接、图形图像、XML操作等。
  • 最外一层,包含各种类型的应用程序,例如Windows Forms、Asp.NET、WPF、WCF、WF等。

6.4 CTS——公共类型系统

假设要开发一套新的语言,这种语言和C#或VB.NET一样,在编译后也能够生成CIL代码,也可以在.NET环境下运行,那么首先需要什么呢?

根据6.2节所讲述的内容我们知道,要开发的新语言相当于CIL的高级语言版本,所以实际上要做什么并不是由新语言决定的,而是由CIL来决定的。
因此,需要一套CIL的定义、规则或标准。这套规则定义了我们的语言可以做什么,不可以做什么,具有哪些特性。这套规则就称作CTS(Common
Type
System,公共类型系统)。任何满足了这套规则的高级语言就可以称为面向.NET框架的语言。C#和VB.NET不过是微软自己开发的一套符合了
CTS的语言,实际上还有很多的组织或团体,也开发出了这样的语言,比如Delphi.Net、FORTRAN等。

那么CTS具体包括哪些内容呢?在回答这个问题之前我们需要弄清楚一个概念。还是通过一段C#代码来说明,先看下面几行代码:

public class Book {
// 省略实现
}
Book item1 = new Book();
Book item2 = new Book();

对于以上代码,通常是这么描述的:定义了一个Book类,并且创建了两个Book类的实例item1、item2。实际上这只包含了两层含义如表6-2所示。

表6-2 类、类的实例

Book
类的实例 item1,item2

再思考一下就会发现,还有一个更高的层面,那就是Book这个类的类型,我们称之为类类型(Class Type),因此上表可以改成如表6-3所示。

表6-3 类类型、类、类的实例

类类型 class
Book
类的实例 item1,item2

类似的,还有枚举类型(Enum Type)、结构类型((Struct
Type)等。现在大家应该明白这里要表达的意思了,CTS规定了可以在语言中定义诸如类、结构、委托等类型,这些规则定义了语言中更高层次的内容。因
此,在C#这个具体的语言实现中,我们才可以去定义类类型(Class Type)或者结构类型(Struct Type)等。

同样,可以在Book类中定义一个字段name并提供一个方法ShowName()。实际上,这些也是CTS定义的,它规范了类型中可以包含字段(filed)、属性(property)、方法(method)、事件(event)等。

除了定义各种类型外,CTS还规定了各种访问性,比如Private、Public、Family(C#中为Protected)、
Assembly(C#中为internal)、Family and assembly(C#中没有提供实现)、Family or
assembly(C#中为protected internal)。

CTS还定义了一些约束,例如,所有类型都隐式地继承自System.Object类型,所有类型都只能继承自一个基类。从CTS的名称和公共类型
系统可以看出,不仅C#语言要满足这些约束,所有面向.NET的语言都需要满足这些约束。众所周知,传统C++是可以继承自多个基类的。为了让熟悉C++
语言的开发者也能在.NET框架上开发应用程序,微软推出了面向.NET的C++/CLI语言(也叫托管C++),它就是符合CTS的C++改版语言,为
了满足CTS规范,它被限制为了只能继承自一个基类。

关于上面内容有两点需要特别说明:

1)C#并没有提供Family and assembly的实现,C#中也没有全局方法(Global
Method)。换言之,C#只实现了CTS
的一部分功能。,也就是说,CTS规范了语言能够实现的所有能力,但是符合CTS规范的具体语言实现不一定要实现CTS规范所定义的全部功能。

2)C++/CLI又被约束为只能继承自一个基类,换言之,C++中的部分功能被删除了。,就是说,任何语言要符合CTS,其中与CTS不兼容的部分功能都要被舍弃。

显然,由于CIL是.NET运行时所能理解的语言,因此它实现了CTS的全部功能。虽然它是一种低级语言,但是实际上,它所具有的功能更加完整。C#语言和CIL的关系,可以用图6-10进行表示。

图6-10 C#和CIL的关系

6.5 CLS——公共语言规范

既然已经理解了CTS是一套语言的规则定义,就可以开发一套语言来符合CTS了。假设这个语言叫做N#,它所实现的CTS非常有限,仅实现了其中很少的一部分功能,它与CTS和C#语言的关系可能如图6-11所示。

图6-11 C#、N#和CIL的关系

那么现在就有一个问题:由C#编写的程序集,能够引用由N#编写的程序集吗?答案显然是不能,,虽然C#和N#同属于CTS旗下,但是它们并没有共
通之处。因此,虽然单独的N#或C#程序可以完美地在.NET框架下运行,但是它们之间却无法相互引用。如果使用N#开发项目的开发者本来就不希望其他语
言类型的项目来引用他的项目倒也罢了,但是,如果N#项目期望其他语言类型的项目能够对它进行引用,就需要N#中公开的类型和功能满足C#语言的特性,即
它们需要有共通之处。注意,这句话中有一个词很重要,就是“公开的”(public)。N#中不公开的部分(private、internal、
protected)是不受影响的,可以使用独有的语言特性,因为这些不公开的部分本来就不允许外部进行访问。因此,
如果N#想要被C#所理解和引用,它公开的部分就要满足C#的一些规范,此时,它与CTS和C#语言的关系就会变成如图6-12所示。

图6-12 C#、N#、CIL的关系

如果世界上仅有C#和N#两种语言就好办了,把它们共同的语言特性提取出来,然后要求所有公开的类型都满足这些语言特性,这样C#和N#程序集就可
以相互引用了。可问题是:语言类型有上百种之多,并且.NET的设计目标是实现一个开放的平台,不仅现有的语言经过简单修改就可以运行在.NET框架上,
后续开发的新语言也可以,而新语言此时并不存在,如何提取出它的语言特性?因此又需要一套规范和标准来定义一些常见的、大多数语言都共有的语言特性。对于
未来的新语言,只要它公开的部分能够满足这些规范,就能够被其他语言的程序集所使用。这个规范就叫做CLS (Common Language
Specification,公共语言规范)。很明显,CLS是CTS的一个子集。现在引入了CLS,图6-12的关系图就可以改成如图6-13所示。

图6-13 语言、CLS、CIL的关系

如果利用C#开发的一个程序集的公开部分仅采用了CLS中的特性,那么这个程序集就叫做CLS兼容程序集(CLScompliant
assembly)。显然,对于上面提到的FCL框架类库,其中的类型都符合CLS,仅有极个别类型的成员不符合CLS,这就保证了所有面向.NET的语
言都可以使用框架类库中的类型。

现在,读者又会有一个疑问:上面几段文字中反复出现了一个词———“语言特性”(language
features),满足CLS就是要求语言特性要一致,那么什么叫做语言特性?这里给出几个具体的语言特性:是否区分大小写,标识符的命名规则如何,可
以使用的基本类型有哪些,构造函数的调用方式(是否会调用基类构造函数),支持的访问修饰符等。

那么我们如何检验程序集是否符合CLS呢?.NET为我们提供了一个特性CLSCompliant,便于在编译时检查程序集是否符合CLS。我们来看下面一个例子:

using System;
 
[assembly:CLSCompliant(true)]
 
public class CLSTest {
 
    public string name;
 
    // 警告:仅大小写不同的标识符“CLSTest.Name()”不符合 CLS
    public string Name() {
        return "";
    }
 
    // 警告:“CLSTest.GetValue()”的返回类型不符合 CLS
    public uint GetValue() {
        return 0;
    }
 
    // 警告: 参数类型“sbyte”不符合 CLS
    public void SetValue(sbyte a) { }
 
    // 警告标识符“CLSTest._aFiled”不符合 CLS
    public string _MyProperty { get; set; }
}

可以注意到,在CLSTest类的前面为程序集加上了一个CLSCompliant特性,表明这个程序集是CLS兼容的。但是,有三处并不满足这个要求,因此编译器给出了警告信息。这三处是:

  • 不能以大小写来区分成员,因此字段name和方法Name()不符合CLS。
  • 方法的返回类型和参数类型必须是CLS兼容的,uint和sbyte类型并非CLS兼容,因此GetValue()和SetValue()方法不符合CLS。
  • 标识符的命名不能以下划线“_”开头,因此属性_MyProperty不符合CLS。

还会注意到,编译器给出的只是警告信息,而非错误信息,因此可以无视编译器的警告,不过这个程序集只能由其他C#语言编写的程序集所使用。

6.6 CLR——公共语言运行时

6.6.1 程序集概述

前面提到过:程序集包含了CIL语言代码,而CIL语言代码是无法直接运行的,需要经过.NET运行时进行即时编译才能转换为计算机可以直接执行的机器指令。那么这个过程是如何进行的呢?

接下来我们要了解的就是.NET框架的核心部分:CLR(Common Language
Runtime),公共语言运行时),有时也会称做.NET运行时(.NET
runtime)。在了解CLR之前,需要先进一步学习一下程序集,因为下一节会对程序集进行专门的讲述,这里仅简单介绍一下程序集中对于理解CLR有帮
助的概念。

从直觉上来看,前面以.exe为后缀的控制台应用程序就是一个直接的可执行文件,因为在双击它后,它确实会运行起来。这里的情况和面向对象中的继承有一点像:一台轿车首先是一部机动车、一只猫首先是一个动物,而一个.NET程序集首先是一个Windows可执行程序。

那么什么样格式的文件才是一个Windows可执行文件?这个格式被称做PE/COFF(Microsoft Windows Portable
Executable/Common Object File
Format),Windows可移植可执行/通用对象文件格式。Windows操作系统能够加载并运行.dll和.exe是因为它能够理解PE
/COFF文件的格式。显然,所有在Windows操作系统上运行的程序都需要符合这个格式,当然也包括.NET程序集在内。在这一级,程序的控制权还属
于操作系统,PE/COFF头包含了供操作系统查看和利用的信息。此时,程序集可以表示成如图6-14所示。

图6-14 程序集结构1

在前面提到过,程序集中包含的CIL语言代码并不是计算机可以直接执行的,还需要进行即时编译,那么在对CIL语言代码进行编译前,需要先将编译的
环境运行起来,因此PE/COFF头之后的就是CLR头了。CLR头最重要的作用之一就是告诉操作系统这个PE/COFF文件是一个.NET程序集,区别
于其他类型的可执行程序。

图6-15 程序集结构2

在CLR头之后就是大家相对熟悉一些的内容了。首先,程序集包含一个清单(manifest),这个清单相当于一个目录,描述了程序集本身的信息,例如程序集标识(名称、版本、文化)、程序集包含的资源(Resources)、组成程序集的文件等。

图6-16 程序集结构3

清单之后就是元数据了。如果说清单描述了程序集自身的信息,那么元数据则描述了程序集所包含的内容。这些内容包括:程序集包含的模块(会在第7章介
绍)、类型、类型的成员、类型和类型成员的可见性等。注意,元数据并不包含类型的实现,有点类似于C++中的.h头文件。在.NET中,查看元数据的过程
就叫做反射(Reflection)。

图6-17 程序集结构4

接下来就是已经转换为CIL的程序代码了,也就是元数据中类型的实现,包括方法体、字段等,类似于C++中的.cpp文件。

图6-18 程序集结构

注意,图6-18中还多添加了一个资源文件,例如.jpg图片。从这幅图可以看出,程序集是自解释型的(Self-Description),不再需要任何额外的东西,例如注册表,就可以完整地知道程序集的一切信息。

至此对程序集的简单介绍就先到这里,接下来看一下程序集是如何被执行的。

6.6.2  运行程序集

现在已经了解过了程序集,并且知道程序集中包含的CIL代码并不能直接运行,还需要CLR的支持。概括来说,CLR是一个软件层或代理,它管理
了.NET程序集的执行,主要包括:管理应用程序域、加载和运行程序集、安全检查、将CIL代码即时编译为机器代码、异常处理、对象析构和垃圾回收等。相
对于编译时(Compile
time),这些过程发生在程序运行的过程中,因此,将这个软件层命名为了运行时,实际上它本身与时间是没有太大关系的。有一些朋友在初学.NET的时
候,纠结在了Runtime这个词上,总以为和时间有什么关系,总是不能很好地理解CLR。笔者认为重要的是理解CLR是做什么的,而不用过于关注它的名
称。

实际上,CLR还有一种叫法,即VES(Virtual Execution
System,虚拟执行系统)。从上一段的说明来看,这个命名应该更能描述CLR的作用,也不容易引起混淆,但是可能为了和CIL、CTS、CLS等术语
保持一致性,最后将其命名为了CLR。在这里,我们知道CLR不过是一个.NET程序集的运行环境而已,有点类似于Java虚拟机。VES这个术语来自于
CLI,会在6.7节进行讲述。

可以用图6-19来描述CLR的主要作用。

图6-19 CLR的主要作用

前面已经概要地了解了CLR的作用,接下来开始更进一步的学习。首先遇到的问题就是:CLR以什么样的形式位于什么位置?

由于CLR本身用于管理托管代码,因此它是由非托管代码编写的,并不是一个包含了托管代码的程序集,也不能使用IL
DASM进行查看。它位于C:\%SystemRoot%\Microsoft.NET\Framework\版本号下,视安装的机器不同有两个版本,一
个是工作站版本的mscorwks.dll,一个是服务器版本的mscorsvr.dll。wks和svr分别代表work
station和server。

接下来再看一下CLR是如何运行起来的。虽然从Windows Server 2003开始,.NET框架已经预装在操作系统中,但是它还没有集成为操作系统的一部分。当操作系统尝试打开一个托管程序集(.exe)时,它首先会检查PE头,根据PE头来创建合适的进程。

接下来会进一步检查是否存在CLR头,如果存在,就会立即载入MsCorEE.dll。这个库文件是.NET框架的核心组件之一,注意它也不是一个
程序集。MsCorEE.dll位于C:\%SystemRoot%\System32\系统文件夹下所有安装了.NET框架的计算机都会有这个文件。大
家可能注意到了,这个库安装在System32系统文件夹下,而没有像其他的核心组件或类库那样按照版本号存放在C:\%SystemRoot%
\Microsoft.NET\Framework\文件夹下。这里又存在一个“鸡生蛋问题”:根据不同的程序集信息会加载不同版本的CLR,因此加载
CLR的组件就应该只有一个,不能再根据CLR的版本去决定加载CLR的组件的版本。

MsCorEE.dll是一个很细的软件层。加载了MsCorEE.dll之后,会调用其中的_CorExeMain()函数,该函数会加载合适版
本的CLR。在CLR运行之后,程序的执行权就交给了CLR。CLR会找到程序的入口点,通常是Main()方法,然后执行它。这里又包含了以下过程:

  1. 加载类型。在执行Main()方法之前,首先要找到拥有Main()方法的类型并且加载这个类型。CLR中一个名为Class
    loader(类加载程序)的组件负责这项工作。它会从GAC、配置文件、程序集元数据中寻找这个类型,然后将它的类型信息加载到内存中的数据结构中。在
    Class
    loader找到并加载完这个类型之后,它的类型信息会被缓存起来,这样就无需再次进行相同的过程。在加载这个类以后,还会为它的每个方法插入一个存根
    (stub)。
  2. 验证。在CLR中,还存在一个验证程序(verifier),该验证程序的工作是在运行时确保代码是类型安全的。它主要校验两个方面,一个是元数据是正确的,一个是CIL代码必须是类型安全的,类型的签名必须正确。
  3. 即时编译。这一步就是将托管的CIL代码编译为可以执行的机器代码的过程,由CLR的即时编译器(JIT
    Complier)完成。即时编译只有在方法的第一次调用时发生。回想一下,类型加载程序会为每个方法插入一个存根。在调用方法时,CLR会检查方法的存
    根,如果存根为空,则执行JIT编译过程,并将该方法被编译后的本地机器代码地址写入到方法存根中。当第二次对同一方法进行调用时,会再次检查这个存根,
    如果发现其保存了本地机器代码的地址,则直接跳转到本地机器代码进行执行,无需再次进行JIT编译。

可以看出,采用这种架构的一个好处就是,.NET程序集可以运行在任何平台上,不管是Windows、UNIX,还是其他操作系统,只要这个平台拥有针对于该操作系统的.NET框架就可以运行.NET程序集。

6.7 CLI——公共语言基础

CLI是一个国际标准,由ECMA和ISO进行了标准化,全称为Common Language
Infrastructure(公共语言基础)。它只是一个概念和汇总,实际上本章的每一小节都是这个标准的一部分。CLI包括:CIL、CTS、
CLS、VES、元数据、基础框架。

看到这里很多人会感觉到有点奇怪,为什么CLI和.NET框架包含的内容如此雷同?它们之间是什么关系?简单来说,CLI是一个标准,而.NET框
架是这个标准的具体实现。在CLI中,并没有CLR的概念,只有VES,而CLR就是.NET框架中VES的具体实现。既然CLI只是一个标准,
而.NET框架是它在Windows平台上的具体实现,那么是不是就只有.NET框架这一个CLI的实现?显然不是,Mono
Project就是CLI标准的另一个实现。Mono Project的目标就是将.NET框架多平台化,使其可以运行在各种平台上,包括Mac
OS、Linux等。

CLI的详细信息可以在这里查看:http://www.ecma-international.org/publications/standards/Ecma-335.htm,感兴趣的朋友可以将它的PDF标准文档下载下来看一下。

6.8 本章小结

本章系统的学习地介绍了一下.NET框架的底层知识,几乎包含了常见的所有术语,例如程序集、CIL、CTS、CLS、CLR等,同时也介绍了它们之间是如何相互协作共同构建起整个.NET平台的。相信经过本章的学习,大家会对.NET框架有一个更好的全局性认识。

感谢阅读,希望这篇文章能给你带来帮助。

.NET 框架 (转载)的更多相关文章

  1. Executor框架(转载)

    Executor框架是指java 5中引入的一系列并发库中与executor相关的一些功能类,其中包括线程池,Executor,Executors,ExecutorService,Completion ...

  2. 94、EventBus框架 ---- 转载

    EventBus使用之基础 http://blog.csdn.net/yanbober/article/details/45667363 EventBus框架库代码走读  http://blog.cs ...

  3. java 开源缓存框架--转载

    原文地址:http://www.open-open.com/13.htm  JBossCache/TreeCache  JBossCache是一个复制的事务处理缓存,它允许你缓存企业级应用数据来更好的 ...

  4. react-navigation实现页面框架(转载)

    初始化一个RN项目 react-native init page_framework page.json { "name": "page_framework", ...

  5. iOS-动画之CoreAnimation框架(转载)

    一.简介 iOS动画主要是指Core Animation框架.官方使用文档地址为:Core Animation Guide.Core Animation是iOS和macOS平台上负责图形渲染与动画的基 ...

  6. struts2框架 转载 精华帖

    一.Struts2简介 参考<JavaEE 轻量级框架应用与开发—S2SH> Struts框架是流行广泛的一个MVC开源实现,而Struts2是Struts框架的新一代产品,是将Strut ...

  7. Thinking in java基础之集合框架(转载)

    集合简介(容器)把具有相同性质的一类东西,汇聚成一个整体,就可以称为集合,例如这里有20个苹果,我们把每一个苹果当成一个东西(一个对象),然后我们借用袋子把这20个苹果装起来,而这个袋子就是集合(也叫 ...

  8. .net 开源框架--转载

    Json.NET http://json.codeplex.com/ Json.Net 是一个读写Json效率比较高的.Net框架.Json.Net 使得在.Net环境下使用Json更加简单.通过Li ...

  9. 软件项目功能测试框架(转载自51Testing软件测试)

    测试用例的编写需要按照一定的思路进行,而不是想到哪写到哪,一般测试机制成熟的公司都会有公司自己自定义的测试用例模板,以及一整套的测试流程关注点,当然我们自己在测试生涯中也应当积累一套自己的测试框架,所 ...

随机推荐

  1. MySQL ibdata1撑爆占满磁盘空间

    MySQL主从由于ibdata1占满磁盘空间-->主从失效 因为设置了innodb_file_per_table = 1,ibdata1依旧撑爆占满磁盘空间 主从断的时候,IO线程在连接,SQL ...

  2. mysql 配置主从

    1.选择2个ip,1个为主,1个为从:例:主:192.168.12.76 从:192.168.12.772.在192.168.12.76的my.cnf 配置master,添加如下:(红色为添加的内容) ...

  3. advance 模板 怎么生成module

    advance 模板 怎么生成module namespace写什么如果是前台呢就是 frontend\modules\modulename\Module@我叫红领巾 module id有什么用bak ...

  4. MVC与WebForm的一些区别

    MVC与WebForm的一些区别 它们都是ASP.NET WEB开发的两种方式 .但是他们也是有一些不同.做个小结. 1.MVC是没有服务器端控件这么一说的,也就是没有viewstate,也就不会产生 ...

  5. Android PopupWindow 点击消失解决办法

    1.点击PopupWindow 外部区域时,PopupWindow消失 popMenu = new PopupWindow(getApplicationContext()); popMenu.setW ...

  6. C# testJsonAsXMLNodeAttribute - XML& json & Collections - XmlNode, XmlElement, XmlAttribute,Dictionary,List

    testJsonAsXMLNodeAttribute using Newtonsoft.Json; using System; using System.Collections.Generic; us ...

  7. BestCoder Round #2

    TIANKENG’s restaurant http://acm.hdu.edu.cn/showproblem.php?pid=4883 竟然暴力1.44*10^7  还要*T=100  竟然过了 # ...

  8. 引擎设计跟踪(九.10) Max插件更新,地形问题备忘

    最近没有大的更新. 最近本来要做max的骨骼/动画导出, 看导出插件代码的时候, 突然想起之前tagent space导出的疑问, 于是确认了一下. http://www.cnblogs.com/cr ...

  9. MySQL 5.7原生JSON格式支持

    在MySQL与PostgreSQL的对比中,PG的JSON格式支持优势总是不断被拿来比较.其实早先MariaDB也有对非结构化的数据进行存储的方案,称为dynamic column,但是方案是通过BL ...

  10. tomcat 解析(五)-Tomcat的核心组成和启动过程

    声明:源码版本为Tomcat 6.0.35 前面的文章中介绍了Tomcat的基本配置,每个配置项也基本上对应了Tomcat的组件结构,如果要用一张图来形象展现一下Tomcat组成的话,整个Tomcat ...