“托管代碼”概念
能夠執(zhí)行額外記錄一般在“幾乎任何時(shí)刻”報(bào)告其正在使用的有效GC引用的代碼,就稱做 托管代碼 (因?yàn)槠浔籆LR“管理”)。不能實(shí)現(xiàn)這個(gè)目標(biāo)的代碼叫 非托管代碼。因此在CLR之前存在的代碼都是非托管代碼,特別來(lái)說(shuō),所有操作系統(tǒng)的代碼都是非托管的。
堆棧展開的問(wèn)題
由于托管代碼需要用到操作系統(tǒng)的服務(wù),所有有時(shí)托管代碼需要調(diào)用非托管代碼。相應(yīng)的,托管代碼最初是操作系統(tǒng)啟動(dòng)的,因此有時(shí)非托管代碼也會(huì)調(diào)用托管代碼。因此,當(dāng)你在任意位置中斷托管程序,堆棧上混合了由托管代碼和非托管代碼創(chuàng)建的幀。
非托管代碼的堆棧幀對(duì)其上運(yùn)行的程序 沒有 要求。即一般不要求 展開 (非托管堆棧幀)來(lái)找到它的調(diào)用者。這意味著當(dāng)程序在非托管函數(shù)中斷時(shí),一般[1] 是沒有辦法找到它的調(diào)用函數(shù)的。只能在調(diào)試器里才能做到,這是因?yàn)樵诜?hào)(PDB)文件里保存了額外信息。但這些信息不保證一直存在(這就是為什么在調(diào)試器里無(wú)法獲取準(zhǔn)確堆棧信息的原因)。這在托管代碼里是很大的問(wèn)題,因?yàn)樵跓o(wú)法展開的堆棧里可能包含托管代碼幀(它包含了需要收集的GC引用)。
托管代碼有一些額外需求:不僅是在運(yùn)行過(guò)程中需要跟蹤所有的GC引用,而且還需要能展開它的調(diào)用函數(shù)。另外,無(wú)論何時(shí)從托管代碼到非托管代碼(或發(fā)過(guò)來(lái))的過(guò)渡,托管代碼都需要做一些額外的記錄來(lái)避免非托管代碼無(wú)法展開堆棧帶來(lái)的影響。實(shí)際上,托管代碼在堆棧里將托管代碼幀鏈接起來(lái)了。因此,即使在無(wú)法利用額外信息展開非托管代碼堆棧幀的情況下,還是能在堆棧上找到托管代碼塊并枚舉托管代碼幀。
[1] 最近的平臺(tái)ABI(應(yīng)用程序二進(jìn)制接口 application binary interfaces)定義了編碼這些信息的約定,但其不是一個(gè)所有代碼都必須遵循的嚴(yán)格規(guī)范。
托管代碼的“世界”
結(jié)果就是進(jìn)出托管代碼的每次過(guò)渡都要做特殊的記錄。托管代碼只能在CLR理解的“世界”。這兩個(gè)世界是非常不同的(在任何時(shí)刻,代碼要么在 托管世界,要么在 非托管世界)。而且,因?yàn)樵贑LR格式里定義了托管代碼的執(zhí)行(即 [通用中間語(yǔ)言]cil-spec),并由CLR在原生硬件上解釋執(zhí)行,CLR對(duì)運(yùn)行情況有 非常多 的控制。例如,CRL可以更改從一個(gè)對(duì)象里獲取字段的值或調(diào)用一個(gè)函數(shù)的意思。實(shí)際上,CLR在創(chuàng)建MarshalByReference對(duì)象時(shí)就是這么做的。它們看起來(lái)是普通的本地對(duì)象,但實(shí)際上存在于另外一臺(tái)機(jī)器。簡(jiǎn)單來(lái)說(shuō),CLR的托管世界里有很多 運(yùn)行時(shí)鉤子 來(lái)支持后續(xù)章節(jié)要介紹的強(qiáng)大功能。
另外,托管代碼還有一個(gè)很重要但不是很明顯的衍生物。在非托管代碼里,GC指針是不被允許的(因?yàn)槠洳豢杀桓櫍?,在托管和非托管之間的切換有一個(gè)記錄的成本。這意味著雖然你 可以 在托管代碼里調(diào)用任意的非托管方法,但過(guò)程通常不是很方便。非托管代碼無(wú)法使用GC對(duì)象作為其參數(shù)或者返回值,也就是說(shuō)這些非托管方法創(chuàng)建和使用的任何“對(duì)象”或“對(duì)象引用”都需要顯式釋放。這實(shí)在是個(gè)悲劇。因?yàn)檫@些API無(wú)法享受到諸如異?;蚶^承這樣的CLR的功能的益處,這將會(huì)導(dǎo)致與托管代碼交互時(shí)“不匹配”的用戶體驗(yàn)。
其結(jié)果就是大部分非托管接口在暴露給托管代碼開發(fā)者時(shí)是 封裝的。舉個(gè)例子,當(dāng)訪問(wèn)文件時(shí),一般不使用操作系統(tǒng)提供的Win32 CreateFile 函數(shù),而是用封裝了其的System.IO.File托管代碼類。實(shí)際上直接使用非托管API的地方非常少。
雖然這種封裝在一些地方看起來(lái)“差勁”(更多的代碼不見得做的更多),實(shí)際上它還是增加了一些價(jià)值的。請(qǐng)記住直接暴露非托管接口總是可能的;但我們選擇封裝這些功能,為什么?因?yàn)檎麄€(gè)運(yùn)行時(shí)設(shè)計(jì)的首要目標(biāo)是使編程更簡(jiǎn)單,而且一般來(lái)說(shuō)非托管代碼不是足夠簡(jiǎn)單。通常來(lái)說(shuō),非托管接口一開始就不是為了使用簡(jiǎn)單而設(shè)計(jì)的,而是為完整性優(yōu)化的。當(dāng)你看到CreateFile或者CreateProcess函數(shù)的參數(shù)列表時(shí),很難將其歸類到簡(jiǎn)單里。幸運(yùn)的是,這些接口進(jìn)入托管世界“整了次容”,雖然過(guò)程很“沒技術(shù)含量”(無(wú)非就是重命名,簡(jiǎn)化,重組其功能),但非常有用。為CLR編寫的一個(gè)非常重要的文檔就是 [Framework Design Guidelines][fx-design-guidelines]。這本800+頁(yè)的文檔詳細(xì)描述了創(chuàng)建新的托管代碼類庫(kù)的最佳實(shí)踐。
到這里,我們分析了托管代碼和非托管代碼里兩個(gè)重要不同:
- 高科技:代碼在兩個(gè)不同的世界里運(yùn)行,而CLR在程序運(yùn)行時(shí)的各個(gè)方面進(jìn)行良好的把控(甚至到單個(gè)指令級(jí)別),而且CLR可以檢測(cè)什么時(shí)候進(jìn)入或退出托管代碼的運(yùn)行。這點(diǎn)使很多有用的功能變得可能。
- 低技術(shù)含量:在托管和非托管代碼之間有切換成本,而且非托管代碼無(wú)法使用GC對(duì)象這點(diǎn)事實(shí)鼓勵(lì)用facade模式封裝非托管代碼。即通過(guò)遵循一系列的命名和設(shè)計(jì)指南來(lái)達(dá)到一定程度的一致性和可發(fā)現(xiàn)性來(lái)“整容”并簡(jiǎn)化(操作系統(tǒng))接口。
上面兩個(gè)特性對(duì)于托管代碼的成功都非常重要。