前面提到, 託管的程式集包含著metadata和中繼語言(IL), IL是一個獨立於CPU的機器語言, 是微軟與幾家外部商業和學術的語言/編譯器作者協商之後開發的. IL是比絕大多數CPU機器語言進階的語言, IL能夠訪問和操作物件類型, 能夠建立和初始化對象, 調用對象的虛函數, 能直接運算元組元素, 它甚至還能為錯誤處理拋出和捕獲異常. 你可以把IL想象為一個物件導向的機器語言.
通常, 開發人員編寫進階語言, 例如C#,C++/CLI, 或者VB. 為這些進階語言工作的編譯器將產生IL. 然而, 像其它機器語言, IL可以用組合語言來編寫, 微軟確實提供了一個IL彙編器, ILAsm.exe, 還提供了一個IL反組譯碼器ILDasm.exe.
記住任何層次的語言很可能只暴漏出CLR提供的一部分功能. 然而, IL組合語言允許開發人員訪問所有的CLR功能. 因此, 如果你選擇的程式設計語言隱藏了部分CLR的功能, 你可以用IL組合語言編寫那部分代碼, 或者利用其他具有你需要的CLR功能的程式設計語言.
獲得CLR提供的功能的唯一方式是閱讀CLR文檔, 在本書, 我儘力關注CLR功能, 以及如何被C#語言暴露的(或沒有暴露). 我懷疑其他書籍或者文章只是通過一種語言來闡述CLR的功能, 使得大多數開發人員理解CLR只提供了那種語言所對應的那部分功能. 只要你的語言允許你完成你想做的事情, 那麼這種模糊的看法也不是件壞事.
重要: 我認為這種方便在語言之間進行切換和切換的能力是CLR一個很強大的功能. 不幸的是, 我也認為開發人員將會更加忽視這個特性. 像C#和VB這樣的程式設計語言對於執行I/O操作是非常優秀的語言. APL對於執行進階工程或商務計算是非常優秀的語言. 通過CLR, 你可以用C#編寫你的應用程式的I/O操作部分, 用APL編寫工程計算的部分. CLR提供了這些語言之間整合的層次, 這是前所未有的技術, 這使得混合語言編程值得許多開發項目考慮.
為了執行一個函數方法, 它的IL必須被轉換成native CPU指令, 這是CLR JIT(just-in-time)編譯器的工作.
給出了一個函數方法第一次被調用所發生的事情:
在執行Main函數之前, CLR檢測被Main的代碼所引用的所有類型, 這導致CLR分配內部的資料結構用於管理被應用類型的訪問. 在中, Main函數引用一個類型, Console, 導致CLR分類一個內部結構, 這個內部資料結構為Console類型定義的每個方法包含一個條目, 每個條目包含方法實現的地址. 當初始化這個結構時, CLR設定這些條目到一個內部的沒有文檔描述的函數(CLR內部), 我們稱這個函數為JIPCompiler.
當Main函數第一次調用WriteLine時, JITCompiler函數被調用, JIPCompiler函數負責編譯一個函數的IL為native CPU指令. 因為IL是在”just in time”時編譯的, 這個CLR組件也常稱為JITter或者JIT Compiler.
注意: 如果應用程式運行在x86版本的Windows或者WoW64上, JIT Compiler將產生x86指令, 如果應用程式以64位應用程式運行在x64或者IA64版本的Windows上, JIT Compiler將產生x64或者IA64指令.
在調用時, JITCompiler函數知道正在調用的是什麼函數, 知道這個函數的類型. JITCompiler函數然後在程式集的metadata中搜尋被呼叫者法的IL. 然後JITCompiler驗證和編譯IL代碼為native CPU指令, native CPU指令被儲存在動態分類的記憶體塊中. 然後JITCompiler返回到被調用函數的條目上(在CLR建立的類型的內部資料結構), 並替換那個引用為包含剛才編譯為native CPU指令的記憶體塊的地址. 最後, JITCompiler函數跳到記憶體塊的代碼處, 這個代碼就是函數WriteLine的實現(接受String參數的版本). 當這個代碼返回時, 它返回到Main中, 然後繼續正常執行.
Main現在第二次調用WriteLine函數, 這次, WriteLine的代碼已經被驗證和編譯了, 因此調用直接轉到記憶體塊, 完全跳過了JITCompiler函數, 當WriteLine函數執行完之後, 它就返回到Main函數. 給出了第二次調用WriteLine函數的過程:
函數只有在第一次調用時有效能上的損失, 隨後的調用是全速執行native代碼, 因為不需要對native代碼進行驗證和編譯.
JIT compiler將native CPU指令儲存在動態記憶體中, 這意味著被編譯的代碼在程式終止時將被拋棄, 因此如果你再次運行應用程式, 或者同時運行兩個應用程式執行個體(在兩個不同的作業系統進程), JIT compiler將再次將IL編譯為native指令.
對大多數應用程式來說, 由JIT編譯所導致的效能下降不是很重要, 多數應用程式常常是重複調用相同的函數, 這些函數在應用程式執行時只有一次效能上的下降, 容易看出花在函數內部的時間比調用的時間長的多.
你應該也注意到CLR的JIT編譯器最佳化了native代碼, 這類似於非託管的C++編譯器在後端所作的工作, 它可能需要更多的時間來產生最佳化的代碼, 但是最佳化過的代碼比沒有被最佳化的代碼執行的更快.