1. 程式人生 > >擋不住的好奇心:ASP.NET 5是如何通過XRE實現跨平臺的

擋不住的好奇心:ASP.NET 5是如何通過XRE實現跨平臺的

好奇號火星車

.NET程式設計師也有自己的幸福,.NET的跨平臺是一種幸福,.NET的開源也是一種幸福,而更幸福的是可以通過開源的.NET瞭解.NET是如何一步步走向跨平臺的,所以幸福是一種過程。

在.NET跨平臺的程序中,ASP.NET顯然走在了前頭,而通過探究ASP.NET 5是如何實現跨平臺的,可以稍稍滿足一下自己的好奇心。

體驗ASP.NET 5跨平臺有2種方式:

1)在Mac下,git簽出XRE的原始碼(前身是KRuntime),然後執行sh build.sh,就能完成整個XRE專案的生成。

這篇博文就從k命令下手,一探ASP.NET 5跨平臺的究竟。

執行k kestrel(即將是dotnet kestrel),實際執行的是下面的命令(根據project.json中的commands配置):

k "Microsoft.AspNet.Hosting --server kestrel --server.urls http://localhost:8002"

Microsoft.AspNet.Hosting是一個.NET控制檯程式實現的OWIN Host(原始碼),kestrel是一個基於libuv用.NET實現的OWIN Server(也是Web Server,原始碼),kestel是由Microsoft.AspNet.Hosting載入的。

既然Microsoft.AspNet.Hosting是一個託管程式,它自己是無法直接執行的。因為執行一個.NET程式的前提條件是CLR已執行,而CLR自己不能執行自己,CLR執行的前提是有一個host程式將它載入。

如果你在Mac下用過Mono,就你就知道執行一個.NET程式需要用mono命令,mono命令的作用就是建立一個程序,載入Mono Runtime(Mono CLR),然後由Mono Runtime執行.NET程式。

而在ASP.NET 5中,並沒有直接用mono命令,而是k命令,自從KRuntime改名為XRE之後,k命令也將會被dotnet命令取代。

dotnet.sh

在非Windows平臺下,k命令對應的是k.sh。現在改為XRE之後,也就是donet命令對應dotnet.sh。所以ASP.NET 5跨平臺的祕密就藏在dotnet.sh中。

下面就直擊XRE專案中的scripts/dotnet.sh:

#...
if [ -f "$DIR/mono" ]; then
  exec "$DIR/mono" $MONO_OPTIONS "$DIR/dotnet.mono.managed.dll" "[email protected]"
else
  exec mono $MONO_OPTIONS "$DIR/dotnet.mono.managed.dll" "[email protected]"
fi

毫無懸念,依然用的是mono。但是用了mono,如何載入.NET Core CLR,難道還是用Mono Runtime?

帶著這個疑問,順藤摸瓜,看dotnet.mono.managed.dll幹了啥。

dotnet.mono.managed.dll的實現程式碼就在XRE專案中,是一個簡單的C#控制檯程式,它幹了兩件事:1)分析命令列引數;2)呼叫RuntimeBootstrapper.Execute():

public class EntryPoint
{
    public static int Main(string[] arguments)
    {
        //...
        arguments = ExpandCommandLineArguments(arguments);
        //...
        return RuntimeBootstrapper.Execute(arguments);
    }
}

繼續順藤摸瓜至 [dotnet.hosting.RuntimeBootstrapper],[RuntimeBootstrapper.Execute()] 呼叫了 [RuntimeBootstrapper.ExecuteAsync()]。

而託管的 [RuntimeBootstrapper.ExecuteAsync()] 竟然拐了個彎,執行了非託管的dotnet命令(一個由dotnet.cpp實現的C++程式)。

mono命令(非託管) -> Mono Runtime -> dotnet.mono.managed(託管) -> RuntimeBootstrapper(託管) -> dotnet命令(非託管),ASP.NET 5 XRE的程式碼真是十八彎。

千呼萬喚始出來,原來真正的主角就藏在十八彎之後。

dotnet.cpp載入了非託管的dotnet.coreclr.dll:

LPCWSTR pwzHostModuleName = L"dotnet.coreclr.dll";
m_hHostModule = ::LoadLibraryExW(pwzHostModuleName, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
pfnCallApplicationMain = (FnCallApplicationMain)::GetProcAddress(m_hHostModule, pszCallApplicationMainName);

而dotnet.coreclr.dll是由XRE中的C++程式dotnet.coreclr.cpp實現的,最終由dotnet.coreclr.cpp載入了coreclr.dll: 

hCoreCLRModule = ::LoadLibraryExW(L"coreclr.dll", NULL, 0);

dotnet.coreclr.cpp就是載入CLR的主角。

這不讓人產生疑問,這也可以?僅靠一個C++程式就能載入CLR,執行.NET程式,那我們在Windows上為什麼要安裝一個龐大的.NET Framework?

載入CLR的目的是為了執行.NET程式集中的IL程式碼,而要執行的程式集是由dotnet命令(前身是k命令)的命令列引數所傳遞過來的,比如dotnet kestrel(之前是k kestrel),對應的程式集是 Microsoft.AspNet.Hosting。CLR呼叫 Microsoft.AspNet.Hosting.Program.Main() 方法開始執行,ASP.NET 5就開始幹活了。

Core CLR被載入、Microsoft.AspNet.Hosting被執行之後,在 RuntimeBootstrapper.ExecuteAsync() 中,還繼續載入了一些dotnet.host的相關程式集(注意:這時不是Core CLR,而是Mono Runtime)。

//...
var assembly = Assembly.Load(new AssemblyName("dotnet.host"));
//...
var loaderContainerType = assembly.GetType("dotnet.host.LoaderContainer");
var cachedAssemblyLoaderType = assembly.GetType("dotnet.host.CachedAssemblyLoader");
var pathBasedLoaderType = assembly.GetType("dotnet.host.PathBasedAssemblyLoader");
//...

到這裡,不知你有沒有被這十八彎給繞暈,如果沒被繞暈,請繼續往下看。

疑問

這時一個大大的問號浮現在眼前,既然dotnet命令能直接載入Core CLR,為什麼還要用mono命令中轉一下?

百思不得其解。。。

在寫這篇博文的過程中,突然產生了一個大膽猜想——

在Core CLR被載入,Microsoft.AspNet.Hosting被執行之後,為什麼還要用Mono Runtime載入一些dotnet.host相關的程式集?為什麼不直接用Core CLR載入呢?這隻能用一個原因來解釋,dotnet.host依賴的一些程式集在在.NET Framework中有實現,但是在.NET Core Framework中還沒有實現,而Mono是.NET Framework的一個跨平臺實現,在Mono中也有對應的實現。完整的.NET Core Framework(github.com/dotnet/corefx)還在緊張開發之中,在它出來之前,微軟只能藉助Mono。這也是ASP.NET的跨平臺走在前面要付出的代價,隨著.NET Core Framework的完成,XRE的改進,可以預計ASP.NET的跨平臺是會脫離Mono的。

當然,這只是一個猜想,如果你知道真相,歡迎來揭開。