科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件从.NET类库代码来看ASP.NET运行模式

从.NET类库代码来看ASP.NET运行模式

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

虽然本文的重点是对托管代码的解析,但为了整个知识点的完整性,这里简单介绍一下IIS处理请求的一些基本情况。

作者:EagleFish 来源:论坛整理 2007年11月5日

关键字: 类库代码 ASP.NET 运行模式 Windows

  • 评论
  • 分享微博
  • 分享邮件
写在前面的话:网上讲Asp.net运行模式的好文章已经很多了,笔者本不用多此一举,另成一文。但从笔者自己的学习经验看,如果学到的这些知识不能对应到类库中的源代码,印象总归不够深刻,大有隔靴搔痒之感。只好自己写上一篇,对这方面的知识做个小小的总结。文中所有内容都是笔者在看了网上很多文章后,结合自己的开发经验得出的一些理解,难免有错误的地方,欢迎批评指出。另外,由于笔者能力所限,很多地方并未说透(真正对应到代码),也盼高手能够给予补充。

  一.进入Asp.net运行时之前

  虽然本文的重点是对托管代码的解析,但为了整个知识点的完整性,这里简单介绍一下IIS处理请求的一些基本情况。在一个IIS服务器上,你可以设置多个应用程序池(每个应用程序池可以单独设置允许使用的最大内存数量、CPU使用率、回收工作进程的时间间隔等参数,而且一个应用程序池里面只能使用一个版本的.NET Framework),然后把自己的Web应用分别部署到这些应用程序池中。在默认情况下,每个应用池会有一个工作进程w3wp.exe来维护(如果开通了Web园功能,也可以设置多个工作进程)。每个应用程序(虚拟目录)在池中都有自己的应用程序域,这些应用程序域都处于这个应用程序池的工作进程的进程空间内。

  IIS是通过各种ISAPI的扩展来处理各种类型的应用的。当我们从客户端提交一个请求过来之后,IIS会根据请求的页面或者服务的类型,把请求映射到指定的ISAPI扩展。比方说,如果我们需要让IIS支持perl这样的服务器端程序(当然,这个移植工作早就有人做过了),我们就需要编写一个专门处理对perl页面进行的请求的ISAPI扩展。根据ISAPI的定义(符合这个定义的ISAPI扩展才能和IIS正常交互),在你的扩展中可以包括ISAPI Extension和ISAPI Filter两大部分。ISAPI Extension是对请求的处理程序,完成和web服务器之间的输入输出;而ISAPI Filter则是一些回调接口,你可以通过实现这些接口来介入到整个请求处理的每一步骤,对Authentication,RevolveCache等环节进行控制。另外,ISAPI本身就是在工作进程里运行的,而asp.net运行时也是在工作进程里运行的,所以两者的交互非常有效率。

  对于.aspx页面,这个扩展就是aspnet_isapi.dll。因为这些ISAPI都是非托管的Win32应用,直接对它们进行改动是比较困难的。所以,为了增强Asp.net运行时的可扩展性,aspnet_isapi.dll本身的功能非常少,我们可以把aspnet_isapi.dll简单理解为请求信息的路由器,负责把请求从IIS传送到asp.net运行时。而后面我们将要讲到的HttpHandle和HttpModule则分别担负起了ISAPI Extension和ISAPI Filter的功能,幸运的是,HttpHandle和HttpModule可以由纯的托管代码来实现。

  二.从非托管代码到托管代码

  前面说了,aspnet_isapi.dll是非托管代码,而asp.net运行时是托管代码,他们都运行在w3wp.exe工作进程里面,那么两者之间的调用点发生在什么地方呢?在介绍接下来的内容之前必须先介绍一个概念:ECB。ECB的全称是Extension Control Block,它是一个非托管资源包,具有对ISAPI接口完整的访问能力,包含了所有和一个传入请求有关的底层信息,如提交的标单中的数据等等。所以说,asp.net中的托管代码想要访问aspnet_isapi.dll对外提供的接口,就需要通过ECB。其实更准确的来说,是托管代码公布了一个IUnknown类型的接口供aspnet_isapi.dll调用,而aspnet_isapi.dll在调用的时候会把自己的ecb地址传进去。

  明白了ECB的概念,下面我们要介绍一个接口和一个接口的实现类(位于System.Web.Hosting名字空间下),请读者注意笔者在代码中的注释(本文的主要目的就是和大家一起从代码实现的角度来认识整个Asp.net运行时,所以代码里的注释是笔者添加的关键性说明,后面的所有代码段都是这样):

