前言 《CLR via C#》(Jeffrey Richter著)——.NET 界的經典之作,相讀"恨晚",讀的過程寫點筆記跟大家分享:
【我也推薦大家看英文版,能夠直接領會原意 】
認識CLR
一
個被多種程式設計語言使用的運作時。核心功能包括:記憶體管理,程式集加載,安全性,異常處理,以及線程同步。這些核心功能能夠被所有以它作為目标平台的語言使
用,實際上,在運作時,CLR并不關心程式員使用哪一種語言編寫源碼的。微軟開發了很多以CLR作為目标平台的語言編譯器,
如:C++/CLI,C#,VB,F#,Iron Python,Iron
Ruby,以及IL彙編。另外還有很多其他的公司,學校開發了相應的編譯器,如:Ada,APL,Caml,COBOL,Fortran,Lua等等。下
面的圖展示了編譯源檔案的過程:

由
圖可知,我們不用去考慮使用什麼編譯器,因為結果都是托管子產品。托管子產品是标準的PE32/32+檔案(微軟Windows可移植可執行檔案,PE32表
示32位,32+表示64位),它需要CLR執行。托管程式集一直采用了資料執行保護(Data Execution
Prevention,DEP)和在Windows下的位址空間布局随機化(ASLR),這兩個功能提升了整個系統的安全性。
托管子產品的組成部分
1.PE32/PE32+ header:标準的PE檔案頭,類似通用對象檔案格式頭(Common Object File Format,COFF).
如果檔案頭是PE32格式:可以運作在32位/64位Windows系統。
PE32+格式:運作在64位系統。該檔案頭還指明了檔案類型,如:GUI, CUI, DLL并且包含檔案建立的時間戳。
對于僅僅包含IL代碼的子產品這些在PE32(+)檔案頭裡面的資訊會被忽略。對于包含本地CPU代碼,
該檔案頭包含本地CPU代碼的資訊
2.CLR header:包含托管子產品的資訊:需要CLR的版本,一些标志資訊,MethodDef中繼資料(擷取托管子產品入口方法(Main方法)),以及子產品的元素據,
資源,強命名,一些标志的位置/尺寸
3.中繼資料:每一個托管子產品包含中繼資料表。有兩種主要的類型:1.描述在源碼中定義成員的類型。2.描述在源碼中引入的成員類型
4.IL 代碼:編譯器編譯源碼産生的代碼,在運作時,CLR将IL編譯為本地CPU指令
本地代碼編譯器會以具體的CPU架構為目标産生代碼,比如X86,X64,或IA64。所有符合CLR的編譯器都會編譯源碼生成IL代碼,也稱為托管代碼,因為由CLR管理IL的執行。
什麼是中繼資料?
簡言之,中繼資料是描述在子產品裡面定義了什麼的這樣資料表集合,比如成員的類型,比如引用了其他的什麼類型或成員。
中繼資料一直作為代碼嵌入在同名的EXE/DLL檔案裡面,使得它們不可能分開。因為編譯器是同時生成中繼資料和IL代碼并綁定到托管子產品。
中繼資料的用途
1.編譯不需要依賴于原生C/C++頭及庫檔案,因為可以直接從托管子產品讀取中繼資料
2.智能提示
3.CLR用中繼資料進行代碼稽核,確定類型安全
4.允許序列化一個對象的字段為一個記憶體塊,發送到其他機器,然後被反序列化,重建對象狀态。
5.允許GC跟蹤對象的生命周期
認識程式集
CLR不直接運作子產品,而是程式集。程式集是一個抽象的概念:
1.程式集是一個或多個子產品或資源檔案的邏輯分組。
2.程式集是重用,安全性,版本控制的最小單元
根據我們使用的編譯器或工具,可以生成一個或多個檔案的程式集。在CLR世界裡,一個程式集就是我們通常所說的元件。
下面的圖具體說明什麼是程式集:
一些托管子產品和資源檔案通過一個工具進行處理,然後生成一個代表檔案邏輯分組的PE32(+)檔案。這個PE32(+)檔案包含了資料塊——稱為清單,該清單是另一種簡單的中繼資料集表,這些表描述了組成程式集的檔案。
預設情況下,編譯器實際做的工作就是将托管子產品轉換為程式集,也就是說編譯器會生成一個包含清單的托管子產品。對于隻有一個托管子產品沒有資源或資料檔案的項目而言,程式集就是托管子產品,建立的過程中也不需要額外的步驟。如果想把檔案組合到程式集中,很多工具能夠實作。
程式集允許我們可重用的,安全的,版本控制元件的邏輯和實體概念分離開來。至于怎麼分離代碼跟資源檔案完全取決于自己。一個程式集的子產品可以包含引用的另外的程式集的資訊(版本号等)——程式集的自我描述,換句話說,CLR可以決定程式集執行的直接依賴的順序。
運作一個可執行檔案需要做的工作?
如果一個非托管的程式調用LoadLibrary載入一個托管程式集,Windows會加載并初始化CLR(如果沒有加載),當然前提是程序已經啟動。
什麼是LoadLibrary?
1.僅僅在桌面應用程式才有 2.載入指定的子產品到調用程序的位址空間。指定的子產品可能引起其他的子產品也被載入
認識IL
托管程式集包含中繼資料和IL。IL是一種獨立CPU的機器語言,它是微軟在咨詢了很多商業和學術語言/編譯器的作者後創造的。
IL比大多數CPU機器語言進階。Why?
1.IL可以通路并操作對象的類型 2.IL具有建立和初始化對象的指令 3.IL可以調用對象的虛方法 4.IL可以直接操作數組元素
5.IL具有處理錯誤異常的指令
可以認為IL是一種面向對象的機器語言
通常我們使用向C#這樣的進階語言開發,編譯器會生成IL。像其他的機器語言一樣,IL可以使用彙編語言編寫,微軟提供了IL彙編語言編譯器——ILAsm.exe,
以及反彙編編譯器——ILDasm.exe
不同于C#僅僅暴露CLR提供的功能的一個子集,IL彙編語言運作開發者通路所有CLR的功能。是以這裡更新一個誤區:不要以為C#提供給我們的功能就是CLR的全部功能。
了解CLR的JIT(即時編譯)
下圖展示當方法首次調用時發生的事情
在
Main方法執行之前,CLR會檢測被Main方法引用的所有類型并且配置設定一個管理通路引用的類型的内部資料結構。上面例子Main方法中引用了
Console,是以CLR會配置設定一個内部的資料結構。這個資料結構包含了定義在Console類裡面的每一個方法的入口,每一個入口儲存的方法可以找到對應方法的位址。當這個内部結構初始化時,CLR設定每一個入口到一個内部的,未公開的CLR裡面的函數,這個函數稱為JITCompiler。
了解JITCompiler函數
當
Main方法使Console第一次調用WriteLine方法時,JITCompiler函數會被調用。JITCompiler負責将一個方法的IL代
碼編譯為本地CPU指令,因為IL就是在"即時編譯"時被編譯的,CLR的這個元件通常被稱為JITer或JIT compiler。
當調用時,JITCompiler知道哪一個方法被調用并且定義在方法裡面的類型是什麼。
1.接着JITCompiler函數搜尋調用方法的IL定義在程式集的中繼資料
2.再接下來就是稽核并編譯該IL為本地CPU指令。本地CPU指令會儲存在一個動态配置設定的記憶體塊裡面
3.然後,JITCompiler回到CLR建立的内部資料結構的該方法的入口處,用編譯成的CPU指令儲存的記憶體位址替換掉開始時推該方法的引用
4.最後,JITCompiler函數跳轉到放在記憶體裡面的代碼處,該代碼實作了WriteLine方法(重載擷取一個string參數的)。當這個方法傳回時回到Main并繼續往下執行。
往下執行,Main方法會第二次調用WriteLine方法,這一次,WriteLine的IL代碼已經被稽核和編譯過了,不會調用
JITCompiler,而是直接跳轉到記憶體裡存放該代碼的地方,在WriteLine方法執行完成傳回Main。下圖展示了第二次調用
WriteLine方法的過程:
我知道很多朋友都必備了這本書,希望路過的朋友多留言或讨論,或指正。
注 《CLR via C#》(Jeffrey Richter著)——.NET 界的經典之作,讀的過程寫點筆記跟大家分享,我也推薦大家看英文版,能夠直接領會原意