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的内容的布局过程,这会带来必定的性能损耗。

相关文章
相关标签/搜索