1. 程式人生 > >託管程式的啟動過程(.NET CLR 寄宿)

託管程式的啟動過程(.NET CLR 寄宿)

託管程式的啟動過程(.NET CLR 寄宿)

大家都知道 C# 等託管語言編寫的程式碼都會被編譯成託管程式集(*.exe 或 *.dll),這些託管程式集最終都會託管給 CLR(公共語言執行時)來執行。那麼,託管程式的啟動過程是怎樣的?CLR 又是如何寄宿到宿主程式中的?為了回答以上問題,本文將首先介紹託管程式集的生成過程;然後介紹託管程式的啟動過程,以及該過程中 CLR 的載入流程。

一個託管應用程式首先被作業系統啟動,然後由作業系統呼叫 CLR 來託管該程式。那麼 .NET 框架到底以什麼方式讓作業系統認識 CLR 並且可以啟動它呢?微軟實際將 CLR 作為 COM 伺服器實現在一個 DLL 中,並提供了標準的 COM 介面。既然是 COM 服務,也就意味著普通的非託管程式也可以呼叫 CLR 來執行託管程式碼,我們把這種呼叫方式叫做寄宿,把呼叫 CLR 的非託管程式叫做宿主。宿主程式不僅可以呼叫 CLR 來執行託管程式集,還可以通過它來進行記憶體管理、垃圾回收管理、策略管理、事件管理以及執行緒控制等高階管理。

如上圖所示,託管語言編寫的原始碼檔案會被編譯成託管模組,多個託管模組連同一些資原始檔會被合併成託管程式集。託管程式集通常有 EXE 和 DLL 兩類,其中前者(EXE)能被作業系統直接啟動,後者(DLL)可以被前者呼叫。託管 EXE 被啟動後,作業系統會呼叫 CLR 來託管它。CLR 啟動後,會建立 AppDomain 來載入並執行託管程式集。

  • 託管模組已經是標準的 PE 檔案了,為了部署和版本管理的方便,編譯器會將多個託管模組連同相關的資源合併成一個 PE 檔案,合併生成的 PE 檔案被稱為程式集。
  • PE 檔案的全稱是 Portable Executable,意為可移植執行體(檔案)。常見的 EXE、DLL、OCX、SYS、COM 都是 PE 檔案,PE 檔案是微軟 Windows 作業系統上的程式檔案(可能是間接被執行,如 DLL 檔案)。

託管程式集的生成

如上所述,託管程式集的生成要經歷兩個步驟,首先是將原始碼編譯成託管模組,然後再將多個託管模組及資源(或資料)檔案合併成程式集。

1. 將原始碼編譯成託管模組

如上圖所示,原始碼可以由多種託管語言編寫,然後由相應的編譯器編譯成統一的託管模組。託管模組是標準的 PE 檔案,其中除了包含中間語言(IL)程式碼和元資料(Metadata)外,還包含一些頭資訊。

  • PE32 或 PE32+ 頭 :標準的 Windows PE 檔案頭,其標記了檔案執行的系統版本、檔案型別(GUI\CUI\DLL)、生成時間等。PE32 檔案能在 Windows 32 位或 64 位上執行,PE32+ 檔案只能在 64 位版本上執行。
  • CLR 頭 :包含了一些託管資訊,比如 CLR 版本、託管模組入口方法的元資料標記,以及模組的強名稱等。
  • 元資料 :主要包含兩種型別的表,一種型別的表描述原始碼中定義的型別和成員;另一種型別的表描述原始碼引用的型別和成員。
  • IL 程式碼 :編譯器編譯原始碼時生成的程式碼,這些程式碼將在執行時被 CLR (JIT) 編譯成 CPU 指令。

2. 將託管模組合併成程式集

CLR 實際不和模組一起工作。相反,它是和程式集一起工作的。如上圖所示,程式集是由多個託管模組(PE 檔案)和資源(或資料)檔案合併而成的單個 PE 檔案。合併生成的 PE 檔案中包含一個名為 “清單(Manifest)” 的資料塊,其描述了構成程式集的檔案,由程式集中的檔案實現的公開型別,以及與程式集關聯在一起的資源或資料檔案。

  • 預設情況下,編譯器會把生成的託管模組轉為程式集,即使只有一個託管模組。
  • 將託管模組合併為程式集,主要是方便於檔案部署及版本管理。

託管程式的啟動

前面介紹了託管程式集(PE 檔案)的生成過程,託管程式集要麼是 EXE 檔案,要麼是 DLL 檔案。Windows 作業系統可以直接啟動 EXE 檔案,我們來看看託管 EXE 的啟動過程是怎樣的。

CLR 介紹

在介紹託管程式的啟動過程之前,我們先來了解一下 CLR 。CLR(Common Language Runtime)是公共語言執行時,它可以載入並執行託管模組(將模組中的 IL 程式碼編譯成 CPU 指令,並執行)。除此之外,CLR 還提供了以下功能:

  • 記憶體管理
  • 程式集載入
  • 安全性
  • 異常處理
  • 執行緒同步

CLR 被定義為 COM 服務,MSCorWks.dll(.NET Framework 1.0 | 2.0)和 Clr.dll(.NET Framework 4.0) 實現了此 COM 服務,這兩個檔案位於 %SystemRoot%\Microsoft.NET\Framework(64) 下的相應子目錄中。

雖然 CLR 是 COM 服務,但是在建立 CLR 例項時並不直接使用的 CoCreateInstance 方法,而是使用了另一個被稱為 “墊片” 的 MSCorEE.dll 檔案,由它去決定建立哪個版本的 CLR 例項。這個檔案位於如下位置:

  • %SystemRoot%\System32
  • %SystemRoot%\SysWow64

.NET Framework 4.0 支援單個程序中同時執行多個版本的 CLR,可使用 ClrVer.exe 來檢查某個程序中的 CLR 版本。

CLR 載入流程

執行一個託管 EXE 檔案時,Windows 會檢查 EXE 檔案頭,決定建立 32 位、64 位還是 WOW64 程序,在程序地址空間中載入 MSCorEE.dll 的相應版本。然後,程序主執行緒呼叫 MSCorEE.dll 中定義的一個方法。這個方法初始化 CLR ,載入 EXE 程式集,再呼叫其入口方法(Main),隨即,託管應用程式啟動並執行。

首先,託管 EXE 的檔案頭中包含 JMP _CorExeMain 指令,該指令指向了 MSCorEE.dll 檔案,因此該檔案被啟動。MSCorEE.dll 始終是最新版,它隨最新的 CLR 一起部署在 %SystemRoot%\System32 (SysWow64) 目錄中。

然後,MSCorEE.dll 會呼叫其內部的 CLRCreateInstance 來建立 CLR 例項。在建立例項過程中會從 EXE 中提取 CLR 的版本資訊,應用程式也可通過配置檔案中的 requiredRuntime & supportedRuntime 來為該過程指定 CLR 版本。

最後,CLR 接管宿主程式的主執行緒,載入託管模組並編譯執行。

參考資料

本文主要參考了 《CLR via C#》 一書的以下兩個章節:

  • 第 1 章. CLR 的執行模型
  • 第 22 章. CLR 寄宿和 AppDomain