1/**//*InterfaceType(ComInterfaceType.InterfaceIsIUnknown)指明了这个接口将作为 IUnknown 派生接口向 COM 公开,这就使得isapi.dll可以以COM方式调用此接口。*/
2[ComImport, Guid("08a2c56f-7c16-41c1-a8be-432917a1a2d1"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
3public interface IISAPIRuntime
4{
5 void StartProcessing();
6 void StopProcessing();
7 /**//*ProcessRequest方法就是整个处理流程中托管代码和非托管代码的分界点,可以看到里面是以一个IntPtr结构传入了调用方(也就是isapi.dll)的ECB地址*/
8 [return: MarshalAs(UnmanagedType.I4)]
9 int ProcessRequest([In] IntPtr ecb, [In, MarshalAs(UnmanagedType.I4)] int useProcessModel);
10 void DoGCCollect();
11}
12
13/**//*这个类实现了IISAPIRuntime接口。它的实例对象存在于每一个AppDomain中,作为整个Asp.net运行时的入口。*/
14public sealed class ISAPIRuntime : MarshalByRefObject, IISAPIRuntime, IRegisteredObject
15{
16 // Fields
17 private static int _isThisAppDomainRemovedFromUnmanagedTable;
18 private static string s_thisAppDomainsIsapiAppId;
19
20 // Methods
21 [AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.Minimal), SecurityPermission(SecurityAction.Demand, Unrestricted=true)]
22 public ISAPIRuntime();
23 public void DoGCCollect();
24 public override object InitializeLifetimeService();
25 /**//*处理请求的入口点方法,由isapi.dll以COM方式调用*/
26 public int ProcessRequest(IntPtr ecb, int iWRType);
27 internal static void RemoveThisAppDomainFromUnmanagedTable();
28 internal void SetThisAppDomainsIsapiAppId(string appId);
29 public void StartProcessing();
30 public void StopProcessing();
31 void IRegisteredObject.Stop(bool immediate);
32}

  所以,一切都是从aspnet_isapi.dll以COM方式调用了一个ISAPIRuntime对象的ProcessRequest方法开始的。可以多提一句的是,这种调用是异步的,也就是说,aspnet_isapi.dll在调用后会立即返回,但ECB会一直保留下来,直到整个请求被处理完毕之后再释放。

  好,现在我们知道了ISAPIRuntime对象是托管代码的入口点,那么这个对象是什么时候产生的呢?换句话说,w3wp也是一个非瀀?潳楬?托管代码写出的程序,它是在什么时候把.net运行时加载进来的呢?(如果好奇心再强一点,还可以问一问一个工作进程是什么时刻产生并开始运行的,它和应用程序池有着怎样的交互。)完全解释清楚这些问题已经超过了笔者目前的能力范围,还望高人补充或提供资料线索。但目前我们从.net的代码中应该可以推断出,ISAPIRuntime对象和应用程序域是对应的,.net在创建应用程序域的时候,就会创建ISAPIRuntime对象,见下面的创建应用程序域的代码:

  创建应用程序域

1/**//*这是System.Web.Hosting.AppDomainFactory类型的Create方法,它调用的是实际工厂的Create方法。*/
2[return: MarshalAs(UnmanagedType.Interface)]
3public object Create(string module, string typeName, string appId, string appPath, string strUrlOfAppOrigin, int iZone)
4{
5 /**//*实际工厂是一个AppManagerAppDomainFactory类型的对象。*/
6 return this._realFactory.Create(appId, appPath);
7}
8
9/**//*AppManagerAppDomainFactory.Create方法,请看代码内的注释。*/
10[return: MarshalAs(UnmanagedType.Interface)]
11public object Create(string appId, string appPath)
12{
13 object obj2;
14 try
15 {
16 if (appPath[0] == '.')
17 {
18 FileInfo info = new FileInfo(appPath);
19 appPath = info.FullName;
20 }
21 if (!StringUtil.StringEndsWith(appPath, '\\'))
22 {
23 appPath = appPath + @"\";
24 }
25 ISAPIApplicationHost appHost = new ISAPIApplicationHost(appId, appPath, false);
26 /**//*这个方法内部的调用链非常复杂,它一方面创建了一个应用程序域,一方面返回一个ISAPIRuntime对象。具体这个方法究竟是如何创建AppDomain对象的,大家可以用
27 JetBrain来跟踪其调用栈。关于这部分内容更详尽的信息,可参见ASP.NET Internals - The bridge between ISAPI and Application Domains一文。
28 另外,如果您使用JetBrain来调试系统程序集的话,有可能会因为缺少相应pdb文件而不能查看完整调试信息,这里提供一个根据已有程序集,先反汇编成中间码,
29 再重新以调试模式生成dll和pdb文件的方法:
30   1)生成IL文件: ildasm /tok /byt system.web.dll /out=system.web.il
31 2)重新生成PDB/DLL: ilasm system.web.il /DEBUG /DLL /OUTPUT=System.Web.dll*/
32 ISAPIRuntime o = (ISAPIRuntime) this._appManager.CreateObjectInternal(appId, typeof(ISAPIRuntime), appHost, false, null);
33 o.SetThisAppDomainsIsapiAppId(appId);
34 o.StartProcessing();
35 obj2 = new ObjectHandle(o);
36 }
37 catch (Exception)
38 {
39 throw;
40 }
41 return obj2;
42}
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章