为你解疑:CLR是什么?

很多人未曾听说过CLR,也有一大部分人不能正确的理解CLR,也许理解的人也不是很透彻,本文笔者为了解决大家的疑惑,要向大家详细介绍CLR是什么。

1、CLR是什么之理解CLR

.NEt提供了一个运行时环境,叫做公用语言运行时,它管理着代码的执行,并使得开发过程变得更加简单。这是一种可操控的执行环境,其功能通过编译器与其它工具共同展现,你的代码将受益于这一环境。依靠一种以运行时为目标的(指完全支持运行时环境的)编译器所开发的代码叫做可操控代码。

它得益于可操控环境的各种特性:跨语言集成、跨语言异常处理、增强的安全性、版本处理与开发支持、简单的组件交互模型以及调试服务。为了使运行时环境能够向可操控代码提供服务,语言编译器需要产生一种元数据,它将提供在你使用语言中的类型、成员、引用的信息。

元数据与代码一起存储,每个可加载的CLR映像均包含了元数据。运行时环境使用元数据定位并载入类,在内存中展开对象实例,解决方法调用,产生本地代码,强制执行安全性,并建立运行时环境的边界。

运行时环境自动处理对象的展开与引用,当它们不再使用时负责它们的释放。被运行时环境进行这样的生命周期管理的对象被称为可操控代码。自动内存管理消除了内存溢出,同时也解决了其它一些常见的语法错误。

如果你的代码是可操控的,你仍然可以在需要的时候使用非可控代码,或者在你的.NET应用中同时使用可控与非可控代码。由于语言编译器支持他们自己的类型,比如一些原始类型,你可能并不总是知道(也不必知道)你的数据是否是可控的。

CLR使设计跨语言的组件与应用变得更加容易。以不同语言设计的对象能够彼此间进行通信,并且它们的行为能够紧密地综合与协调。举个例子,你定义了一个类,然后可以在另一种不同的语言中从该类中派生了一个类或者调用它其中的一个方法。

你也可以向另一种语言中类的方法传递该类的一个实例。这种跨语言的集成之所以可能,因为以运行时间为目标的语言编译器与工具使用一种运行时间所定义的公用类型系统,他们遵守运行时的规则(公用语言规范)来定义新的类型,生成、使用、保持并绑定类型。

作为元数据的一部分,所有可控组件携带了关于它们所依赖的组件与资源的信息。运行时环境使用这些信息来保证你的组件或应用具有需要的所有东西的特定版本,其结果是你的代码将不会因为版本冲突而崩溃。

注册信息与状态数据不再保存在难以建立与维护的注册表中,你所定义的类型及附属信息作为元数据被保存,这使得复制与移动组件的复杂程度得到降低。

编译工具用他们自己的方式向开发人员展现CLR的功能。这意味着运行时间的一些特性可能在不同的语言中表现形式将会有所不同。你怎样体验运行时的特性将取决于你所使用的语言,比如说,如果你是一位VB开发人员,你可能注意到在运行时环境的帮助下,VB语言比以前具有更多的面向对象的特性。

2、CLR是什么之可操控执行的含义

前面的叙述中,我们多次提到了“可操控”这一概念。这意味着它指向的对象在执行过程中完全被运行时环境所控制。在执行过程中,运行时环境提供以下的服务:自动内存管理、调试支持、增强的安全性及与非可操控代码的互操作性,例如COM组件。

在可控执行进程中的第一步是选择源代码的生成工具。如果你希望你的应用拥有CLR提供的优势,你必须使用一种(或多种)以运行时为目标的语言编辑器,例如:VB、C#、VC的编译器,或者一种第三方编译器如PERL或COBOL编译器。

由于运行时是一种多语言执行环境,它支持众多的数据类型和语言特性。你使用的语言编译器决定你将使用运行时的哪一部分功能子集。在代码中使用的语法由你的编译器决定,而不是运行时环境。如果你的组件需要被其他语言的组件完全使用,那么你必须在你组件的输出类型中使用CLR所要求的语言特征。

当你完成并编译你的代码时,编译器将它转换为微软中间语言(Microsoft Intermediate Language,MSIL),同时产生元数据。当你要执行你的代码时,这种中间语言被即时(Just In Time,JIT)编译器编译成本地代码。如果安全策略需要的代码是类型安全的---通常情况下都是如此---JIT编译器将在编译进程中对中间语言进行类型检查。一旦失败,在代码执行中将会触发异常。

3、CLR是什么之CLR的突出特色

跨语言集成的能力

