WPF多進程UI客戶端(Like Chrome)探索

Chrome客戶端每一個Tab都是一個進程,這樣每一個Tab就造成了一個沙盒,任何一個Tab出現問題都不會影響其餘Tab。同時,每一個Tab是獨立進程能夠徹底利用一個進程的資源,當多個Tab合併到一塊兒後,看起來像一個進程,實際上利用了多個進程的資源。下面將探索在WPF框架下實現類Chrome的多進程UI客戶端。bash

Chrome是怎麼作的

如下是Chrome的架構預覽圖: 架構

image27.png

能夠看到,它有一個主進程和多個子進程,主進程和子進程之間用IPC通訊,此IPC的底層走的是命名管道,效率較高。它的每一個子進程都會使用一個渲染進程負責Web頁面的渲染(此渲染進程能夠被多個子進程共享使用),渲染完成以後的RenderView會傳遞到主進程中顯示(RenderViewHost),關於Chrome的更多細節能夠參見 Chrome Architectureapp

進程通訊

用WPF作一個相似Chrome的程序,首先要解決的是進程通訊問題,當Client出現問題,好比崩潰,Host要能收到消息,Client從Host中分離出來,也須要在Host和Client之間通訊。目前在Widnows上實現IPC的方案不少,選擇了.NET Remoting的IPCChannel,這裏有一些介紹。這都比較簡單,比較麻煩一些的是UI的傳遞,在.NET中,可以跨越程序邊界是有條件的,要麼從MarshalByRefObject類繼承,要麼實現ISerializable接口,要麼標記了SerializableAttribute特性,顯然WPF的UI是不具有這樣的特色的。框架

跨進程傳遞UI

思路一:使用窗口模擬

假想每一個進程都有一個窗口,經過控制窗口的位置和大小來模擬Chrome的效果。很快就放棄了,感受坑會比較多,電腦性能稍微差點,窗口中承載的內容稍微複雜點,基本就沒法正常工做了,並且,要求每一個進程都有窗口,那若是後續要作帶UI的第三方插件支持,這些插件都必須有窗口才行了。ide

思路二:UI序列化和反序列化

在子進程渲染完成後,把UI進行序列化,傳遞到主進程,主進程再反序列化回來進行顯示。這個方案也不少坑,首先就是序列化和反序列化的效率,而後全部操做都是在主進程接收的,傳遞到子進程後,子進程處理完再次更新UI,又會走序列化和反序列化。函數

繼續探索,在現有的WPF技術中找找是否有可以讓UI跨越程序邊界的方案。發現MAF支持外接程序爲UI或者外接程序返回UI,看看MAF是怎麼作的。佈局

MAF

MAF是微軟提供的一個方便管理和隔離程序的擴展部分的框架,經過隔離宿主和插件,使得插件的崩潰不影響宿主的運行。如下是MAF開發中插件和宿主之間通訊的方式: 性能

image28.png

在實際開發中,以上幾個部分都不可缺乏,而且,MAF的契約依賴文件夾,管道中的每一個部分都須要輸出到指定的文件夾中才能工做,最終要求的文件夾結構是這樣的: ui

image29.png

顯然,這樣的依賴對於實際項目的開發極不友好。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宿主中使用便可。

繼續跨進程傳遞UI

有了MAF的經驗,彷佛能夠利用INativeHandleContract實現跨進程傳遞UI,立刻在Demo中驗證,果真,沒那麼順利,彈出這樣的錯誤:

image30.png

難道內部有使用非公共或靜態方法?只有看源碼找答案,用dotPeek打開程序集(.NET源碼 中沒有這部分的源碼),沒跟幾步就找到一個internal的方法:

image31.png

方法雖然找到了,可是改不了。

留意到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的內容的佈局過程,這會帶來必定的性能損耗。

相關文章
相關標籤/搜索