进入.NET运行时的真正的入口发生在一些没有被文档记载的类和接口中(译着:当然,你可以用Reflector来查看J).除了微软,很少人知道这些接口,微软的家伙们也并不热衷于谈论这些细节,他们认为这些实现细节对于使用ASP.NET开发应用的开发人员并没有什么用处。
工作进程(IIS5中是ASPNET_WP.EXE,IIS6中是W3WP.EXE)寄宿.NET运行时和ISAPI DLL,它(工作进程)通过调用COM对象的一个小的非托管接口最终将调用发送到ISAPIRuntime类的一个实例上(译注:原文为an instance subclass of the ISAPIRuntime class,但是ISAPIRuntime类是一个sealed类,疑为作者笔误,或者这里的subclass并不是子类的意思).进入运行时的第一个入口就是这个没有被文档记载的类,这个类实现了IISAPIRuntime接口(对于调用者说明来说,这个接口是一个COM接口)这个基于Iunknown的底层COM接口是从ISAPI扩展到ASP.NET的一个预定的接口.图3展示了IISAPIRuntime接口和它的调用签名.(使用了Lutz Roeder出色的.NET Reflector 工具http://www.aisto.com/roeder/dotnet/).这是一个探索这个步步为营过程的很好的方法.
图3-如果你想深入这个接口,打开Reflector,指向System.Web.Hosting命名空间. ISAPI DLL通过调用一个托管的COM接口来打开进入ASP.NET的入口,ASP.NET接收一个指向ISAPI ECB的非托管指针.这个ECB包含访问完整的ISAPI接口的能力,用来接收请求和发送响应回到IIS.
IISAPIRuntime接口作为从ISAPI扩展来的非托管代码和ASP.NET之间的接口点(IIS6中直接相接,IIS5中通过命名管道).如果你看一下这个类的内部,你会找到含有以下签名的ProcessRequest函数:
[return: MarshalAs(UnmanagedType.I4)]
int ProcessRequest([In] IntPtr ecb,
[In, MarshalAs(UnmanagedType.I4)] int useProcessModel);
其中的ecb参数就是ISAPI扩展控制块(Extention Control Block),被当作一个非托管资源传递给ProcessRequest函数.这个函数接过ECB后就把它做为基本的输入输出接口,和Request和Response对象一起使用.ISAPI ECB包含有所有底层的请求信息,如服务器变量,用于表单(form)变量的输入流和用于回写数据到客户端的输出流.这一个ecb引用基本上提供了用来访问ISAPI请求所能访问的资源的全部功能,ProcessRequest是这个资源(ecb)最初接触到托管代码的入口和出口.
ISAPI扩展异步地处理请求.在这个模式下ISAPI扩展马上将调用返回到工作进程或者IIS线程上,但是在当前请求的生命周期上ECB会保持可用.ECB含有使ISAPI知道请求已经被处理完的机制(通过ecb.ServerSupportFunction方法)(译注:更多信息,可以参考开发ISAPI扩展的文章),这使得ECB被释放.这个异步的处理方法可以马上释放ISAPI工作线程,并将处理传递到由ASP.NET管理的一个单独的线程上.
ASP.NET接收到ecb引用并在内部使用它来接收当前请求的信息,如服务器变量,POST的数据,同样它也返回信息给服务器.ecb在请求完成前或超时时间到之前都保持可访问(stay alive),这样ASP.NET就可以继续和它通讯直到请求处理完成.输出被写入ISAPI输出流(使用ecb.WriteClient())然后请求就完成了,ISAPI扩展得到请求处理完成的通知并释放ECB.这个实现是非常高效的,因为.NET类本质上只是对高效的、非托管的ISAPI ECB的一个非常”瘦”(thin)的包装器.
装载.NET-有点神秘
让我们从这儿往回退一步:我跳过了.NET运行时是怎么被载入的.这是事情变得有一点模糊的地方.我没有在这个过程中找到任何的文档,而且因为我们在讨论本机代码,没有很好的办法来反编译ISAPI DLL并找出它(装载.NET运行时的代码)来.
我能作出的最好的猜测是当ISAPI扩展接受到第一个映射到ASP.NET的扩展名的请求时,工作进程装载了.NET运行时.一旦运行时存在,非托管代码就可以为指定的虚拟目录请求一个ISAPIRuntime的实例(如果这个实例还不存在的话).每个虚拟目录拥有它自己的应用程序域( AppDomain),当一个独立的应用(指一个ASP.NET程序)开始的时候ISAPIRuntime从启动过程就一直在应用程序域中存在.实例化(译注:应该是指ISAPIRuntime的实例化)似乎是通过COM来进行的,因为接口方法都被暴露为COM可调用的方法.
当第一个针对某虚拟目录的请求到来时,System.Web.Hosting.AppDomainFactory.Create()函数被调用来创建一个ISAPIRuntime的实例.这就开始了这个应用的启动进程.这个调用接收这个应用的类型,模块名称和虚拟目录信息,这些信息被ASP.NET用来创建应用程序域并启动此虚拟目录的ASP.NET程序.这个HttpRuntime实例(译注:原文为This HttpRuntime derived object,但HttpRuntime是一个sealed类,疑为原文错误)在一个新的应用程序域中被创建.每个虚拟目录(即一个ASP.NET应用程序寄)宿在一个独立的应用程序域中,而且他们也只有在特定的ASP.NET程序被请求到的时候才会被载入.ISAPI扩展管理这些HttpRuntime对象的实例,并根据请求的虚拟目录将内部的请求路由到正确的那个HttpRuntime对象上.
图4-ISAPI请求使用一些没有文档记载的类,接口并调用许多工厂方法传送到ASP.NET的HTTP管道的过程.每个Web程序/虚拟目录在它自己的应用程序域中运行,调用者(译注:指ISAPI DLL)保持一个IISAPIRuntime接口的引用来触发ASP.NET的请求处理.