WPFクライアントがプラグインシステムを実装する必要がある場合、通常はコンテナまたはプロセスに基づいて実装することができます。外部プラグインに対して例外分離を実装する必要がある場合、プラグインのロードには子プロセスのみを使用し、プラグインが例外を投げてもメインプロセスに影響しません。WPF要素はプロセス間で転送できないが、ウィンドウハンドルHWNDは転送できるので、WPF要素をHWNDにラップしてから、プロセス間通信でプラグインをクライアントに転送することで、プラグインのロードを実現することができる。
1. HwndSourceを使ってWin 32ウィンドウにWPFを埋め込む
HwndSourceはWPFを埋め込むWin 32ウィンドウを生成し、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を使用してWin 32ウィンドウをWPF要素に変換する
Win 32ウィンドウはWPFページに直接埋め込むことはできないため、. Netは変換するHwndHostクラスを提供している。HwndHostは、BuildWindowCoreメソッドを実装することでWin 32ウィンドウを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アドレスを指定してプラグインをロードします。プラグインがロードされると、子プロセスが開始され、パイプ通信を介してプラグインをラップするWin 32ウィンドウハンドルが送信されます。
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をロードし、プラグインインターフェイスをWin 32ウィンドウに変換し、ハンドルをパイプで転送します。
[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").Invoke(startup, null);
using (var pipeCline = new AnonymousPipeClientStream(PipeDirection.Out, serverHandle))
{
using (var writer = new StreamWriter(pipeCline))
{
writer.AutoFlush = true;
var handle = ViewToHwnd(view);
writer.WriteLine(handle.ToInt32());
}
}
Dispatcher.Run();
}
5つの効果

参考文献とコメント
- 示例源码
- Win 32とWPFのハイブリッド開発は、必然的に空域問題を含む。
- 例外分離が必要でない場合は、mefやprismを使用してプラグイン機能を実装できます。
- System.AddInも同様の機能を提供しますが、. NET Framework 4.8でのみサポートされます。
- 这里有一个基于 System.AddIn 实现的多进程插件框架
- wpf 跟 win32 的文档
- 如果不具备窗口的知识,这里有篇博文讲的很好