WPF多进程UI探索(Like Chrome)

Chrome客户端每个Tab都是一个进程,这样每个Tab就形成了一个沙盒,任何一个Tab出现问题都不会影响其他Tab。同时,每个Tab是独立进程可以完全利用一个进程的资源,当多个Tab合并到一起后,看起来像一个进程,实际上利用了多个进程的资源。下面将探索在WPF框架下实现类Chrome的多进程UI客户端。

Chrome是怎么做的

以下是Chrome的架构预览图:


image27.png

可以看到,它有一个主进程和多个子进程,主进程和子进程之间用IPC通信,此IPC的底层走的是命名管道,效率较高。它的每个子进程都会使用一个渲染进程负责Web页面的渲染(此渲染进程可以被多个子进程共享使用),渲染完成之后的RenderView会传递到主进程中显示(RenderViewHost),关于Chrome的更多细节可以参见 Chrome Architecture

进程通信

用WPF做一个类似Chrome的程序,首先要解决的是进程通信问题,当Client出现问题,比如崩溃,Host要能收到消息,Client从Host中分离出来,也需要在Host和Client之间通信。目前在Widnows上实现IPC的方案很多,选择了.NET Remoting的IPCChannel,这里有一些介绍。这都比较简单,比较麻烦一些的是UI的传递,在.NET中,能够跨越程序边界是有条件的,要么从MarshalByRefObject类继承,要么实现ISerializable接口,要么标记了SerializableAttribute特性,显然WPF的UI是不具备这样的特点的。

跨进程传递UI

思路一:使用窗口模拟

假想每个进程都有一个窗口,通过控制窗口的位置和大小来模拟Chrome的效果。很快就放弃了,感觉坑会比较多,电脑性能稍微差点,窗口中承载的内容稍微复杂点,基本就无法正常工作了,而且,要求每个进程都有窗口,那如果后续要做带UI的第三方插件支持,这些插件都必须有窗口才行了。

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

在子进程渲染完成后,把UI进行序列化,传递到主进程,主进程再反序列化回来进行显示。这个方案也很多坑,首先就是序列化和反序列化的效率,然后所有操作都是在主进程接收的,传递到子进程后,子进程处理完再次更新UI,又会走序列化和反序列化。

继续探索,在现有的WPF技术中找找是否有能够让UI跨越程序边界的方案。发现MAF支持外接程序为UI或者外接程序返回UI,看看MAF是怎么做的。

MAF

MAF是微软提供的一个方便管理和隔离程序的扩展部分的框架,通过隔离宿主和插件,使得插件的崩溃不影响宿主的运行。以下是MAF开发中插件和宿主之间通信的方式:


image28.png

在实际开发中,以上几个部分都不可缺少,并且,MAF的契约依赖文件夹,管道中的每个部分都需要输出到指定的文件夹中才能工作,最终要求的文件夹结构是这样的:


image29.png

显然,这样的依赖对于实际项目的开发极不友好。

还是回归正题,“它是怎么处理不同程序域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的内容的布局过程,这会带来一定的性能损耗。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,069评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,212评论 1 287
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,912评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,424评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,741评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,194评论 1 206
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,553评论 2 307
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,289评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,923评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,251评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,775评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,144评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,698评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,658评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,214评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,159评论 2 258