进入.NET运行时
进入.NET运行时的真正登录点发生在一些没有正式文档的类和接口之间。在微软外面的世界,这些接口鲜为人知。微软的民间也不太热衷于讨论这些细节,可能是因为他们认为这些,对于使用ASP.NET构建程序的开发者没有太多的影响。
工作进程aspnet_wp.exe(IIS5)和w3wp.exe(IIS6)宿主在.NET运行时里。ISAPI DLL通过底层的COM调用一小撮非托管类型的接口,其实,最终调用的是ISAPIRuntime派生类的实例。进入运行时的第一个登录点是未归档ISAPIRuntime类,它通过COM把接口IISAPIRuntime暴露给调用者。这些COM接口是底层的IUnknown,基于这些接口,就意味着从ISAPI扩展到ASP.NET之间的调用属于内部调用。图3是使用有名的反射工具Refector(http://www.aisto.com/roeder/dotnet/)看到的IISAPIRuntime接口的签名。Refector是一个可以查看和反编译程序集的工具,使用它可以很容易的查看元数据、反编译代码,就像图3中看到的那样。使用它一步一步地探究处理的过程,这是个非常不错的方法。

图3
如果想深入了解底层的接口,打开Refector工具,然后指向System.Web.Hosting命名空间。进入ASP.NET的登录点以一个托管的COM接口出现,该接口将在ISAPI dll里被调用。该登录点接收一个指向ISAPI ECB非托管类型的指针。ECB拥有访问整个ISAPI接口的权限,它可以获取请求的数据以及把返回的数据发回IIS。
IISAPIRuntime接口担当着来自于ISAPI扩展(在IIS6里是直接通信的,在IIS5里间接的通过命名管道通信的)的非托管代码和托管代码之间的桥梁。如果留意一下这个类,就会发现ProcessRequest方法的签名像下面的样子:
[return: MarshalAs(UnmanagedType.I4)]ecb参数是ISAPI扩展控制块(extension control block),它被作为非托管资源传给ProcessRequest方法。此方法将获取ECB,然后把它作为基本的输入和输出接口,用于Request和Response对象。ISAPI ECB包含着所有底层的请求信息,这其中包括服务器变量,用于表单变量(form variables)的输入流,以及用于写数据并把数据发送到客户端的输出流中。一个单独的ECB引用基本上提供了一个ISAPI请求可以访问的所有功能。ProcessRequest既是登录点也是登出点,在这里非托管资源最先与托管代码相联系。
int ProcessRequest([In] IntPtr ecb, [In, MarshalAs(UnmanagedType.I4)] int useProcessModel);
ISAPI扩展以异步的方式处理请求。所以,当ISAPI扩展调用了工作进程或者IIS的线程后,会立即返回,但会为当前有效的请求保留ECB。因此,ECB需要包含这样的机制,即当请求结束的时候通知ISAPI(通过ecb.ServerSupportFunction实现),然后ISAPI扩展释放ECB资源。接着以异步的方式立即释放ISAPI工作线程,和卸载由ASP.NET托管的那个隔离的处理线程。
ASP.NET得到ecb引用后,会在内部使用它来获取当前请求的相关信息,如服务器变量,POST的数据以及返回输出到客户端的数据。Ecb将继续存活直到这个请求结束或者IIS超时,在这之前,ASP.NET将会与ecb继续保持通信。当请求结束的时候,输出的内容会写进ISAPI的输出流里(通过ecb.WriteClient()实现)。然后ISAPI扩展会被通知请求已经结束,让它知道ECB可以被释放了。这个执行过程是非常高效的,这是因为.NET类本质上只是担当着一个相当瘦小的包装器,而它包装的内容就是具有高性能的非托管ISAPI ECB。