深入了解.NET框架
一、
公共语言运行时(common language runtime)
.NET框架提供了一个运行时环境叫公共语言运行时(common language runtime),它管理代码的执行,并使开发过程更加容易。编辑器和工具提供运行时的功能,使你编写的代码在这个受管理的执行环境中编写更好的工作。用一个支持运行时的开发工具所编写的代码被成为受管理的代码(managed code);它有如下特性:跨语言完整性,跨语言例外(exception)处理,提高安全性,版本方法和扩展支持,一个简单的组件交互模型以及调试和描述服务。
为了使运行时提供受管理的代码服务,语言编辑器必须产生一种元语言,它提供描述你代码的类型,成员,引用等信息。元语言和代码一起被保存;每个可装载的共同语言运行时映射包含元语言。运行时用元语言定位和装载类,在内存中展开实例,决定方法的激活,产生本地代码,加强安全性和设置运行时环境边界。
运行时自动处理对象的展开和管理对对象的引用,并在不许要时释放他们。用这种方法管理起来的对象的生命期叫受管理的数据(managed data)。自动内存管理消除了内存泄漏和其他一些程序设计的错误。如果你的代码时受管理的,你可以用受管理的数据,不受管理的数据(unmanaged data)或者两者都在你的.NET应用程序中使用。由于语言编辑器支持他们各自的类型,如初始类型,你并不总是或并不必要知道你的数据是否是被管理的。
公共语言运行时能使你的组件和应用程序设计更加容易,这些组件和应用程序的对象是跨平台交互的。用不同的语言谢的对象能和其他语言写的对象相会交流,并且他们的行为是具有高度完整性的。由于很多支持运行时的编译器和工具使用了一种被运行时定义的叫公共类型系统(common type system)的技术,因而如果你定义了一个类,你可以用另一种语言从你定义的类中导出子类或者调用这个类的方法。你也可以把一个类的实例传递到用其他语言写的类的一个方法中。这种跨平台的完整性是完全可以的。
所有的组件都有关于在它被建立时所需的组件和资源的信息,这些信息是他们的元数据的一部分。运行时用这些信息来确定你的组件或应用程序拥有他们所需要的一切东西的版本信息。这样能使你的代码几乎不可能因为碰到不得预期的依赖而崩溃。注册信息和状态数据不再存储在很难建立和维护的注册表中。更好的途径是关于你的类型(和他们的依赖)的信息作为元数据存储在一起,这样使组件和复制和移动变的简单。
二、
理解受管理的执行(managed excution)
在受管理的执行过程的第一步是设计源程序。如果你想你的应用程序受益于common language runtime(公共语言运行时),你必须使用一个或多个支持运行时的语言编译器,比如visual basic,c#,visual c++或者其他第三方编辑器,比如perl或cobel编辑器。
由于运行时(runtime)是一种多语言执行环境,它广泛的支持多种数据类型和语言特性。你所使用的编辑器决定了将要使用运行时(runtime)哪一部分的功能,这样,你可以使用这些特性设计你的代码。你代码的语法时由编辑器决定的,而不是由运行时决定的。如果你的组件要能够被其他语言编写的组件完全可用,你必须在组件的导出类型中使用这种包括在common language specification(CLS)中的语言特性。
当你的代码被写好,编译好后,编辑器将它翻译成微软中间语言(MSIL),并产生所需要的元数据(metadata)。当你准备执行你的代码时,MSIL就通过Just In Time(JIT)编辑器编译成本地代码(即:适合本机执行环境的代码)。如果安全策略要求代码是安全类型的,JIT编辑器的还要检测IL是否是安全类型(type-safety)的;如果安全类系检查没有通过,当代码执行时,将抛出一个例外(exception)。
在执行过程中,运行时(runtime)提供许多服务,包括自动内存管理,调试支持,加强安全性和使代码能和其他非管理代码(unmanaged code)比如COM组件之间协调工作的能力。下面将介绍更多的关于受管理的执行的详细信息。
1、
微软中间语言(Microsoft Intermediate Language MSIL)
如果你把你的源程序编译成受管理的代码(managed code),编辑器将把你的源代码翻译成微软中间语言(MSIL)。MSIL与具体的CPU指令无关,并能有效的转化为本地代码。MSIL包括很多指令,比如用于转载,存储,初始化,调用对象方法的指令,以及用于算术、逻辑运算,控制流,直接内存访问,例外处理等指令。在代码被执行前,MSIL必须被JIT(Just In Time)编辑器转化成适合特定CPU的代码。由于运行时支持一个和多个用于不同计算机体系结构的JIT编辑器,所以相同的MSIL能够被JIT编译后在任何一台MSIL支持的计算机上运行。
当一个编辑器产生了MSIL后,它也产生了元数据(metadata),它能够描述你的代码中的类型,包括每种类型定义,每种类型成员的识别标志,你的代码应用的成员和其他在代码执行的时候运行时(runtime)所要使用到的数据。MSIL和元数据被包含在PE (Portable Executable)文件中。这种文件是基于和扩展了已经发布了的Microsoft Portable Executable(PE)和Common Object File Format(COFF)。这种包括了MSIL或者本地代码一级元数据的文件格式使操作系统能识别公共语言运行时(Common Language Runtime)映象。在这个有MSIL的文件里,元数据的存在使你的代码能够描述它自身,这就意味着不再需要类型库或者IDL。当在执行过程中,运行时(runtime)能定位和摘取所需要的元数据。
2、
JIT编译
在MSIL被执行之前,它必须被.net框架Just In Time(JIT)编译器转化成本地代码(native code),这是一种与特定CUP有关(CUP-specific)的代码,它能运行在有JIT编辑器的相同体系结构的计算机上。由于运行时提供了一个能支持不同CPU的的JIT编辑器,开发人员就能够写出一套能被JIT编译后,在不同体系结构的计算机执行的MSIL。(如果你的受管理的代码调用了特定平台的本地APIs或者特定平台的类库,你的代码就只能运行在这个特定的操作系统。)
在JIT编辑后,我们可以获得某些代码在执行过程中从来不会被调用的信息。这样,与其消耗许多时间和内存将包含所有的MSIL的PE文件转化成本地代码,不如只将要使用MSIL转化为本地代码。装载器在装载时为每种类型的方法创建一个stub(关于将MSIL转化成本地代码的方法的信息描述,包括方法的入口地址等),并和方法联系起来。在调用方法的初始化时,这个stub将控制传给JIT编译器,编译器把方法的MSIL转化成本地代码并将stub指向本地代码的地方。以后在被JIT编译的指向先前生成的本地代码的方法调用时,就会减少被JIT编译器和执行代码所花费的时间。
在将MSIL转化成本地代码的过程中,代码必须通过一个认证过程(除非管理员已经建立了安全策略,这样就可以将代码绕过认证)。认证检查MSIL和元数据,以便知道代码是否时类型安全的(type-safe),这也就意味着对经过授权的内存进行访问。类型安全对确定对象之间是否时安全独立的很重要。这样就能防止无意和恶意的冲突。类型安全也为代码的安全限制可靠的加强提供了保障。
由于代码时经过安全类型检测的,所以运行时就能保证下面的几条:
.对类型的引用是和正在应有的类型是严格兼容的
.只有正确的定义的操作才能被一个对象调用
.标识符是被申明了的
在认证过程中,检查MSIL代码以便确定代码是否被允许访问的内存地址和是否通过正确定义的类型调用方法。例如,代码不能访问所限定的内存以外的地址。此外,由于错误的MSIL能导致类型安全规则错误,认证要观察代码,看MSIL是否被正确的产生。认证过程只传递类型安全的代码。然而,某些类型安全的代码由于认证过程的限制而不能通过认证,并且有些语言也不能产生可认证的类型安全代码。
3、
执行
公共语言运行时(CLR)提供了一种基础框架使执行和各种在执行过程中所使用的服务一起发生。在一个方法被执行前,它必须被编译成特定处理器的代码。每种被MSIL产生的方法当第一次被调用时,将被JIT编译,然后才执行。在下一次次方法被执行时,已经存在的被JIT编译的本地代码就直接被执行。在执行结束前,JIT编译和执行代码过程将被重复。
在执行过程中,受管理的代码将会收到许多服务,例如自动内存管理,安全控制,和其他非管理的代码的协同工作控制,跨语言的调试支持以及增强扩展和版本支持。关于这些服务的更多信息,请阅读下列内容(可跳过):
(1)代码访问安全
今天高度联网的计算机系统经常被暴露在移动代码(mobile code)下,这些移动代码来源于多处,并在许多环境中使用。移动的,可执行的代码能通过E-mail,文件和INTERNET获得。不幸的使,许多计算机用户都直接经历果恶意的移动代码比如病毒,蠕虫所带来的毁灭性后果。一般,用户和管理员都通过频繁的备份数据,不断升级反病毒软件,建立防火墙来防止恶意的移动代码。尽管有了这些措施,移动代码仍然经常成功损坏,偷窃你的私有数据。
还有一些其他的安全机制在使用,比如登陆信任(通常使口令),限制用户能访问的资源(一般使文件夹和文件)。然而,这些途径也不能解决一下问题:用户获得的移动代码有些是不可靠的,这些代码可能包含BUG和其他一些容易被恶意代码利用的缺陷。而且这些代码经常做一些用户并不知道的事情。结果,计算机系统会被损坏和泄漏用户的私有数据。当使用操作系统安全机制时,每个代码片在被允许执行前必须得到充分的信任,可能除了一些脚本和Web页面。然而,仍然有必要建立一种广泛的安全机制使一台计算机上安全的代码被另一台计算机安全的执行,甚至在两台没有任何信任关系的计算机系统之间。
为了保护计算机系统防止恶意的移动代码的侵袭并提供一种方法使移动代码安全的执行,.NET框架提供了一种叫代码访问安全(code access security)的安全机制.代码访问安全允许代码依靠代码的来源和其他代码识别的观察得到各种程度的信任。代码访问安全也能提高代码信任的程度,进而最大限度减少代码运行时所必须的全部安全信任。使用代码访问安全能减少你的代码被恶意代码利用的可能性。
(2)基于角色的安全
一般,当一台服务器相应远程系统的请求时,用处理程序正确的处理这些请求。
在程序代码被实际调用前,请求可能被一个或多个处理程序处理,这些处理程序有路由处理,表记处理和鉴定处理等。当鉴定程序认证了用户的身份后,服务器开始决定发出这个外部请求的用户是否有权利访问应用程序。为了进行鉴定处理,服务器必须访问被鉴定者的信息。这些信息需要和到来的请求关连,以便信息能被当前正在处理请求的线程检查。
.net框架的基于角色安全机制为鉴定提供支持,即产生可以被当前线程使用的负责人(principal)信息。.net应用程序能作出鉴定决定,这种鉴定决定是基于负责人标识或角色成员关系或两者同时都有的。一个角色是一个被命名的用户集合。他们有与各自安全(如告诉者或管理者)相关的相同的特权。一个负责人可能是一个或多个角色中的一员。这样,应用程序能用角色成员关系决定一个负责人是否有权执行一个请求动作。
角色经常在金融和商业应用程序中作为一种政策执行的手段。例如,一个应用程序可以限制交易大小,在依靠于产生这个请求的用户是否是特定角色中的一员。职员能进行少于最高交易额的交易。管理人员可以进行更高限制的交易,副总裁可以进行更高的甚至没有限制的交易。基于角色安全的另一种情况是在购买系统中,任何一名雇员都能产生一个购买请求,但只有一个购买代理商才能将请求转换成购买订单交给供应商。
在过去,应用程序使用角色和负责人信息用所有者机制处理鉴定。微软用MTS引进了基础框架来支持基于角色的安全,后来,在COM+1.0服务中提供了基于角色的安全。.net框架提供了基于角色安全支持,它是有灵活的,可扩展的,足以满足在很大范围的应用程序的需求。基于角色安全特别适合在ASP.NET web中使用。而且,.net框架的基于角色安全能在客户端或服务端使用。
如果你用过COM+1.0,你可能已经熟悉了基于角色安全在那个环境中的运作。这样,由于.net框架的基于角色安全与COM+1.0安全相似(但更加一般),这对比较两个安全模型可能是有帮助的。COM+1.0安全依靠Windows NT帐户和口令来获得Windows的信任关系。角色被每个应用程序管理,应用程序代码必须被注册在注册数据库中。对于已经注册的应用程序,COM+1.0跟踪与它关连的代码的安全信息并维持在用户帐户和角色之间的映射。使用.net框架的基于角色的安全,负责人不必和Windows NT的帐户和口令联系,一般的普通的基于角色安全的负责人和Windows负责人一样被支持。这样,在计算机系统之间没有潜在的信任关系假设,但在适当的时候,应用程序可以获得这些关系。特定的应用程序角色能被定义,运行时提供允许管理员将操作系统帐户映射到角色,并且支持映射的维护。除了在某些相互操作的情况下,不需要对.net应用程序的注册要求。运行时将调用的内容和负责人联系起来,这样,当前的线程完全对代码进行执行了。负责人保持一个对对一个身份以及该身份所拥有的角色的引用。
由于.net框架的基于角色的安全,COM+1.0不必做什么工作。不过,如果安装了COM+1.0,公共语言运行时的协同工作特性允许使用COM+1.0安全的应用程序和.net应用程序交互。
为了使和代码访问安全的使用和一致性更加容易,.NET框架的基于角色的安全用PrincipalPermission Class对象来使运行时用一种和代码访问安全相似的方式来执行鉴定。这样,你能直接访问负责人的身份并在但需要时你的代码中执行角色和身份检查。