Chrome客戶端每一個Tab都是一個進程,這樣每一個Tab就造成了一個沙盒,任何一個Tab出現問題都不會影響其餘Tab。同時,每一個Tab是獨立進程能夠徹底利用一個進程的資源,當多個Tab合併到一塊兒後,看起來像一個進程,實際上利用了多個進程的資源。下面將探索在WPF框架下實現類Chrome的多進程UI客戶端。bash
如下是Chrome的架構預覽圖: 架構
能夠看到,它有一個主進程和多個子進程,主進程和子進程之間用IPC通訊,此IPC的底層走的是命名管道,效率較高。它的每一個子進程都會使用一個渲染進程負責Web頁面的渲染(此渲染進程能夠被多個子進程共享使用),渲染完成以後的RenderView會傳遞到主進程中顯示(RenderViewHost),關於Chrome的更多細節能夠參見 Chrome Architecture。app
用WPF作一個相似Chrome的程序,首先要解決的是進程通訊問題,當Client出現問題,好比崩潰,Host要能收到消息,Client從Host中分離出來,也須要在Host和Client之間通訊。目前在Widnows上實現IPC的方案不少,選擇了.NET Remoting的IPCChannel,這裏有一些介紹。這都比較簡單,比較麻煩一些的是UI的傳遞,在.NET中,可以跨越程序邊界是有條件的,要麼從MarshalByRefObject類繼承,要麼實現ISerializable接口,要麼標記了SerializableAttribute特性,顯然WPF的UI是不具有這樣的特色的。框架
假想每一個進程都有一個窗口,經過控制窗口的位置和大小來模擬Chrome的效果。很快就放棄了,感受坑會比較多,電腦性能稍微差點,窗口中承載的內容稍微複雜點,基本就沒法正常工做了,並且,要求每一個進程都有窗口,那若是後續要作帶UI的第三方插件支持,這些插件都必須有窗口才行了。ide
在子進程渲染完成後,把UI進行序列化,傳遞到主進程,主進程再反序列化回來進行顯示。這個方案也不少坑,首先就是序列化和反序列化的效率,而後全部操做都是在主進程接收的,傳遞到子進程後,子進程處理完再次更新UI,又會走序列化和反序列化。函數
繼續探索,在現有的WPF技術中找找是否有可以讓UI跨越程序邊界的方案。發現MAF支持外接程序爲UI或者外接程序返回UI,看看MAF是怎麼作的。佈局
MAF是微軟提供的一個方便管理和隔離程序的擴展部分的框架,經過隔離宿主和插件,使得插件的崩潰不影響宿主的運行。如下是MAF開發中插件和宿主之間通訊的方式: 性能
在實際開發中,以上幾個部分都不可缺乏,而且,MAF的契約依賴文件夾,管道中的每一個部分都須要輸出到指定的文件夾中才能工做,最終要求的文件夾結構是這樣的: ui
顯然,這樣的依賴對於實際項目的開發極不友好。spa
仍是迴歸正題,「它是怎麼處理不一樣程序域UI的傳遞的?」
在外界程序返回UI的代碼中有兩段看起來有點意思的代碼。
AddInSideAdapter中:
INativeHandleContract handle = FrameworkElementAdapters.ViewToContractAdapter(frameworkElement);
複製代碼
HostSideAdapter中:
INativeHandleContract handle = GetHandle();
FrameworkElement frameworkElement = FrameworkElementAdapters.ContractToViewAdapter(handle);
複製代碼
這裏MAF利用了WPF和Win32的交互,在插件端,WPF得到要傳遞的UI的窗口句柄,包裝在一個繼承自HwndSource(用於Win32中承載WPF內容)並實現了INativeHandleContract接口的類中,而此類是能夠跨越程序邊界的。在宿主端,收到此類後經過ContractToViewAdapter轉換成一個從HwndHost(用於WPF承載Win32窗口)繼承的類,而HwndHost的基類是FrameworkElement。這就好辦了,WPF中熟悉的FrameworkElement,直接在WPF宿主中使用便可。
有了MAF的經驗,彷佛能夠利用INativeHandleContract實現跨進程傳遞UI,立刻在Demo中驗證,果真,沒那麼順利,彈出這樣的錯誤:
難道內部有使用非公共或靜態方法?只有看源碼找答案,用dotPeek打開程序集(.NET源碼 中沒有這部分的源碼),沒跟幾步就找到一個internal的方法:
方法雖然找到了,可是改不了。
留意到AddInHost的構造函數中,若是contract轉換AddInHwndSourceWrapper失敗就不會執行那個致使拋出異常的方法,而這個contract是能夠本身實現的,把FrameworkElementAdapters.ViewToContractAdapter(frameworkElement)的結果包裝到本身實現的INativeHandleContract中就能夠了,代碼以下:
public class CustomNativeHandleContract : MarshalByRefObject, INativeHandleContract
{
private readonly INativeHandleContract _contract;
public CustomNativeHandleContract(INativeHandleContract contract)
{
_contract = contract;
}
public IContract QueryContract(string contractIdentifier)
{
return _contract.QueryContract(contractIdentifier);
}
//省略若干INativeHandleContract的方法
// ...
}
複製代碼
在子進程中
INativeHandleContract Contract = new CustomNativeHandleContract(FrameworkElementAdapters.ViewToContractAdapter(element));
複製代碼
完成UI到HwndSource的轉換。
等等,咱們剛恰好像少調用了一個方法:RegisterKeyboardInputSite,在MAF中,這個方法會管理HwndSource中的鍵盤焦點,可是咱們這裏HwndSource僅僅是做爲一箇中轉,咱們不會依賴HwndSource的行爲,因此不會有問題。
按照上面的思路,利用MAF的特性經過Remoting的IPCChannel實現了多進程UI的客戶端程序,須要注意的是,在WPF內容轉到HwndSource時會執行WPF的內容的佈局過程,這會帶來必定的性能損耗。