1.什麼是CLR
CLR(Common Language Runtime)公共語言遠行時,是一個可由多種編程語言使用的“遠行時”。CLR的核心功能(比如內存管理、程序集加載、安全性、異常處理和線程同步)可由面向CLR的所有語言使用。CLR不關心開發人員使用哪種語言進行編程,只要編譯器面向CLR就可以了,所有,開發人員應該使用自己最適合和熟悉的語言進行編程。所有的編程語言在面向CLR編譯器的編譯都生成了一個托管模塊。托管模塊是一個標准的32位的Microsoft Windows可移植執行體(PE32)文件,或者是一個標准的64位Windows可移植的PE32+文件,他們都需要CLR才能執行。
2.托管模塊的各個組成部分
PE32或PE32+頭 標准Windows PE文件頭,類似於“公共對象文件格式”。
CLR頭 包含使用這個模塊成為一個托管模塊的信息(可由CLR和一些實用程序進行解釋)。頭中包含了需要的CLR版本,一些標志,托管模塊人口方法(Main方法)的MethodDef元數據標記(token),以及模塊的元數據、資源、強名稱、一些flag以及其他不太重要的數據項的位置/大小
元數據 每個托管模塊都包含元數據表。主要有兩種類型的表:一種類型的表描述源代碼中定義的類型和成員:另一種類型的表描述源代碼引用類型和成員
IL(中間語言)代碼 編譯器編譯源代碼時生成的代碼。在運行時,CLR將將IL編譯成本地的CPU指令
元數據的用途:
編譯時,元數據消除了對本地C/C++頭和庫文件的需求,因為在負責實現類型/成員的IL代碼中,已包和引用的類型/成員有關的全部信息。編譯器可直接從托管模塊讀取元數據
Microsoft Visual Studio 使用元數據幫助你寫代碼。也就是“智能感知(IntelliSense)技術”可以解析元數據,指出一個類型提供了那些方法、屬性、事件和字段等等。
CLR的代碼驗證過程使用元數據確保代碼只執行“類型安全”的操作。
元數據允許將一個對象的字段序列化到一個內存中,將其發送給另一台機器,然後反序列化,在遠程機器上重建對象的狀態
元數據允許垃圾回收器跟蹤對象的生存期。垃圾回收器能判斷任何對象的類型,並從元數據知道那個對象中的哪些字段引用了其他對象。
3.程序集
其實,CLR不和托管模塊一起工作。它和程序集(assembly)一塊工作。程序集是一個或多個模塊/資源文件的邏輯分組。程序集是重用、安全性已經版本控制的最小單元。程序集是自描述的(self-describing)
4 執行程序集的代碼
托管程序集同時包含元數據和IL。為了執行程序,首先必須把它的IL轉換成本地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的引用,讓它現在指向內存塊中的地址。最後,JITCompiler函數跳轉到內存塊中的代碼。
第二次調用WriteLine。這一次,由於對WriteLine的代碼進行了驗證和編譯,所以直接執行內存塊中的代碼,完全跳過JITCompiler函數。
第二次調用WriteLine的情況
5.通用類型系統
為了通過類型,用一種編程語言寫的代碼能與用另一種語言寫的代碼溝通,Microsoft指定了一正式的規范,即“通用類型系統”(Common Type System,CTS),它描述了類型的定義和行為。
CTS規范規定,一個類型可以包含零個或多個成員。
字段(Field) 一個數據變量
方法(Method) 一個函數
屬性(Property) 對於調用者,該成員看起來像是一個字段
事件(Event) 事件在對象以及其他相關對象之間實現了一通用機制。
CTS 還指定了類型可視性規則以及類型成員的訪問規則,例如private,family等
CTS還為類型繼承。虛方法、對象生存期等定義了相應的規則
特比說一下CTS中的一條規則:所有類型最終必須從預定義的System.Object類型繼承。System.Object可以做的事情如下:
比較兩個實例的相等性
獲取實例的哈希碼
查詢一個實例的真正類型
執行實例的淺拷貝
獲取視實例對象的當前狀態的一個字符串表示
6.公共語言規范
為了創建很容易從其他編程語言中訪問的類型,只能從自己的編程語言中挑選其他所有語言都確定支持的那些功能,Microsoft定義了一個“公共語言規范”(Common Language Specifiaction,CLS),它詳細定義了一個最小功能集。
7.元數據
上面已經提到托管的PE文件由4個部分構成:PE32(+)頭、CLR頭、元數據以及IL。
這裡我們主要說一下元數據。
元數據是一個二進制數據塊,由幾個表構成。這些表分為三個類別:定義表(definiton talbe)、引用表(reference table)和清單表(mainfest table)。
常用元數據定義表(編譯器編譯源代碼時,代碼定義的任何一樣東西都會導致定義表中的表中創建一個記錄項):
ModuleDef 總是包含一個用於標示模塊的記錄項。
TypeDef 模塊中定義的每個類型都在這個定義表中有一個對應的記錄項。
MethodDef 模塊中定義的每個方法都在這個定義表中有一個對應的記錄項。
FieldDef 模塊中定義的每個字段都在這個定義表中有一個對應的記錄項
ParamDef 模塊中定義的每個參數都在這個定義表中有一個對應的記錄項
PropertyDef 模塊中定義的每個屬性都在這個定義表中有一個對應的記錄項
EventDef 模塊中定義的每個事件都在這個定義表中有一個對應的記錄項
常用的引用元數據表:
AssemblyRef 模塊中引用的每個程序集在這個表中都有一個對應的記錄項
ModuleRef 模塊引用的每個類型可能是由別的PE模塊實現的,所有那些模塊在這個表都有一個記錄項
TypeDef 模塊引用的每個類型在這個表中都有一個對應的記錄項
MemberRef 模塊引用的每個成員都在這個表中有一個對應的記錄項
清單元數據表:
AssemblyDef 如果該模塊標示的是一個程序集,就在這個元數據表中包含單個記錄項。該記錄項列出了程序集名稱(不含路徑和擴展名)、版本(major,minor,build和revision)、語言文化(culture)、一些標志(flag)、哈希算法以及發布者的公鑰。
FileDef 作為程序集一部分的每個PE文件和資源文件在這個表中都有一個對應的記錄項。
MainifestResourceDef 作為程序集一部分的每個資源在這個表中都有一個對應的記錄項
ExportedTypesDef 從程序集的所有PE模塊中導出的每個public類型中在這個表中都有一個對應的記錄項。