CLR寄宿
开发CLR时,微软实际上是将其作为一个COM服务器实现在了一个DLL内。也就是说,微软为CLR定义了一个标准的COM接口,并且为该接口和COM服务器分配了GUID。我们安装.NET框架时,表CLR的COM服务器就像其他的COM服务器一样被注册到了Windows的注册表里。
任何Windows应用程序都可以寄宿CLR。我们的非托管宿主(如Windows PE文件格式,其或为一个Windows Exe文件,或为一个动态连接库DLL文件)可以调用CorBindToRuntimeEx函数(其原型定义在MSCorEE.h中)。CorBindToRuntimeEx函数实现在MSCorEE.dll(通常位于Windows\System32目录下)中。该DLL被称作“垫片”,它的职责是判断创建何种版本的CLR,其本身并不包括CLR COM服务器。
1.0版本的.NET框架包含两个版本的CLR COM服务器。MSCorWks.dll文件包含的是工作部版本的CLR COM服务器,该版本在单处理器、工作站环境下有较好的性能。MSCorSvr.dll文件包含的是服务器版本的CLR COM服务器,它在多处理器、服务器环境下有较好的性能。
当调用CorBindToRuntimeEx函数时,非托管宿主可以传入参数来指定创建哪个版本的CLR。其中的版本信息指定了是工作站版本,还是服务器版本,及CLR的版本号。CorBindToRuntimeEx函数在判断到底加载哪个版本的CLR时,除了使用宿主指定的版本信息外,还会搜索另外一些自己需要的信息(如机器中安装了多少个CPU,及安装了哪些版本的CLR)。也就是说,MSCorEE.dll不一定会加载宿主所指定的那个版本的CLR。
默认情况下,MSCorEE.dll会查看托管exe文件,并提取当初生成和测试应用程序时使用的CLR版本信息。但是,我们也可通过应用程序的配置文件覆盖这一默认设置。
<configuration>
<startup>
<requiredRuntime version="v1.0.0.0" safemode="true"/>
</startup>
</configuration>
查看完上面的信息后,MSCorEE.dll会加载相应版本的CLR。如果safemode为false(默认设置),那么MSCorEE.dll将加载最近安装的、与指定版本相兼容的那个版本的CLR。
CorBindToRuntimeEx函数返回的是一个指向非托管ICorRuntimeHost接口的指针。宿主应用程序可通过调用该接口定义的方法来初始化CLR。宿主还可以调用其中的方法告诉CLR加载哪个程序集,及从哪个方法开始执行。
应用程序域简介
当CLR COM服务器被加载到一个Windows进程时,它便开始执行初始化。初始化的部分工作就是创建一个托管堆,该托管堆将用于分配所有的引用对象、及对它们执行垃圾收集。另外,CLR还会创建一个可被加载到当前进程中所有托管类型使用的线程池。在进行这些初始工作的同时,CLR还会创建一个应用程序域。一个应用程序域是一组程序集的一个逻辑容器。CLR初始化时创建的第一个应用程序域称作默认应用程序域,该应用程序域只有在Windows进程中断时才会被销毁。
除了默认的应用程序域外,一个宿主还可以指示CLR创建额外的应用程序域。另外,托管程序集中的代码也可以告诉CLR创建额外的应用程序域。应用程序域有3个非常有用的特点:
在一个进行中运行多个非托管应用程序是很危险的,因为不同的应用程序将能访问彼此的数据和代码,从而使得一个应用程序可以很容易破坏另外一个应用程序。但托管代码却不存在这样的问题,因为托管IL代码是类型安全的,且也是经过验证的,这使得一个应用程序域中的代码不可能破坏另一个应用程序域中的代码。但如果管理员关闭验证过程,允许托管代码调用非托管函数,那以上的所有担保将全部失效,应用程序域也有可能崩溃。
每个应用程序域都有自己的加载器堆,其中维护着自应用程序创建以来被访问过的类型记录。加载器堆中的每个类型都有一个方法表,对于方法表中的每个条止,如果其中的方法至少被执行过一次,那么它将指向被JIT编译后的x86代码。
独立域与中立域
默认情况下,每个应用程序域所加载的程序集都是相互独立的,即使不同的应用程序域加载了相同的一个程序集,该程序集的相关信息亦会每个应用程序域的加载器堆中构建一次。甚至该程序集中定义的一些方法被JIT编译后的代码亦会在每个应用程序域中存在一份。这样做的好处的一个应用程序域可以完全被从进程中卸载下来,而不影响其他应用程序域。
但有时我们也有一些程序集是期望被几个应用程序域所共用的。比如MSCorLib.dll,这是微软创建的一个单模块程序集。该程序集中包含对.Net框架来说所必须的类型。CLR初始化时,该程序集会被自动加载,所有应用程序域会共享该程序集中的类型。为减少资源使用,MSCorLib.dll程序集会以一种中立域的方式被加载。对于以中立域方式加载的程序集来说,CLR会为它们维护一个特殊的加载器堆,以中立域方式加载的程序集只有在进行中断时才会被卸载。
跨越应用程序域边界访问对象
一个应用程序域中的代码可以和另一个应用程序域中的类型和对象相互通信。但是,这样的通信必须通过一种预先定义的机制进行。大多数类型在跨越应用程序域边界时是通过传值方式来进行封送处理的。换句话说,如果我们在一个应用程序域中构造了一个对象,然后又将该对象的引用传递给了另一个应用程序域,那么CLR必须首先将该对象的字段序列化到一个内存块中,然后再将内存块传递给另一个应用程序域,最后再执行反序列化得到新的对象。对于以传值方式进行远程传送的对象来说,对象的类型必须应用有Serializable定制特性。
除应用Serializable定制特性的类型外,继承自System.MarshalByRefObject的类型也可以为对象提供跨越程序域边界的访问能力。这种访问是通过传引用而非传值进行。假设我们在一个应用程序域中创建了一个继承自System.MarshalByRefObject的对象,该对象的引用被传递给一个目的应用程序域时,CLR实际会在目的应用程序域中创建一个代理类型的实例,目的应用程序域中的代码将使用这个代理对象引用。原来的对象仍然驻留在原来的应用程序域中。代理对象实际上是一个封装器,它知道怎样调用原来应用程序域中的对象上的实例方法。
很明显,跨越应用程序域边界访问对象会有一些性能损耗。我们应用尽量避免这样的操作。
应用程序域事件
应用程序域提供了一些非常有用的事件:
AssemblyLoad:每次CLR将一个程序集加载到应用程序域中时被触发。事件处理器接受一个标识被加载程序集的System.Reflection.Assembly对象。
DomainUnload:在应用程序域被卸载之前触发。如果包含应用程序域的进程被中断,该事件将不会被触发。
ProcessExit:在进程中断之前被触发。只会为默认的应用程序域触发。
UnhandledException:在一个应用程序域中出现未处理异常时被触发。
AssemblyResolve:在CLR不能定位应用程序域所需的程序集时被触发。
ResourceResolve:在CLR不能定位应用程序域所需的资源时被触发。
TypeResolve:在CLR不能定位应用程序域中某个程序集所需类型时被触发。
控制台应用程序和Windows窗体应用程序寄宿CLR
当我们调用一个托管控制台应用程序或Windows窗体应用程序时,MsCorEE.dll会检查应用程序的程序集中包含的CLR表头信息。该表头信息指出了生成与测试应用程序时所使用的CLR版本。MSCorEE.dll使用该信息来判断创建哪个CLR COM服务器。在CLR加载并初始化完后,它会再次检查程序集的CLR表头来判断应用程序的入口点方法(Main)。接着,CLR调用该方法,应用程序开始运行。
随着代码运行,它会访问其他类型。当引用到包含在其他程序集中的类型时,CLR会定位必要的程序集,且将其加载到同一应用程序域中。另外间接被引用的程序集也会被加载到同一个应用程序域中。当程序的Main方法返回时,默认的应用程序域将被卸载,Windows进程也随之中断。
如果我们希望关闭Windows进程(包括其中的所有应用程序域),我们可以调用System.Environment的静态方法Exit。Exit是中断一个进行的最佳方式,因为它会首先调用托管堆中所有对象的Finalize方法,然后还会释放所有由CLR维护的非托管COM对象。最后,Exit将调用Win32函数ExitProcess。
除默认应用程序域外,控制台应用程序或Windows窗体应用程序还可以告诉CLR在进程的地址空间上创建额外的应用程序域。但这两类应用程序很少使用或需要多个应用程序域。
ASP.Net Web窗体和XML Web服务应用程序寄宿CLR
ASP.Net是一个ISAPI DLL(实现于ASPNet_ISAPI.dll中)。当客户端请求一个由ASP.NET ISAPI DLL处理的URL时,ASP.NET会创建一个工作者进程(ASPNet_wp.exe)。工作者进程是一个寄宿有CLR COM服务器的Windows进程。
当客户端向一个Web应用程序发出一个请求时,ASP.NET会判断该请求是否为第一次。如果是,ASP.NET会告诉CLR为该Web应用程序创建一个新的应用程序域(每个Web应用程序可以由它的虚拟根目录来判定)。ASP.NET然后告诉CLR将必要的程序集加载到新建的应用程序域中,并创建一个Web应用程序类型的实例,调用其中的方法响应客户端请求。强命令程序集(如System.Web.dll)是以中立域的方式来加载的,因为这样可以节省操作系统的资源。
当更多的客户端向一个已经运行的Web应用程序发出请求时,ASP.NET不会再去创建新的应用程序域,它会使用现有的应用程序域,创建一个新的Web应用程序类型的实例,并调用其中的方法。
如果客户端请求的是另外一个不同的Web应用程序,ASP.NET告诉CLR创建一个新的应用程序域。这意味着许多Web应用程序将运行在同一个Windows进程中,这样做有助于提高整个系统的效率。