完整流程總覽
- 原始碼 → C# 編譯器 (csc.exe) → 組件 (EXE/DLL)
- 組件包含:MSIL 代碼 + 中繼資料 + 資訊清單 + PE 標頭
- 作業系統載入器 載入 PE 檔案 → 跳入 CLR 啟動存根
- CLR (Common Language Runtime) 初始化 → JIT 編譯器 將 MSIL 編譯為原生機器碼
- 執行原生代碼,GC 管理記憶體,例外處理、執行緒同步等執行期服務
階段一:C# 編譯器 (csc.exe)
輸入與輸出
- 輸入:`.cs` 原始碼檔案
- 輸出:可攜式執行檔 (PE/COFF 格式),副檔名 `.exe` (主控台/視窗) 或 `.dll` (類別庫)
組件內部結構
PE 標頭 (標準 Windows PE)
├── CLR 標頭 (標識為 .NET 組件)
├── 中繼資料表
│ ├── TypeDef (型別定義)
│ ├── MethodDef (方法定義)
│ ├── FieldDef (欄位定義)
│ ├── MemberRef (成員參考)
│ ├── AssemblyRef (組件參考)
│ └── ...
├── MSIL 代碼 (方法主體)
├── 資源
└── 強式名稱簽章 (可選)
關鍵編譯器動作
- 語法分析、型別檢查、語意分析
- 產生 MSIL (Microsoft Intermediate Language) 指令
- 發出中繼資料表(型別、成員、簽章、泛型參數等)
- 建立資訊清單:組件名稱、版本、文化特性、公開金鑰、檔案清單、型別轉發
- 匯入啟動存根:從 `mscoree.dll` 匯入 `_CorExeMain` (EXE) 或 `_CorDllMain` (DLL)
階段二:作業系統載入與 CLR 啟動
Windows PE 載入流程
- OS 載入器讀取 PE 標頭,驗證格式
- 載入相依 DLL (`mscoree.dll`、系統 DLL)
- 配置進程記憶體空間、執行緒、堆疊
- 定位 PE 入口點 (AddressOfEntryPoint)
- 關鍵差異:.NET 組件入口點非使用者代碼,而是 CLR 啟動存根 (JMP _CorExeMain)
CLR 啟動序列
_CorExeMain (mscoree.dll)
→ 確認 CLR 版本 (由組件資訊清單指定)
→ 初始化 CLR 執行引擎
→ 建立預設 AppDomain
→ 找到組件入口點方法 (Main)
→ 觸發 JIT 編譯 Main 方法
→ 執行原生代碼
階段三:JIT (Just-In-Time) 編譯
為什麼需要 JIT?
- MSIL 是 CPU 無關的中介指令集,無法直接執行
- JIT 在方法首次被呼叫時將 MSIL 編譯為該 CPU 架構的原生機器碼 (x86/x64/ARM)
- 編譯後代碼快取在記憶體,後續呼叫直接執行原生碼
JIT 編譯觸發時機
class Program
{
static void Main()
{
MethodA(); // 第一次呼叫 → JIT 編譯 MethodA
MethodA(); // 第二次呼叫 → 直接執行快取的原生碼
MethodB(); // 首次呼叫 → JIT 編譯 MethodB
}
static void MethodA() { ... }
static void MethodB() { ... }
}
三種 JIT 編譯器
| 類型 | 特點 | 適用場景 |
| Standard JIT | 預設,方法級編譯,快取原生碼 | 一般應用程式 |
| Econo JIT | 不快取,編譯後即捨棄,節省記憶體 | 記憶體受限裝置 (已淘汰) |
| PreJIT (NGen / ReadyToRun) | 安裝/建置時預先編譯全組件 | 啟動效能關鍵、大型應用 |
分層編譯 - .NET Core 3.0+ / .NET 5+
- Tier 0:快速編譯,無優化,極低延遲啟動
- Tier 1:背景優化編譯 (內聯、向量化、分支預測等)
- 熱門方法自動升級到 Tier 1
- 環境變數控制:`COMPlus_TieredCompilation=1` (預設開啟)
階段四:執行期服務 (CLR 核心功能)
記憶體管理:垃圾收集器 (GC)
- 自動追蹤物件參考圖
- 分代收集 (Gen 0/1/2) + 大型物件堆 (LOH)
- 背景/並行/工作站/伺服器 模式
IDisposable / Finalize 釋放非受控資源
例外處理
- 結構化例外處理 (SEH) 整合
- 跨語言、跨組件例外傳播
- 堆疊追蹤自動捕獲
執行緒與同步
- ThreadPool、Task/async-await、Timer
- Monitor、Mutex、Semaphore、ReaderWriterLock
- 非同步 I/O 完成埠 (IOCP) 整合
安全性
- 程式碼存取安全性 (CAS) - .NET Framework
- 透明度模型 (SecurityTransparent/Critical) - .NET Core+
- 組件載入驗證、強式名稱、Authenticode
互操作
- P/Invoke (平台呼叫)
- COM 互操作 (RCW/CCW)
- C++/CLI 混合模式組件
進階主題:AOT 編譯與 Native AOT
NGen (Native Image Generator) - .NET Framework
ngen install MyApp.exe
// 預先編譯至原生映像,儲存於 Native Images Cache
// 優點:啟動快、共用記憶體頁
// 缺點:需管理員權限、版本綁定、無法動態適應 CPU
ReadyToRun (Crossgen) - .NET Core 3.0+
true
// 發布時預先編譯,輸出 .dll 含原生碼區段
// 仍保留 MSIL 作為回退,支援多平台單一檔案
Native AOT - .NET 7+
true
// 完全移除 JIT、CLR 大部分元件
// 輸出獨立原生可執行檔 (無 .dll 相依)
// 優點:極快啟動、極小體積、無 JIT 開銷
// 缺點:無動態載入、反射受限、泛型實體化需預先定義
除錯與診斷工具
| 工具 | 用途 |
ildasm.exe | 反組譯 MSIL、檢視中繼資料 |
dotnet-ildasm | .NET Core 版 IL 反組譯 |
ngen display | 檢視原生映像狀態 |
fuslogvw | 組件繫結失敗記錄 |
perfview | JIT 編譯、GC、鎖爭用分析 |
dotnet-trace | 跨平台效能追蹤 |
總結
C# 代碼經歷:原始碼 → csc 編譯 → MSIL+中繼資料+PE → OS 載入 → CLR 啟動 → JIT 編譯 → 原生執行。理解每個階段的角色(特別是 MSIL 的中介性質、JIT 的按需編譯、CLR 的執行期服務),有助於優化啟動效能、診斷載入失敗、設計跨平台部署策略(JIT / ReadyToRun / Native AOT)。