マルチプロセスを使用したWPFの例外分離クライアント

マルチプロセスを使用したWPFの例外分離クライアント

WPFクライアントでプラグインシステムを実装する場合、通常はコンテナまたはプロセスベースで実装できます。外部プラグインに対して例外分離が必要な場合、子プロセスでプラグインをロードするしかありません。これにより、プラグインが例外をスローしてもメインプロセスに影響を与えません。

最終更新 2021/09/22 22:10
鹅群中的鸭霸
読了目安 3 分
カテゴリ
WPF
タグ
.NET WPF プラグインシステム モジュール化 Win32

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").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. 効果

参考資料と注釈

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2025/09/13

WPF から Avalonia への移行シリーズ:なぜ WPF プログラムを Avalonia に移行しなければならないのか

過去数年間、当社の上位機ソフトウェアは主に WPF と WinForm で開発されてきました。これらの技術は Windows プラットフォームで非常に便利であり、小規模試作から現在の規模拡大による納品まで、私たちを支えてきました。しかし、ビジネスの発展や顧客ニーズの変化に伴い、単一の Windows テクノロジースタックは私たちが必ず乗り越えなければならない壁となってきました。

続きを読む
同じカテゴリ / 同じタグ 2025/01/26

WPF カスタムXMLファイルによる国際化

この記事では、WPFプログラムでカスタムXMLファイルを使用して国際化を実現する方法について詳しく説明します。必要なNuGetパッケージのインストール、言語リストの動的取得、言語の動的切り替え、コードおよびXAMLインターフェースでの翻訳文字列の使用などを含み、ソースコードのリンクも提供し、開発者がWPFアプリケーションの国際化を簡単に実装できるように支援します。

続きを読む