文章內容
從上章文章都知道,asp.net是執行在HttpRuntime裡的,但是從CLR如何進入HttpRuntime的,可能大家都不太清晰。本章節就是通過深入分析.Net4的原始碼來展示其中的重要步驟。請先看下圖:
首先,CLR在初始化載入的時候,會載入一個非常重要的類AppManagerAppDomainFactory,這個類是做什麼用的呢?首先這個類繼承了IAppManagerAppDomainFactory介面,而這個介面是是有個可供COM呼叫的Create方法,程式碼如下:
[ComImport, Guid("02998279-7175-4d59-aa5a-fb8e44d4ca9d"), System.Runtime.InteropServices.InterfaceTypeAttribute(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)] public interface IAppManagerAppDomainFactory { #if !FEATURE_PAL // FEATURE_PAL does not enable COM [return: MarshalAs(UnmanagedType.Interface)] #else // !FEATURE_PAL Object Create(String appId, String appPath); #endif // !FEATURE_PAL Object Create([In, MarshalAs(UnmanagedType.BStr)] String appId, [In, MarshalAs(UnmanagedType.BStr)] String appPath); void Stop(); }
我們來細看一下這個AppManagerAppDomainFactory是如何實現這個介面的,首先該類在預設的建構函式裡,獲取了一個ApplicationManager的例項用於在Create方法裡使用。程式碼如下:
[SecurityPermission(SecurityAction.Demand, Unrestricted=true)] public AppManagerAppDomainFactory() { _appManager = ApplicationManager.GetApplicationManager(); _appManager.Open(); }
回到實現介面的Create方法,我們來看最重要的3行程式碼:
ISAPIApplicationHost appHost = new ISAPIApplicationHost(appId, appPath, false /*validatePhysicalPath*/); ISAPIRuntime isapiRuntime = (ISAPIRuntime)_appManager.CreateObjectInternal(appId, typeof(ISAPIRuntime), appHost, false /*failIfExists*/, null /*hostingParameters*/); isapiRuntime.StartProcessing();
程式碼的主要作用,就是通過ApplicationManager的CreateObjectInternal一系列操作,最終獲取ISAPIRuntime的例項,然後讓非託管程式碼呼叫。所以說CreateObjectInternal方法在這裡發揮了至關重要的功能:建立AppDomain,建立HostingEnvironment等一系列操作。
首先來看看AppManagerAppDomainFactory的建構函式,其裡面呼叫的ApplicationManager. GetApplicationManager()方法是一個單例的實現,程式碼如下:
public static ApplicationManager GetApplicationManager() { if (_theAppManager == null) { lock (_applicationManagerStaticLock) { if (_theAppManager == null) { if (HostingEnvironment.IsHosted) _theAppManager = HostingEnvironment.GetApplicationManager(); if (_theAppManager == null) _theAppManager = new ApplicationManager(); } } } return _theAppManager; }
從程式碼看,大家可能有點疑惑,為什麼HostingEnvironment屬性IsHosted為true的時候會呼叫它的靜態方法GetApplicationManager()來獲取ApplicationManager的例項,這是因為ApplicationManager在後續的步驟建立HostingEnvironment物件並初始化的時候,將this自動傳遞給了HostingEnvironment物件例項(稍後在細說這個事情)。
回頭再來看ApplicationManager例項的CreateObjectInternal方法,部分程式碼如下:
// get hosting environment HostingEnvironment env = GetAppDomainWithHostingEnvironment(appId, appHost, hostingParameters); // create the managed object in the worker app domain // When marshaling Type, the AppDomain must have FileIoPermission to the assembly, which is not // always the case, so we marshal the assembly qualified name instead ObjectHandle h = env.CreateWellKnownObjectInstance(type.AssemblyQualifiedName, failIfExists); return (h != null) ? h.Unwrap() as IRegisteredObject : null;
通過程式碼我們可以看到,首先要先得到HostingEnvironment的例項,然後通過該例項的CreateWellKnownObjectInstance方法返回上述Create方法需要的ISAPIRuntime的例項。那我們應該能想到GetAppDomainWithHostingEnvironment有2個作用,其一是先要獲取AppDomain,其二是獲取HostingEnvironment例項,來看看程式碼是否如我們猜想的結果,先來看程式碼:
private HostingEnvironment GetAppDomainWithHostingEnvironment(String appId, IApplicationHost appHost, HostingEnvironmentParameters hostingParameters) { LockableAppDomainContext ac = GetLockableAppDomainContext (appId); lock (ac) { HostingEnvironment env = ac.HostEnv; if (env != null) { try { env.IsUnloaded(); } catch(AppDomainUnloadedException) { env = null; } } if (env == null) { env = CreateAppDomainWithHostingEnvironmentAndReportErrors(appId, appHost, hostingParameters); ac.HostEnv = env; Interlocked.Increment(ref _accessibleHostingEnvCount); } return env; } }
程式碼告訴我們,首先會檢查是否會有已經存在的AddDomain以及相應的HostingEnvironment例項,如果有返回,沒有就會建立一個新的。通過輾轉呼叫,最終來到一個私有方法CreateAppDomainWithHostingEnvironment,在這個300行的私有方法裡,有我們所迷惑已久的東西。
首先會有關於信任級別的程式碼,比如是執行在FullTrust上還是MiddleTrust上,這裡會有相應的處理程式碼,由於我們這次程式碼分析的重點不在這裡,所以具體程式碼就不細說了,來看看我們需要知道的程式碼段:
// Create the app domain AppDomain appDomain = null; // 此處省略很多程式碼 if (isLegacyCas) { appDomain = AppDomain.CreateDomain(domainId, #if FEATURE_PAL // FEATURE_PAL: hack to avoid non-supported hosting features null, #else // FEATURE_PAL GetDefaultDomainIdentity(), #endif // FEATURE_PAL setup); } else { appDomain = AppDomain.CreateDomain(domainId, #if FEATURE_PAL // FEATURE_PAL: hack to avoid non-supported hosting features null, #else // FEATURE_PAL GetDefaultDomainIdentity(), #endif // FEATURE_PAL setup, permissionSet, fullTrustAssemblies /* fully trusted assemblies list: null means only trust GAC assemblies */); }
通過程式碼可以看出,這就是傳說中建立AppDomain的地方,後續所有的東西比如HttpRuntime, HttpContext都是依託於這個AppDomain,這就是為什麼HttpContext為什麼不能在多站點共享,而能安全存在於AppDomain的原因。
繼續往下看,在建立AppDomain的程式碼之後有幾行這樣的程式碼:
Type hostType = typeof(HostingEnvironment); String module = hostType.Module.Assembly.FullName; String typeName = hostType.FullName; ObjectHandle h = null; // 此處省略很多程式碼 h = Activator.CreateInstance(appDomain, module, typeName); // 此處省略很多程式碼 HostingEnvironment env = (h != null) ? h.Unwrap() as HostingEnvironment : null; // 此處省略很多程式碼 if (appDomainStartupConfigurationException == null) { env.Initialize(this, appHost, configMapPathFactory, hostingParameters, policyLevel); } else { env.Initialize(this, appHost, configMapPathFactory, hostingParameters, policyLevel, appDomainStartupConfigurationException); } return env;
這就是建立HostingEnvironment例項的地方,建立例項以後,緊接著會呼叫Initialize方法來進行初始化,然後返回物件例項(注意該方法的第一個引數哦,是this,也就是ApplicationManager例項自身,就解釋了上面我所說的那個為什麼能通過HostingEnvironment的靜態方法GetApplicationManager()來獲取ApplicationManager例項了)。
通過這些程式碼,我們就可以簡單的知道了,如何獲取ISAPIRuntime例項,從而為進入HttpRuntime做準備了。但是,我依然好奇HostingEnvironment的Initialize初始化方法到底都做了什麼,好吧,我們來看看。
OK,瞄到了一行重要的程式碼:
// initiaze HTTP-independent features HttpRuntime.InitializeHostingFeatures(hostingFlags, policyLevel, appDomainCreationException);
該程式碼進入HttpRuntime的靜態方法,接著呼叫HostingInt方法進行一些初始化工作,其中有一行程式碼也是我們需要知道的,如下:
// Initialize the build manager BuildManager.InitializeBuildManager();
該BuildManager的InitializeBuildManager方法,會呼叫自己的Initialize方法進行初始化另外一些工作,其中包括編譯App_Code目錄下所有的.NET原始碼。由上面的一系列介紹我們知道,在一個AppDomain內,只有一個HostringEnvironment,所以該這個BuildManager的Initialize也就只執行一次,從而保證了編譯不出問題(原始碼的註釋也是這麼說的哦)。
另外HostingInit方法裡在初始化失敗的時候,在catch裡有一行非常特殊的程式碼:
_hostingInitFailed = true;
這是說在建立HostingEnvironment失敗的時候,會給HttpRuntime的HostingInitFailed賦值為True。後面的章節所討論的PreApplicationStartMethodAttribute的概念和WebActivator的入口都和這個值有關係,現在先不做討論,後面章節細再說。
好了,回到AppManagerAppDomainFactory的Create方法,在得到ISAPIRuntime的例項,並且執行StartProcessing方法以後,會返回一個ObjectHandle物件給非託管程式碼,其中包括了ISAPIRuntime的例項,程式碼如下:
return new ObjectHandle(isapiRuntime);
非託管程式碼接受ObjectHandle物件以後,要幹什麼呢?我們且看下篇文章的繼續分析。
同步與推薦
本文已同步至目錄索引:MVC之前的那點事兒系列
MVC之前的那點事兒系列文章,包括了原創,翻譯,轉載等各型別的文章,如果對你有用,請推薦支援一把,給大叔寫作的動力。