CLR包含了一个丰富的语言特性集,保证了它与各种程序设计语言的兼容性。这一特性集即公用语言规范,稍后将对其进行详细说明。

内存管理自动化

在执行过程中管理应用程序的资源是一项单调而困难的工作。它会将你的注意力从你本应解决的问题中引开。而垃圾收集机制完全解决了程序员在编程过程中头痛的问题,跟踪内存的使用,并知道何时将它们释放。

在面向对象的环境中,每种类型都标识了对你的应用程序有用的某种资源。为了使用这些资源,你需要为类型分配内存。在应用中,访问一种资源要通过以下步骤:

(1)为类型分配内存。

(2)初始化内存,设置资源的初始状态并使其可用。

(3)通过访问该类型的实例成员来访问资源。

(4)卸下将被清除的资源状态。

(5)释放内存。

这一看似简单的过程在实际的编程中是产生错误的主要来源之一。更可怕的是:内存中的错误往往导致不可预见的结果。如果你有过编程的经验,想想看,有多少次你的程序因为内存访问错误而崩溃?

CLR要求所有的资源从可操控的推(注:在此指一种内存结构)中分配。当一个进程被初始化后,CLR保留了一个未被分配的地址空间。这一区域叫做可操控堆。在堆中保持了指向下一个将被分配给对象的堆地址的指针(NEXT)。

初始状态下,该指针是保留地址空间的基地址。一个应用使用新的操作产生对象。此操作首先检查新对象需要字节的大小是否会超出保留空间。如果对象大小合适,指向下一个地址的指针将指向堆中的这个对象,该对象的构造器被调用,新的操作返回对象的地址。

当一个应用请求建立一个对象时,地址空间可能不够大。堆将发现这一点(通过将新对象的大小与NEXT指针相加,并与堆的大小进行比较),这时垃圾收集器就将被调用。在这里,CLR引入了“代”的概念。代,指堆中对象产生的先后。这样,垃圾收集器在将发生溢出时回收属于特定的“代”的对象,而不是回收堆中的所有对象。

(6)即时编译

在各种语言的编译器对源代码进行编译之后,在CLR环境中产生的是中间代码(出于兼容性与跨语言集成的考虑),其内容虽然有效,但在转化为本地代码之前它本身是不可执行的。这就是JIT编译器需要完成的工作。

这里需要说明一个问题:为什么要即时编译,而不是一次性的将中间代码文件进行编译?答案很简单:原因在于效率。在大型的应用中,你很少会用到程序的全部功能,这种边执行边编译的措施比一次性的完全编译效率更高。

在Windows平台中,CLR带有三个不同的JIT编译器:

(7)缺省的编译器---主编译器,由它进行数据流分析并输出经过优化的本地代码,所有的中间代码指令均可被它处理。

(8)PREJIT,它建立在主JIT编译器之上。其运行方式更象一个传统的编译器:每当一个.NET组件被安装时它就运行。

(9)ECONOJIT,在并不充分优化的前提下,它能够快速完成IL代码到本地码的转换,编译速度与运行速度都非常快。

为了配合编译器的工作,在.NET SDK的安装路径下的/bin目录中有一个负责管理JIT的应用程序:jitman.exe。具体的使用参见联机帮助。

(10)解决版本与发布问题。

在当前以组件为基础的系统中,开发人员和用户对于软件版本和发布中存在的问题已经十分熟悉了。当我们安装一个新的应用之后,我们很可能发现原本正常的某个应用程序奇怪的停止了工作。绝大多数开发人员将时间花在了确保所有注册表入口的一致性,以便激活COM类上。这就是所谓的“DLL地狱”。

.NET平台通过使用集合来解决这一问题。在这里,“集合”是一个专有名词,指类型与资源的发布单元,在很大程度上它等同于今天的DLL。正象.NET用元数据描述类型一样,它也用元数据描述包含类型的集合。

通常说来,集合由四个部分组成:集合的元数据(集合的内部清单)、元数据描述的类型、实现类型的中间语言代码和一组资源。在一个集合中,以上四个部分并不是都必须存在,但是,集合中必须包含类型或资源,这样集合才有意义。

在.NET中一个基本的设计方针是使用孤立的组件。一个孤立的集合的含义是指一个集合只能被一个应用所访问。在一台机器上,它不被多个应用共享,也不会受其它应用程序对系统的更改的影响。“孤立”赋予了开发人员在自己的程序中对代码的完全控制权。

任何共享代码都需要被明确地标识。同时,.NET框架也支持共享集合的概念。一个共享集合指在一台机器上被多个应用共享的集合。共享集合需要严格地命名规定。

相关推荐