CLR via C#讀書筆記 CLR寄宿和AppDomain
寄宿
寄宿是指讓其他應用程序(非托管代碼)使用CLR的能力,比如自己用C++開發的窗體能創建CLR實例。
托管代碼也能調用非托管代碼
- [DllImport("kernel32.dll")]
- public static extern int WinExec(string exeName, int operType);
通常會調用win32 api,但是要查文檔才知道怎麽定義extern方法
CLR實際上被實現為COM服務器,可以通過CoCreateInstance
或CLRCreateInstance
(推薦)創建CLR COM服務器實例,而宿主就能使用CLR的東西。
CLRCreateInstance
在MSCorEE.dll
裏面(在安裝.Net Framework時被到系統目錄中),他的工作決定創建哪個版本的CLR(這個dll與.Net一起安裝,但卻是唯一的,安裝了多個版本的.Net電腦中這個dll總是最新版本)
無論是托管程序還是非托管程序,他們都會編譯成PE文件(保存程序的自描述信息,比如入庫函數),而托管程序開始執行時會有一條JMP指令跳轉到MSCorEE.dll裏,通過MSCorEE.dll的PE文件信息找到這個_CorExeMain
函數的入口地址,然後修改剛才的JMP指令要跳轉的地址,從而將控制跳轉到了_CorExeMain這個函數裏面去。隨後CLR被啟動,接著根據托管程序的CLR表頭找到入口地址,再跳轉進去,程序開始運行
AppDomain
程序集邏輯容器
有點像進程,提供的數據和代碼隔離,但創建的代價比真正創建進程小。
- 不能訪問其他AppDomain創建的對象
- 可卸載
- 可單獨配置
- 這些配置涉及搜索路徑,版本綁定重定向,加載程序集的方式
- 可單獨保護
- 有單獨的權限集
- CLR啟動時會創建一個默認的AppDomain
- AppDomain中有個Loader堆,記錄該AppDomain創建以來訪問過了哪些類型(類型對象)
- 頻繁被使用的類比如Object,它的類型對象會保存在特殊的AppDomain中被其他AppDomain共享,代價是這個AppDomain永遠不會被卸載
- 註意多個AppDomain是用同一個托管堆
跨程序域通信
AppDomain的核心是隔離但是它還是提供了跨AppDomain訪問對象的方法
假設一個情景,在AppDomain A中去訪問AppDomain B的對象
- AppDomain appDomainB = AppDomain.CreateDomain("NewDomain");
- DemoClass obj;
- // 在新的應用程序域中創建對象
- obj = (DemoClass)appDomainB.CreateInstanceAndUnwrap("ClassLib", "ClassLib.DemoClass");
這裏會拋出異常:"ClassLib.DemoClass"未標記為可序列化
其實這個過程叫做按值封送
,這個過程中會創建兩個對象,在AppDomain B中創建一個,然後序列化成字節數組,傳給AppDomain A,再反序列化成對象,這種做法要求DemoClass必須被打上可序列化特性。
還有一種封送方式叫按引用封送
,這種方式要求DemoClass派生自MarshalByRefObject
還是用上面一樣的代碼,這個過程本質是創建了兩個對象,在AppDomain A中創建了個代理對象obj,AppDomainB也創建個對象(這是真正的對象),在B向A發送對象引用前會做以下操作:
- 在AppDomain A的Loader堆中定義一個代理對象的類型對象(有與AppDomain B的ClassLib.DemoClass類的類型對象完全一樣的實例成員:方法,屬性,事件,但不包括實例字段)
- 實際上內部會通過代理類找到真正的對象,再用反射獲得字段值
- 定義代理類對象自己的實例字段(保存了那個AppDomain擁有真實的對象,以及如何找到他)
訪問對象時(這是個同步的過程),利用代理類的信息,切換線程到B,獲得真正的對象,調用真正的方法
卸載AppDomain
通過AppDomain.Unload方法
過程:
- 掛起執行托管代碼的所有線程
- 檢查與被卸載AppDomain有關系的線程,強迫它拋出異常ThreadAbortException
- 如果拋出的異常沒被處理,異常會被CLR”吞噬”,線程終止,進程繼續執行
- 找到所有創建的代理對象,給它們設置個flag,之後所有嘗試調用代理對象方法的操作都會拋異常
- 強制啟動垃圾回收
- 恢復剩余線程
監視AppDomain
通過修改AppDomain的靜態屬性MonitoringEnabled為true來啟動監視,一旦啟動就不能關閉.
啟動後可以讀取下面4個屬性
- MonitoringSurivedProcessMemorySize 當前CLR實例控制的所有AppDomain使用的字節數
- MonitoringTotalAllocatedMemorySize 已分配的字節數
- MonitoringSurvivedMemorySize 正則使用的字節數
- MonitoringTotalProcessorTime CPU占用率
異常通知
通過給AppDomain.FirstChanceException事件註冊方法能在AppDomain拋異常時接到通知
這個時機是:在異常拋出後查找catch塊前,FirstChanceException不能處理異常,AppDomain拋異常後會尋找catch,如果找不到則將異常拋給調用該AppDomain的AppDomain(中間有跨AppDomain傳遞),如果一直到線程棧頂都找不到catch塊,CLR將被終止
宿主如何使用AppDomain
- winform,wpf,控制臺都是自寄宿(self-host)的,在本文開頭說過,啟動exe時會加載
MSCorEE.dll
緊接著啟動CLR,初始化後再運行Main方法 - Asp.Net中當用戶請求aspx時會被iis轉交給aspnet_isapi.dll(非托管代碼),它負責啟動CLR,之後Asp.Net判斷該Web應用是否是第一次被請求,如果是則創建AppDomain,並以虛擬根目錄來標識。所以默認情況下,一個進程是可以跑多個網站(AppDomain)。
- Asp.Net的一個亮點是允許不關閉服務器的前提下動態更改網站代碼,當Asp.Net檢測出文件被修改後則會卸載就的AppDomain並創建新的AppDomain(加載新的文件),為了確保這一過程順利執行,Asp.Net使用了一個名為shadow copying1的功能
資料
- 什麽是COM
- CLR寄宿(上) MSCOREE.DLL
- http://m.blog.csdn.net/zlbcdn/article/details/70195565
- .Net,你為什麽會慢 (托管程序與非托管程序啟動過程)
- <<.Net之美>>
TODO
- .net Romoting(不同計算機,不同進程,跨AppDomain訪問對象技術)
- SQLServer可以用托管代碼寫存儲過程?
- 在加載程序集時,先把程序集復制到Cache目錄下,再加載,這樣原程序集不會被鎖定。 ?
CLR via C#讀書筆記 CLR寄宿和AppDomain