WPF 通过进程实现异常隔离的客户端
当 WPF 客户端需要实现插件系统的时候,一般可以基于容器或者进程来实现。如果需要对外部插件实现异常隔离,那么只能使用子进程来加载插件,这样插件如果抛出异常,也不会影响到主进程。WPF 元素无法跨进程传输,但是窗口句柄(HWND)可以,所以可以将 WPF 元素包装成 HWND,然后通过进程间通信将插件传输到客户端中,从而实现插件加载。
1. 使用 HwndSource 将 WPF 嵌入到 Win32 窗口
HwndSource 会生成一个可以嵌入 WPF 的 Win32 窗口,使用 HwndSource.RootVisual 添加一个 WPF 元素。
private static IntPtr ViewToHwnd(FrameworkElement element)
{
var p = new HwndSourceParameters()
{
ParentWindow = new IntPtr(-3), // message only
WindowStyle = 1073741824
};
var hwndSource= new HwndSource(p)
{
RootVisual = element,
SizeToContent = SizeToContent.Manual,
};
hwndSource.CompositionTarget.BackgroundColor = Colors.White;
return hwndSource.Handle;
}
2. 使用 HwndHost 将 Win32 窗口转换成 WPF 元素
Win32 窗口是无法直接嵌入到 WPF 页面中的,所以 .Net 提供了一个 HwndHost 类来转换。 HwndHost 是一个抽象类,通过实现 BuildWindowCore 方法,可以将一个 Win32 窗口转换成 WPF 元素。
class ViewHost : HwndHost
{
private readonly IntPtr _handle;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SetParent(HandleRef hWnd, HandleRef hWndParent);
public ViewHost(IntPtr handle) => _handle = handle;
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
SetParent(new HandleRef(null, _handle), hwndParent);
return new HandleRef(this, _handle);
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
}
}
3. 约定插件的入口方法
可以通过多种方式返回插件的界面。我这里约定每个插件的 dll 都有一个 PluginStartup 类,PluginStartup.CreateView() 可以返回插件的界面。
namespace Plugin1
{
public class PluginStartup
{
public FrameworkElement CreateView() => new UserControl1();
}
}
4. 启动插件进程,使用匿名管道实现进程间通信
进程间通信有多种方式,需要功能齐全可以使用 grpc,简单的使用管道就好了。
- 客户端通过指定插件 dll 地址来加载插件。加载插件的时候,启动一个子进程,并且通过管道通信,传输包装插件的 Win32 窗口句柄。
private FrameworkElement LoadPlugin(string pluginDll)
{
using (var pipeServer = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable))
{
var startInfo = new ProcessStartInfo()
{
FileName = "PluginProcess.exe",
UseShellExecute = false,
CreateNoWindow = true,
Arguments = $"{pluginDll} {pipeServer.GetClientHandleAsString()}"
};
var process = new Process { StartInfo = startInfo };
process.Start();
_pluginProcessList.Add(process);
pipeServer.DisposeLocalCopyOfClientHandle();
using (var reader = new StreamReader(pipeServer))
{
var handle = new IntPtr(int.Parse(reader.ReadLine()));
return new ViewHost(handle);
}
}
}
- 通过控制台程序装载插件 dll 并将插件界面转换成 Win32 窗口,然后通过管道传输句柄。
[STAThread]
[LoaderOptimization(LoaderOptimization.MultiDomain)]
static void Main(string[] args)
{
if (args.Length != 2) return
var dllPath = args[0];
var serverHandle = args[1];
var dll = Assembly.LoadFile(dllPath);
var startupType = dll.GetType($"{dll.GetName().Name}.PluginStartup");
var startup = Activator.CreateInstance(startupType);
var view =(FrameworkElement) startupType.GetMethod("CreateView").Invo(startup, nul;
using (var pipeCline = new AnonymousPipeClientStream(PipeDirection.OutserverHandle))
{
using (var writer = new StreamWriter(pipeCline))
{
writer.AutoFlush = true;
var handle = ViewToHwnd(view);
writer.WriteLine(handle.ToInt32());
}
}
Dispatcher.Run();
}
4 效果

参考资料和备注
- 示例源码
- win32 和 WPF 混合开发,不可避免会涉及空域问题。
- 如果不需要异常隔离,使用 mef 或者 prism 已经可以实现良好的插件功能。
- System.AddIn 也可以提供类似的功能,但是只支持到 .net framework 4.8。
- 这里有一个基于 System.AddIn 实现的多进程插件框架
- wpf 跟 win32 的文档
- 如果不具备窗口的知识,这里有篇博文讲的很好
WPF 通过进程实现异常隔离的客户端的更多相关文章
- 百度编辑器ueditor通过ajax方式提交,不需要事先转义字符的方法(异常:从客户端(xxx)中检测到有潜在危险的 Request.Form 值)
最近项目中使用百度编辑神器ueditor,确实是很好用的一款编辑器.官网教程提供的与后端数据交互都是跟表单方式有关的,项目中使用的是ajax方式提交,因此出现了不少问题,现在记录备忘下. 环境:.ne ...
- selenium登录爬取知乎出现:请求异常请升级客户端后重试的问题(用Python中的selenium接管chrome)
一.问题使用selenium自动化测试爬取知乎的时候出现了:错误代码10001:请求异常请升级客户端后重新尝试,这个错误的产生是由于知乎可以检测selenium自动化测试的脚本,因此可以阻止selen ...
- Python进阶----进程间数据隔离, join阻塞等待, 进程属性, 僵尸进程和孤儿进程, 守护进程
Python进阶----进程间数据隔离, join阻塞等待, 进程属性, 僵尸进程和孤儿进程, 守护进程 一丶获取进程以及父进程的pid 含义: 进程在内存中开启多个,操作系统如何区分这些进程, ...
- 子进程回收资源两种方式,僵尸进程与孤儿进程,守护进程,进程间数据隔离,进程互斥锁,队列,IPC机制,线程,守护线程,线程池,回调函数add_done_callback,TCP服务端实现并发
子进程回收资源两种方式 - 1) join让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源. - 2) 主进程 “正常结束” ,子进程与主进程一并被回收资源. from multipr ...
- java.net.SocketException:Software caused connection abort: recv failed 异常分析 +socket客户端&服务端代码
java.net.SocketException:Software caused connection abort: recv failed 异常分析 分类: 很多的技术 2012-01-04 12: ...
- C# 在WPF中使用Exceptionless异常日志框架
登录http://exceptionless.com/官网,注册一个账户. 创建项目 选择wpf项目类型 拷贝下箭头指的这个密钥,过后程序里用的到. 下面我们打开vs,新建一个wpf的项目 打开git ...
- IIS进程池异常崩溃,导致网站 service unavailable,原因排查与记录。
昨晚十点钟的样子,网站崩溃,开始 service unavailable,最近开始业务高峰,心里一惊,麻痹肯定进程池又异常崩溃了.又碰到什么问题?上次是因为一个异步线程的问题,导致了进程池直接崩溃,后 ...
- 跨进程SharedPreferences异常。
诡异的SharedPreferences异常,在ACC之后,SharedPreferences获取不到值了,但是另一个应用可以获取到值.同样的方法,一个正常一个异常. Context c = null ...
- WPF 打印崩溃问题( 异常:Illegal characters in path/路径中有非法字符)
现象: 打印时候程序直接崩溃.调试时出现下列异常. 异常信息: 中文:System.ArgumentException : 路径中有非法字符. 英文: System.ArgumentException ...
随机推荐
- Vue实现点击按钮进行文件下载(后端Java)
最近项目中需要实现点击按钮下载文件的需求,前端用的vue,因为文件是各种类型的,比如图片.pdf.word之类的.这里后端是可以返回文件的地址给前端的,但我看了下网上各种五花八门的答案,感觉都不是我想 ...
- elsa-core—2.Hello World: HTTP
在本快速入门中,我们将了解一个执行工作流的最小 ASP.NET Core 应用程序.工作流将侦听传入的 HTTP 请求并写回一个简单的响应. 我们将: 创建 ASP.NET Core 应用程序. 使用 ...
- 【笔记】Ada Boosting和Gradient Boosting
Ada Boosting和Gradient Boosting Ada Boosting 除了先前的集成学习的思路以外,还有一种集成学习的思路boosting,这种思路,也是集成多个模型,但是和bagg ...
- Python - typing 模块 —— Any Type
前言 typing 是在 python 3.5 才有的模块 前置学习 Python 类型提示:https://www.cnblogs.com/poloyy/p/15145380.html 常用类型提示 ...
- pikachu Unsafe Fileupload
不安全的文件上传漏洞概述文件上传功能在web应用系统很常见,比如很多网站注册的时候需要上传头像.上传附件等等.当用户点击上传按钮后,后台会对上传的文件进行判断 比如是否是指定的类型.后缀名.大小等等, ...
- ReentrantLock中的Condition(等待和唤醒)
Condition 类的 awiat 方法和 Object 类的 wait 方法等效 Condition 类的 signal 方法和 Object 类的 notify 方法等效 Condition 类 ...
- docker开源系统监控软件Nagios
开源的系统监控软件 下面整理了3个: 1.Zabbix 2.Nagios 3.Open-Falcon 这位博主进行的完整的介绍: https://www.cnblogs.com/linuxandy/p ...
- 安装 iperf和服务器之间测速
安装 # https://downloads.es.net/pub/iperf/iperf-3.1.3.tar.gz wget https://iperf.fr/download/source/ipe ...
- c#创建windows服务入门教程实例
用c#中创建一个windows服务非常简单,与windows服务相关的类都在System.ServiceProcess命名空间下. 每个服务都需要继承自ServiceBase类,并重写相应的启动.暂停 ...
- Walkthrough: Create and use your own Dynamic Link Library (C++)
参考网站:https://docs.microsoft.com/en-us/cpp/build/walkthrough-creating-and-using-a-dynamic-link-librar ...