1. 程式人生 > >CLR via C#讀書筆記 CLR寄宿和AppDomain

CLR via C#讀書筆記 CLR寄宿和AppDomain

利用 create 控制 jmp 代理 情況 note 系統目錄 exce

寄宿

寄宿是指讓其他應用程序(非托管代碼)使用CLR的能力,比如自己用C++開發的窗體能創建CLR實例。

托管代碼也能調用非托管代碼

  1. [DllImport("kernel32.dll")]
  2. public static extern int WinExec(string exeName, int operType);

通常會調用win32 api,但是要查文檔才知道怎麽定義extern方法

CLR實際上被實現為COM服務器,可以通過CoCreateInstanceCLRCreateInstance(推薦)創建CLR COM服務器實例,而宿主就能使用CLR的東西。

CLRCreateInstanceMSCorEE.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的對象

  1. AppDomain appDomainB = AppDomain.CreateDomain("NewDomain");
  2. DemoClass obj;
  3. // 在新的應用程序域中創建對象
  4. 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可以用托管代碼寫存儲過程?

  1. 在加載程序集時,先把程序集復制到Cache目錄下,再加載,這樣原程序集不會被鎖定。 ?

CLR via C#讀書筆記 CLR寄宿和AppDomain