🕳️

WPF+PrismでMessagePipeを利用する

2022/04/13に公開

高速メッセージングライブラリMessagePipe。同じメッセージングシステムはPrismにもEventAggregatorクラスとして提供されていますが、それよりもはるかに高速軽量であるとうたわれています。

https://github.com/Cysharp/MessagePipe

ただ個人的にはそもそもPrismのEventAggregatorはほとんど使ったことがなく、実際にはRxのSubjectのほうをもっぱら使っています。WPFだと結局通常のeventハンドリングや、ReactivePropertyを組み合わせることが多いことから、Rxで統一するのが簡単ですし…

さてMessagePipeの大きな利点ですが高速軽量なだけではありません。名前付きパイプやTCP/UDPを利用して、IPC機能も提供されている点も見逃せません。デスクトップアプリ作っていてもちょろっとIPCしたいなーというときに便利そうです。
よしじゃあこれをPrismに統合して使ってみよう!と思い立ちちょろちょろと奮闘したのでその記録を残しておきます。

適当なWPFプロジェクトを作成し、さっそくPrismとMessagePipeをインストします。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="MessagePipe" Version="1.7.2" />
    <PackageReference Include="Prism.DryIoc" Version="8.1.97" />
  </ItemGroup>
</Project>

PrismにはDIが統合されていますが、そのDIコンテナのバックエンドはUnityかDryIocから選択することができます。Unityは数か月前にアーカイブ化されてメンテが終了してしまったため、実質DryIoc一択です。

さああとはコンテナに型やらインスタンスを登録するだけですね。MessagePipeはMicrosoft.Extensions.DependencyInjectionを前提としたDI登録用APIを持ちます。当然DryIocにはそのまま適用できないので、ServiceCollectionExtensions.AddMessagePipeの実装をカンニングして型を登録しちゃいましょう。これで完成ですね。

internal!!!!!
internal

はい。 というわけで何とかしましょう。
実はDryIocにはMicrosoft.Extensions.DependencyInjectionへの変換や互換APIを提供するDryIoc.Microsoft.DependencyInjectionというライブラリが公開されています。なので、

  1. ServiceCollectionのインスタンス作成
  2. MessagePipeを登録
  3. DryIocに変換してPrismに渡す

この手順でいけるはずです。
さっそくパッケージを追加して…コネコネすれば…

<ItemGroup>
  <PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
  <PackageReference Include="MessagePipe" Version="1.7.2" />
  <PackageReference Include="Prism.DryIoc" Version="8.1.97" />
</ItemGroup>

いけました。出来上がったのがこちらです。

using System;
using System.Collections.Generic;
using System.Windows;
using Microsoft.Extensions.DependencyInjection;
using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
using Prism.Ioc;
using Prism.DryIoc;
using MessagePipe;

public partial class App
{
    private Container ConvertToDryIocContainer(IEnumerable<ServiceDescriptor> services, Func<IRegistrator, ServiceDescriptor, bool>? registerService = null)
    {
        var rules = this.CreateContainerRules();
        var container = new Container(rules);

        container.Use<IServiceScopeFactory>(r => new DryIocServiceScopeFactory(r));
        container.Populate(services, registerService);

        return container;
    }

    protected override IContainerExtension CreateContainerExtension()
    {
        var serviceCollection = new ServiceCollection();

        serviceCollection.AddMessagePipe();

        var container = this.ConvertToDryIocContainer(serviceCollection);
        var ext = new DryIocContainerExtension(container);
        return ext;
    }

    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
    }

    protected override Window CreateShell()
        => this.Container.Resolve<MainWindow>();
}

結局DryIoc.Microsoft.DependencyInjectionが提供するDryIocへの変換メソッドでは、Rulesを指定することができなかったため、そこの実装だけ自前で実装する形で対応しました。

CreateContainerExtensionメソッドは名前の通りDIコンテナのバックエンドインスタンスを生成することだけが目的のメソッドなので、本来ここで型登録をするのはお作法がよろしくないのですが…
ServiceCollectionにアクセスできるポイントがここしかないので、なにとぞご容赦を…

何はともあれこれでWPF+Prismな環境でMessagePipeを使えるようになりました。
PrismがGenericHostでも動くようになってくれれば苦労しないんですがねぇ…

Discussion