《CLR via C#:框架設計》讀書筆記 - CLR寄宿和AppDomain

22.1 CLR寄宿
返回

.NET Framework在Microsoft Windows平臺跑,意味着它必須用Windows可理解的技術來構建。所以,所有的託管模塊和程序集文件必須使用Windows PE文件格式,而且要麼是Window EXE文件,要麼是DLL文件。

開發CLR時,Microsoft實際上把他實現成在一個DLL中的COM服務器。也就是說,Microsoft爲CLR定義了一個標準的COM接口,併爲該接口和COM服務器分配了GUID。安裝.NET Framework時,代表CLR的COM服務器和其他COM服務器一樣在Windows註冊表中註冊。

任何Windows應用程序都可以寄宿CLR,寄宿CLR步驟:

(1)在Windows檢查EXE文件頭並決定是創建32位、64位還是WoW64位進程

(2)Windows將x86、x64或IA64版本的MSCorEE.dll加載到進程的地址空間中(參考1.3 加載公共語言運行時)

(3)進程的主線程調用MSCorEE.dll中定義的方法CLRCreateInstance,該方法返回ICLRMetaHost接口。接着調用該接口的方法GetRumtime,該方法返回接口ICLRRuntimeInfo的指針。然後利用該指針和GetIntoface方法獲得ICLRRuntimeHost接口,該接口做了一下事情:

   設置宿主管理器。CLR宿主要做的事:內存管理,程序集加載,安全性,異常處理,以及線程同步。
初始化並啓動CLR
加載程序集並執行其中代碼。
停止CLR。
22.2 AppDomain
返回

AppDomain是一組程序集的邏輯容器。它的唯一的作用就是進行隔離。請參考進程、線程與應用程序域區別。

具體功能:
(1)兩個AppDomain中代碼不能直接訪問。

(2)AppDomain可以卸載。

(3)AppDomain可以單獨保護。

(4)AppDomain可以單獨實施配置。

上圖中演示了一個Windows進程,其中運行着一個CLR COM服務器。該CLR當前管理者兩個AppDomain。每個AppDomain都有自己的Loader堆(參考4.4 運行時相互關係)。除此之外,每個AppDomain都加載了一些程序集。還有一個與進程相同生命週期的“AppDomain中立”提供公用的程序集。

跨越AppDomain邊界訪問對象
一個AppDomain中的代碼可以和另一個AppDomain中的類型和對象通信。但是,只允許通過良好定義的機制訪問。下面的代碼演示瞭如何創建一個新的AppDomain,在其中加載一個程序集,然後構造程序集所定義的類型實例。代碼演示構造了三種類型的不同行爲:

(1)”按引用封送“(Marshal-by-Reference)的類型;

(2)”按值封送“(Marshal-by-value)的類型;

(3)不能封送的類型。

View Code
AppDomain是CLR的功能,Windows對此一無所知。

CreateInstanceAndUnwrap方法導致調用線程從當前AppDomain到新的AppDomain。線程將制定程序集加載到新AppDomain中,並掃描程序集類型定義元數據表,查找指定類型“MarshalByRefType”)。找到類型後,調用它的無參構造函數。然後,線程又範圍默認AppDomain,對CreateInstanceAndUnwrap返回的MarshalByRefType對象進行操作。

如何將一個對象從一個AppDomain(源AppDomain,這裏指真正創建對象的地方)封送到另一個AppDomain(目標AppDomain,這裏指調用CreateInstanceAndUnwrap的地方)?

  1. Marshal-by-Reference

CLR會在目標AppDomain的Loader堆中定義一個代理類型。這個代理類型是用原始類型的數據定義的。因此,它看起來和原始類型完全一樣;有完全一樣的實例成員(屬性、事件和方法)。但是,實例字段不會成爲(代理)類型的一部分。

  1. Marshal-by-Value

CLR將對象字段序列化一個字節數組。這個字節數組從源AppDomain複製到目標AppDomain。然後,CLR在目標AppDomain中反序列化字節數組,這會強制CLR將定義了的“被反序列化的類型”的程序集加載到目標AppDomain中。接着,CLR創建類型的一個實例,並利用字節數組初始化對象的字段,使之與源對象中的值相同。換言之,CLR在目標AppDomain中準確的複製了源對象。

22.3卸載AppDomain
返回

AppDomain.Unload()中執行操作:

(1)CLR掛起進程中執行中執行的託管代碼的所有線程;

(2)CLR檢查所有線程棧,查看哪些線程正在執行要卸載的那個AppDomain中的代碼,或者哪些線程會在某個時刻返回至要卸載的那個AppDomain。在任何一個棧上,如果準備卸載的AppDomain,CLR都會強迫對應的線程拋出一個ThreadAbortException異常(同時恢復線程的執行)。這將導致線程展開(unwind),在展開的過程中執行遇到的所有finally塊中的內容,以執行資源清理代碼。如果沒有代碼捕捉ThreadAbortException,它最終會成爲一個未處理的異常,CLR會“吞噬”這個異常,線程會終止,但進程可以繼續運行。這一點是非常特別的,因爲對於其他所有未處理的異常,CLR都會終止進程。

重要提示:如果一個線程當前正在finally塊、catch塊、類構造器、臨界執行區(critical execution region)域或非託管代碼中執行,那麼CLR不會立即終止該線程。否則,資源清理代碼、錯誤恢復代碼、類型初始化代碼、關鍵代碼或者其他任何CLR不瞭解的代碼都無法完成,導致應用程序的行爲變得無法預測,甚至可能造成安全漏洞。線程在終止時,會等待這些代碼塊執行完畢。然後當代碼塊結束時,CLR再強制線程拋出一個ThreadAbortException。

臨界區是指線程終止或未處理異常的影響可能不限於當前任務的區域。相反,非臨界區中的終止或失敗只對出現錯誤的任務有影響。

(3)當上一步發現的所有線程都離開AppDomain後,CLR遍歷堆,爲引用了“已卸載的AppDomain創建的對象”的每一個代理都設置一個標誌(flag)。這些代理對象現在知道它們引用的真實對象已經不在了。如果任何代碼在一個無效的代理對象上調用一個方法,該方法會拋出一個AppDomainUnloadedException

(4)CLR強制垃圾回收,對現已卸載AppDomain創建的任何對象佔用的內存進行回收。這些對象的Finalize方法被調用(如果存在Finalize方法),使對象有機會徹底清理它們佔用的資源

(5)CLR恢復剩餘所有線程的執行。調用AppDomain.Unload方法的線程將繼續執行,對AppDomain.Unload的調用是同步進行的在前面的例子中,所有工作都用一個線程來做。因此,任何時候只要調用AppDomain.Unload都不可能有另一個線程在要卸載的AppDomain中。因此,CLR不必拋出任何ThreadAbortException異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章