.NET CLI 是如何運行你的程式碼的 ??

Oct 02, 2020 • 花費 3 分鐘

前言:

Microsoft 都在 .NET 5.0 Preview 前把之前 Git repository 中的 coreclr 和 corefx 合并成單一 runtime 了,順便把所有 repository 都整理一番 ❗

剛好早前在網上找到一個 Blog 科普 .NET Core

由於里面有些是 2016 年時的文章,版本都落在 1.0 release,所以在這里整理一下原文章里提到地方,也更新一下到了 .NET 5 會改變成如何。

歡迎 follow 原作者的 Blog,他是一個 .NET 愛好者 😀 原文章


.NET CLI tooling ???

現在是 2020 年, .NET Core 3.1.x LTS 的一年。根據 RoamMap,11 月也是 .NET 5.0 發佈的時間。

而早在 .NET 1.1.0 release 的時候,你就可以使用 Microsoft 官方提供的 .NET CLI tooling 寫出 Hello World,如下:

dotnet new console
dotnet restore
dotnet run

然後就預設輸出:

Hello World!

這就是 dotnet CLI (Command Line Interface) tooling,

這篇文章會著重於它是如何運行你的程式碼的 ?


運行 .NET assembly (可執行檔) 的傳統形式

記得 .NET 的可執行檔是不能直接執行的,它們只是 IL code, 不是 machine code。

CLR via C# 一書里提到:

Windows 在檢查完 exe 檔案的 header (標頭) 以確定是創建 32bit,64bit 或 WoW64 類型的 process 後,Windows 會 load (加載) 不同版本的 MSCorEE.dll (x86, x64, IA64), 加載到 process 的 address space 中。然後該 process 的第一個 thread (primary thread) 就會 calling (調用) MSCoreEE.dll 內定義好的 functions, 用來初始化 CLR, 加載 EXE assembly,最後 calling entry point method (Main),到這里為止,被託管的應用程式就正式執行了。

MSCorEE.dll 等 libraries 通常預設位於 C:\Windows\System32 目錄下

所以說在 .NET Framework 的環境下,無論如何都會需要 MSCorEE.dll 的存在。


運行 .NET assembly (可執行檔) 的新形式

正如前面提到,有了 .NET Core CLI tooling 後,你可以使用

dotnet run

那麼我們有了新的 runtime CoreCLR 和新的工具後,整件事到底是怎麼運作的 ?

為取得更多的資訊,我們需要先設 2 個 environment variables (環境變數) :

COREHOST_TRACTDOTNET_CLI_CAPTURE_TIMING 來獲得更詳細的輸出

在 cmd (with devenv.exe) 下是:

set DOTNET_CLI_CAPTURE_TIMING=1
set COREHOST_TRACT=1

在 bash 下是

export DOTNET_CLI_CAPTURE_TIMING=1
export COREHOST_TRACT=1

更詳細的 tracing 介紹在這邊 Host tracing

如果你處於一個多語系,多版本的 .NET Core 環境下,由於輸出太多,我們可以把它 pipe 到一個檔案內:

dotnet run > logfile.txt 2>&1

再回頭看這個 logfile.txt 里面,

我們忽略那些多 CPU 架構等訊息,

在 Windows 下面,嘗試搜尋一下 “dotnet.exe

我們不難地找到真正執行的 command,是類似這樣的

--- main = {
dotnet.exe
exec
C:\Program Files\dotnet\sdk\3.1.401\MSBuild.dll
-maxcpucount
-verbosity:m
-restore
C:\Users\si1kdd\Desktop\Console\Console.csproj
-nologo
-verbosity:quiet
-distributedlogger:Microsoft.DotNet.Tools.MSBuild.MSBuildLogger,C:\Program Files\dotnet\sdk\3.1.401\dotnet.dll*Microsoft.DotNet.Tools.MSBuild.MSBuildForwardingLogger,C:\Program Files\dotnet\sdk\3.1.401\dotnet.dll
}

在 Linux 下的輸出也是大同小異,

所以是 command 由 "dotnet run" 變到 "dotnet.exe exec MSBuild.dll"



dotnet execcorehost

到目前為止,所有的事情都發生在 Managed code 中,

一旦 dotnet exec 這一步被調用,我們就會跳到 corehost 程序中的 Unmanged code (實現在 corehost.cpp) 中。

它會另外 load (加載) 一些其他的 .dll 檔案,最後就是創建 CoreCLR runtime 本身。

原文只列出 3 個預設 dll,到了 .NET 5.0 版本己經有這麼多個 folder 了 ……

dlls default

居然預設使用騰訊家的 rapidJSON 😄

corehost 最主要的任務是計算且找到運行該應用所需的 .dll 檔以及其 dependencies (依賴)

在 .NET Core 5.0 的版本內,個數大約是:

600 個 Managed dlls (有 runtime asset 字眼)

50 個 Native dlls (有 native asset 字眼)

By the way 原文提及要設置 9 個屬性才能控制 CoreCLR runtime

但在 .NET 5.0 的版本內,增加到要確定 13 個預設屬性

一旦完成所有的整理工作,控制權會落入 corehost 中,



執行一個 .NET Assembly (可執行檔)

最後,我們可以從 unixinterface.cpp 中的程式碼看一下 .NET dll/assembly 是如何被加載和執行的。

透過 coreclr_initialize 這個 function 里面, 注解表明 :

Initialize the CoreCLR. Creates and starts CoreCLR host and creates an app domain

這邊啟動和初始化 CoreCLR, 然後看到以下程式碼 :

ReleaseHolder<ICLRRuntimeHost4> host;
hr = CorHost2::CreateObject(IID_ICLRRuntimeHost4, (void**)&host);

所以這里利用到 ICLRRuntimeHost 的 interface,

什麼是 ICLRRuntimeHost Interface ??

  • 它主要是使我們的程序 Host (托管) 一個 .NET 的 runtime (這里是 CoreCLR).

具體實現都在 corehost.cpp 檔案中看到細節。

  • 要注意的地方是,這個 Hosting interface (托管接口) 之前在 Windows 上的 CLR 是基於 COM 的 hosting API 的一部分。

所以不要被檔案名字搞亂,實際上它是由 Windows CLI tooling 的慣例抄過來的。

  • 再具體一點的定義都在 holder.h 內。

而在 CoreCLR 的世界中,為 Unix 寫的 hosting API 己經在所有的平台上實現了,也建立了通用的 method名字

unixinterface.cpp 這里執行完 ,使用 .NET Core 預設的 Hosting 後。

最後,你的 .NET program 就順利執行起來了 !


題外話:

自己寫 C++ 來做 .NET Core Hosting 是個進階的 Topic,官方也有提供一些 Example 例子分享用法。

有什麼情境需要到自己做一個 host program 呢 ?? 哈哈可以想一想。 😄


參考連結:


#.NET Core#2020

😱 日本女生 Youtuber 挑戰 11 下引體上升 ...

😱 外國 Google 工程師 WFH (Work From Home) 時的配置 ...

comments powered by Disqus