CLR(Common Language Runtime)公共语言运行时:是一个可由多种编程语言使用的"运行时"。
所有面向CLR的语言都可以使用CLR的核心功能,如:内存管理,程序集加载,安全性,异常处理,线程同步等。CLR使用异常来报告错误,这些语言都能通过异常来报告错误。CLR允许创建线程,这些语言也能创建线程。
不同语言有自己的编译器,这些编译器可以被看成是语法检查器和"正确代码"的分析器。
不同语言都有自己的优势:非托管C/C++可对系统进行一些相当低级的控制,可完全按自己的想法管理内存,在必要时能够方便的创建线程。VB6可以快速生成UI应用程序,并可以方便快捷的控制COM和数据库。APL语法有利于处理数学或金融应用程序。
而现在除了以上语言,还有上百种语言都有了面向CLR的编译器了,所以你可以在一个程序中使用不同的语言来处理不同的事务。
这些面向CLR的语言都将被其编译器编译成一种比任何机器语言都高级的面向对象的机器语言IL(中间语言代码,一种托管代码,因为其运行需要CLR,该语言是微软请教了外面几个商业及学术性语言/编译器的作者之后费劲心思开发出来的)。
前面说了面向CLR的语言能实现CLR的核心功能,但不能实现CLR的全部功能。每种语言实现CLR的功能都不一样,但IL可以实现CLR的全部功能,如下图:
下面我们来讨论一下执行.NET程序的基本过程,首先当你运行一个.NET程序集(这里一般指.EXE程序)的时候,由于该程序集是由IL代码写的,所以肯定要用到CLR。首先Windows会检查该程序集的PE头(这个会在稍后讲)来判断该程序集是32位的还是64位的,然后创建32位或64位的进程,之后会在该进程地址空间中加载相应位的MSCorEE.dll,进程的主线程会调用MSCorEE.dll中的一个方法,该方法会初始化CLR,之后加载该EXE程序集,调用其入口Main方法,随即该托管的程序启动并运行。
下面我们来看一下执行.NET程序中的代码是怎么样的,如下图(左面是第一次运行时,右面是第二次....第N次运行时):
.NET程序第一次运行的时候JIT(just in time)即使编译器会将IL托管代码编译成本地CPU指令存储在动态分配的内存当中,然后执行本地CPU指令。但第二次或以上次执行该程序时,就不需要再次经JIT编译器编译了,将直接执行本地CPU指令。一旦程序终止,编译好的本地CPU指令也会被丢弃。
上面讲到了PE头,说到PE头我们就不得不提到托管模块(后缀名是.netmodule),它其实也是由语言编译器编译而来,与程序集(可以是GUI,CUI或DLL 后缀名是.exe或.dll)的不同之处就是它没有清单。 换言之就是,程序集是有清单的托管模块,并且一个程序集可由多个托管模块及其他资源文件(.jpg,.doc等)组成。默认情况下,语言编译器会把生成的托管模块转换成程序集。
利用语言编译器或微软提供的AL.exe(程序集连接器)来将托管模块合并成程序集,另外还可以用AL.exe将资源文件合并到程序集中。
既然托管模块和程序集差不多,我们就以程序集为例来讲一讲其内部构造:
哦,对了 托管模块是一个标准的windows32位/64位可移植执行体文件,简称PE32文件/PE32+文件
暂且除去清单不说(就是先说PE文件,呵呵)
PE文件由4部分组成 PE头 CLR头 元数据 IL
PE头:windows要求的标准信息,上面提到过了
CLR头:包含CLR的版本号,一些标识(flag),一个MethodDef token,一个可选的强名称数字签名
IL:这个上面也提到了
元数据:我们主要来讲讲元数据,它是一个二进制数据块,有几类表组成,如下:
定义表
ModuleDef:用于标识模块,包含模块的文件名和扩展名,一个模块版本ID
TypeDef:记录模块中定义的类型
MethodDef:记录模块中定义的方法
FieldDef:记录模块中定义的字段
ParamDef:记录模块中定义的参数
PropertyDef:记录模块中定义的属性
EventDef:记录模块中定义的事件
引用表
AssemblyRef:记录模块中引用的程序集
ModuleRef:有些引用的类型有可能是别的模块实现的,所以这里记录这些模块
TypeRef:记录模块中引用的类型
MemberRef:记录模块中引用的成员(包括字段,方法,属性,事件)
我们再来看看程序集的元数据中独有的清单表
AssemblyDef:记录了程序集的名称,版本,语言文化,一些标识(flag),哈希算法,发布者的公钥
FileDef:记录了合并到程序集中的PE文件,资源文件,数据文件
MainfestResourceDef:记录了资源的名称,一些标识,FileDef表的一个索引
ExportedTypesDef:记录了所有public类型
AssemblyRef:记录了所有引用的程序集
下面让我们分别使用语言编译器生成PE托管模块 和 程序集
CSC /t:module helloword.cs
CSC /t:library /addmodule:helloword.netmodule hiworld.cs
关于程序集差不多先介绍到这里了
CTS(common type system)通用类型系统 是一套规范 所有语言都必须遵循这些规范
如CTS规定了单继承 规定了所有类型都从System.Object继承 等
CLS(common language specification)公共语言规范 这也是一套规范 但范围比CTS小的多 甚至比某语言的规范范围还小 因为这套规范就是为了支持不同语言的相互操作而定制的 如某语言要使用另一种语言所创建的对象
因为不同的语言差别很大 如有的语言不区分大小写 有的不支持无符号整数 不支持操作符重载 或不支持参数数量可变的方法等
所以要定制出一套规范 使得所有语言都支持 这样才能实现不同语言之间的相互访问
如上图,如果你要写一个可以和任何面向CLR的语言交互的程序就要符合CLS规范,但同时你要受到很大的限制
using System;
[assembly:CLSCompliant(true)] 告诉编译器检查CLS相容性
namepace CommonLangage{
public UInt32 abc() {return 0;} 警告:返回类型不符合CLS (因为有的语言不支持无符号整数值)
}