CLR(Common Language Runtime)公共語言進行時是一個可由多種編程語言使用的“進行時”。 將源代碼編譯成托管模塊 可用支持CLR的任何語言創建源代碼文件,然後用對應的編譯器檢查語法和分析源代碼。無論選擇哪個編譯器,結果都是托管模塊(managed module)。托管模塊是標準 ...
CLR(Common Language Runtime)公共語言進行時是一個可由多種編程語言使用的“進行時”。
- 將源代碼編譯成托管模塊
可用支持CLR的任何語言創建源代碼文件,然後用對應的編譯器檢查語法和分析源代碼。無論選擇哪個編譯器,結果都是托管模塊(managed module)。托管模塊是標準的32位Microsoft Windows可移植執行體(PE32)文件,或者是標準的64位Windows可移植執行體(PE32+)文件,他們都需要CLR才能執行。(註:PE是Portable Executable(可移植執行體)的簡稱)
本機代碼編譯器(native code compilers)生成的是面向特定CPU架構(比如x86,x64或ARM)的代碼。相反,每個面向CLR的編譯器生成的都是IL(中間需要)的代碼。
除了生成IL面向CLR的編譯器還要在每個托管模塊中生成完整的元數據(metadata)。元數據簡單地說就是一個數據表集合。一些數據表描述了模塊中定義了什麼(比如類型及其成員),另一些描述了模塊引用了什麼(比如導入的類型及成員)。
Microsoft的C++編譯器預設生成包含非托管(native)代碼的exe/dll模塊,併在運行時操作非托管數據(native記憶體)CLR即可執行。然而,通過指定/CLR命令行開關,C++編譯器就能生成包含托管代碼的模塊。當然,最終用戶必須安裝CLR才能執行這種代碼。在前面提到的所有Microsoft編譯器中,C++編譯器是獨一無二的,只有它才允許開發人員同時寫托管和非托管代碼,並生成到同一個模塊中。它也是唯一允許開發人員在源代碼中同時定義托管和非托管數據類型的Microsoft編譯器。
- 將托管模塊合併成程式集
CLR實際不和模塊工作。它和程式集工作。
首先,程式集是一個或多個模塊/資源文件的邏輯分組。其次,程式集是重用、安全性以及版本控制的最小單元。
圖中一些托管模塊和資源(或數據)文件準備交由一個工具處理。工具生成代表文件邏輯分組的一個PE32(+)文件。實際發生的事情是,這個PE32(+)文件包含一個名為清單(mainfest)的數據塊。清單也是元數據表的集合。這些表描述了構成程式集的文件、程式集中的文件所實現的公開導出的類型以及與程式集關聯的資源或數據文件。(註:所謂公開導出的類型,就是程式集中定義的public類型,它們在程式集內部外部均可見。)
編譯器預設將生成的托管模塊轉換成程式集。也就是說,C#編譯器生成的是含有清單的托管模塊。清單指出程式集吸由一個文件構成。對於只有一個托管模塊而且無資源(或數據)文件的項目,程式集就是托管模塊,生成過程中無需執行任何額外的步驟。但是,如果希望將一組文件合併到程式集中,就必須撐握更多的工具(比如程式集鏈接器AL.exe)及其命令行選項。
- 載入公共語言運行時CLR
可執行文件(exe)運行時,Windows檢查EXE文件頭,決定是創建32位還是64位進程之後,會在進程地址空間載入MSCorEE.dll的x86,x64或ARM版本。如果是Windows的x86或ARM版本,MSCorEE.dll的x86版本在%SystemRoot%\System32目錄中。如果是Windows的x64版本,MSCorEE.dll的x86版本在%SystemRoot%\SysWow64目錄中,64位版本則在%SystemRoot%\System32目錄中(為了向後相容)。然後,進程的主線程式調用MSCorEE.dll中定義的一個方法。這個方法初始化CLR,載入EXE程式集,再調用其入口方法(Main)。隨即,托管應用程式啟動並運行。(PS:微軟在64位系統中將所有處理32位程式的工具都放在SysWow64目錄下,Wow就是Windows on Windows的意思。而System32目錄是處理64位程式的。還叫32,只是延續了以前的叫法,其實應該是64)
- 執行程式集的代碼
開發人員一般用c#,VB等高級語言進行編程。它們的編譯器將生成IL。然而,和其他任何機器語言一樣,IL也能使用彙編語言編寫,Microsoft甚至專門提供了名為ILAsm.exe的IL彙編器和名為ILDasm.exe的IL反彙編器。註意,高級語言通常只公開了CLR全部功能的一個子集。然而IL彙編語言允許開發人員訪問CLR的全部功能。要知道CLR具體提供了哪些功能,唯一的辦法是閱讀CLR文檔。
為了執行方法,首先必須把方法的IL轉換成本機(navive)CPU指令。這是CLR的JIT(just-in-time或者"即時")編譯器的職責。
就在Main方法執行之前,CLR會檢測出Main的代碼引用的所有類型。這導致CLR分配一個內部數據結構來管理對引用類型的訪問。圖中Main方法引用了一個Console類型,導致CLR分配一個內部結構。在這個內部數據結構中,Console類型定義的每個方法都有一個對應的記錄項。每個記錄項都含有一個地址。根據此地址即可找到方法的實現。對這個結構初始化,CLR將每個記錄項都設置成(指向)包含在CLR內部的一個未編檔函數。我將該函數稱為JITCompiler。
Main方法首次調用WriteLine時,JITCompiler函數會被調用。JITCompiler函數負責將方法的IL代碼編譯成本機CPU指令。由於IL是”即時“(just in time)編譯的,所以通常將CLR的這個組件稱為JITter或者JIT編譯器。
JITCompiler函數被調用時,它知道是要調用的是哪個方法,以及具體是什麼類型定義了該方法。然後,JITCompiler會在定義(該類型的)程式集的元數據中查找被調用方法的IL。接著JITCompiler驗證IL代碼,並將IL代碼編譯成本機CPU指令。CPU指令保存到動態分配的內在塊中。然後 ,JITCompiler回到CLR為類型創建的內部數據結構,找到與被調用方法對應的那條記錄,修改最初對JITCompiler的引用,使其指向內在塊(其中包含了剛纔編譯好的本機CPU指令)的地址。最後,JITCompiler函數中跳轉到記憶體塊中的代碼。這些代碼正是WriteLine方法(獲取單個String參數的那個版本)的具體實現。代碼執行完畢並返回時,會回到Main中的代碼,並像往常一樣繼續執行。
現在,Main要第二次調用WriteLine。這一次,由於已對WriteLine的代碼進行了驗證和編譯,所以會直接執行記憶體塊中的代碼,完全跳過JITCompiler函數。WriteLine方法執行完畢後,會再次回到Main。
- IL和驗證
將IL編譯成本機CPU指令時,CLR執行一個名為驗證(verification)的過程。
CLR確實提供了在一個操作系統中執行之個托管應用程式的能力。每個托管應用程式都在一個AppDomain中執行。每個托管EXE文件預設都在它自己的獨立地址空間中運行,這個地址空間只有一個AppDomain。然而,CLR的宿主進程(比如IIS或者Microsoft SQL Server)可決定在一個進程中運行多個AppDomain。
- 本機代碼生成器:NGen.exe
使用用.NET Framework提供的NGen.exe工具,可以在應用程式安裝到用戶的電腦上時,將IL代碼編譯成本機代碼。由於代碼在安裝時已經編譯好,所以CLR的JIT編譯器不需要在運行時編譯IL代碼,這有助於提升應用程式的性能。NGen.exe能在以下兩種情況下發揮重要作用。
1提高應用程式的啟動速度
2減少應用程式的工作集(所謂工作集,是指在進程的所有記憶體中,已映射的物理記憶體那一部分(即這些記憶體全在物理記憶體中,CPU可以直接訪問);進程還有一部分虛擬記憶體,它們可能在轉換列表中(CPU不能通過虛擬地址訪問,需要Windows映射之後才能訪問);還有一部分記憶體在磁碟上的分頁文件里。)
NGen.exe生成的文件有以下問題
1沒有知識產權保護
2NGen生成的文件可能失去同步
3較差的執行時性能
- Framework類庫
FCL(Framework Class Library)
- 通用類型系統
Microsoft制定了一個正式規範來描述類型的定義和行為,這就是“通用類型系統”(Common Type System,CTS)。
- 公共語言規範
要創建很容易從其他編程語言中訪問的類型,只能從自己 的語言中挑選其他所有語言都支持的功能。為了在這個方面提供幫助,Microsoft定義了“公共語言規範”(Common Language Specification,CLS),它詳細定義了一個最小功能集。任何編譯器只有支持這個功能集,生成的類型才能相容由其他符合CLS、面向CLR的語言生成組件。
(個人的理解:CLS是為了不同編程語言之間互相調用而設計的,如果只用一種語言,就不用考慮CLS的規範)
(說明:文中99%內容來自書本原文。把知識要點搬運到這裡,只是為了方便本人複習、查閱)