代码改变世界

.NET(C#) Internals: as a developer, .net framework in my eyes

2010-05-17 01:19 by 吴秦, ... 阅读, ... 评论, 收藏, 编辑

——当我第一次听到Microsoft .NET平台时,我就知道它将续写微软不败的神话。(Jeffrey Richter)

引言

这篇文章我很早很早之前就想写了,本来是想把它作为我开博的第一篇的,但由于种种原因直到现在写出来。本文不是用.NET平台和其余平台(诸如Java)做比较,不去评论孰优孰劣。仅仅是作为一个.NET开发者,介绍一下我眼中的.NET。

1、.NET Framework

.NET Framework包括公共语言运行时(Common Language Runtime,CLR)和框架类库(Framework Class Library,FCL)。

  • CLR是.NET Framework 的基础、核心,提供了包括内存管理、线程管理和远程调用等核心服务。
  • FCL是一个基于面向对象的可重用类型集合,用于支持多种应用的快速开发,诸如ASP.NET Web应用、Windows Form应用、Web Service应用、Windows Presentation Foundation(WPF)应用等。

IC104620 图1、.NET Framework(来自:MSDN)

.NET Framework提供一个托管执行环境、简化开发和部署、整合了多种开发语言。CLR是.NET体系的基础,所有的.NET代码都运行在CLR之上,并且与FCL紧密结合,并由此创建基于.NET的Windows Forms、Web Forms和XML Web Services等应用程序。.NET Framework的下层是操作系统,上层是.NET的高级开发语言(C#、F#等)。

1.1、CLR

CLR(公共语言运行时,Common Language Runtime)和Java虚拟机一样也是一个运行时环境,它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间必要的分离。

为了提高平台的可靠性,以及为了达到面向事务的电子商务应用所要求的稳定性级别,CLR还要负责其他一些任务,比如监视程序的运行。按照.NET的说法,在CLR监视之下运行的程序属于“托管”(managed)代码,而不在CLR之下、直接在裸机上运行的应用或者组件属于“非托管”(unmanaged)的代码。

CLR将监视形形色色的常见编程错误,许多年来这些错误一直是软件故障的主要根源,其中包括:访问数组元素越界,访问未分配的内存空间,由于数据体积过大而导致的内存溢出,等等。

可以使用 C# 语言编写托管代码,C# 语言提供了下列优点:

  • 完全面向对象的设计。
  • 非常强的类型安全。
  • 很好地融合了 Visual Basic 的简明性和 C++ 的强大功能。
  • 垃圾回收。
  • 类似于 C 和 C++ 的语法和关键字。
  • 使用委托取代函数指针,从而增强了类型安全和安全性。函数指针通过 unsafe C# 关键字和 C# 编译器 (Csc.exe) 的 /unsafe 选项可用于非托管代码和数据。

1.2、FCL

.NET Framework 类库是一个由 Microsoft .NET Framework 中包含的类、接口和值类型组成的库。该库提供对系统功能的访问,是建立 .NET Framework 应用程序、组件和控件的基础。为便于语言之间进行交互操作,.NET Framework 类型是符合 CLS 的,并因此可在任何编程语言中使用,只要这种语言的编译器符合公共语言规范 (CLS)。NET Framework 包括的类型执行下列功能:

  • 表示基础数据类型和异常。
  • 封装数据结构。
  • 执行 I/O。
  • 访问关于加载类型的信息。
  • 调用 .NET Framework 安全检查。
  • 提供数据访问、多客户端 GUI 和服务器控制的客户端 GUI。

.NET Framework 提供一组丰富的接口以及抽象类和具体(非抽象)类。可以按原样使用这些具体的类,或者在多数情况下从这些类派生您自己的类。若要使用接口的功能,既可以创建实现接口的类,也可以从某个实现接口的 .NET Framework 类中派生类。.NET Framework 类库提供下列命名空间

 

2、.NET的程序运行

.NET上的程序从源码到执行有以下几个步骤(来自Jeffery Richter的《.NET框架程序设计》):

  • 将源码编译为托管模块;
  • 将托管模块组合为程序集;
  • 加载公共语言运行时CLR;
  • 执行程序集代码。

这几个过程我总结为下图:

运行过程

图2、.NET上的程序运行

关于托管模块与程序集的关系,我理解如下:

CLR实际上不和托管模块打交道,它直接打交道的对象是程序集。程序集由一个或多个托管模块及相关的资源文件逻辑组成。其次,程序集是组件复用,以及实施安全策略和版本策略的最小单位。程序集中有一个托管模块中包含清单(manifest)的数据块,而清单仅仅也是一些元数据表的集合,但是这些表描述了组成程序集所有文件中的公有导出类型,以及一些和程序集相关的资源文件或数据文件。如果程序集只有一个托管模块且没有资源文件,该程序集就是托管模块。

由于CLR不直接和托管模块打交道,所以默认情况下,编译器会将产生的托管模块转换为一个程序集。只有这样程序才能够运行。

2.1、托管模块

托管模块有下列部分组成:

  • PE32或PE32+表头:标准PE文件头,类似于Common Object File Format头。如果头使用PE32格式,文件可以运行在32位或64位的Windows当中。如果头使用PE32+格式,文件必须运行在64位 Windows当中。该头还指名了文件的类型:GUI、CUI或DLL,同时还包含了一个用来指明文件何时创建的时间戳。对于只包含IL代码的模块,PE32(+)中的大量信息都会被忽略。对于包含本地CPU代码的模块,头还包括本地CPU代码的信息。
  • CLR表头:包含标识托管模块的一些信息(可以被CLR或一些工具解析)。包括托管模块所需要的CLR版本号、一些标记、托管模块入口点方法(main方法)的MethodDef元数据标记、以及有关托管模块的元数据(metadata)、资源(resources)、强命名(strong name)、标记(flags)、和其他一些意义不大的信息的位置和大小。
  • 元数据:每个托管模块都包含一些元数据表。元数据表主要分两种:一种用来描述代码中定义的类型和成员。一种用来描述代码中引用的类型和成员。
  • IL代码:编译器在编译源代码时产生的代码。在运行时,CLR将IL编译为本地CPU指令。

2.2、程序集

程序集是CLR操作的对象。程序集由一个或多个托管模块及相关的资源文件逻辑组成。在程序集包含的所有文件中,有一个文件用于保存清单,清单是另外一组元数据表的集合,其中主要包含了程序集中的一部分文件的名称,另外清单文件还描述了程序集的版本、语言文化、发布者、公有导出类型、以及组成该程序集的所有文件。CLR总是先加载包含清单元数据表的文件,然后利用该清单来获取程序集中的其它文件。

使用程序集的原因有:

  • 程序集允许我们分离可重用类型的逻辑表示和物理表示。
  • 可以将类型分别实现在不同的文件中,从而允许在互联网环境中进行增量下载。
  • 可以按需向程序集中添加资源或数据文件。
  • 可以使我们创建的程序集包含一些用不同编程语言实现的类型。

总而言之,程序集是一个可重用、可实施版本策略和安全策略的单元。它允许我们将类型和资源划分到不同的文件中,这样程序集的使用者便可以决定将哪些文件打包一起部署。一旦CLR加载了程序集中包含清单的那个文件,它就可以确定程序集中的其他文件中哪些包含了程序正在引用的类型和资源。任何程序集的使用者仅需要知道包含了清单的文件名。要生成一个程序集,我们必须选择一个托管模块作为清单的保存者。

 

PS:以上仅为个人愚见,定有不当之处,欢迎指正!当中设计的很多概念也没有深入,以及很多概念也没有引出,如元数据没有深入介绍、FCL也没有一个系统的介绍、未介绍到应用程序域等等。当然要完成这些需要大量篇幅,以后会逐步介绍。