microsoft / vs-streamjsonrpc の Formatter を MessagePack にして使う

github.com

Microsoft が提供している jsonrpc ライブラリ vs-streamjsonrpcJson 以外にも MessagePack でシリアライズ・デシリアライズできるように作成されています。

以前の記事 microsoft/vs-streamjsonrpc のバージョンが 2 になっていたので使ってみた。 - bamch0h’s diary を MessagePackで通信できるようにしてみたコードが以下。

JsonRpc.Attach を使う代わりに、JsonRpc(IJsonRpcMessageHandler) を使うのがミソ。ハンドラは LengthHeaderMessageHandler を使います。HeaderDelimitedMessageHandler が内部的にはデフォルトで使われているようですが、 Formatter が IJsonRpcMessageTextFormatter を実装していないとダメなようなので使えません。なので、 LengthHeaderMessageHandler を使います。

また、メソッドの引数や戻り値にクラスを指定する場合は各クラスにMessagePackの属性をつけておく必要があります。自身で定義している場合は属性付けれますが、サードパーティーが提供してくれているクラスを指定する場合はどうするんでしょうね。ラップクラスを自身で定義する必要がありそうです。

using System;
using System.IO;
using System.IO.Pipes;
using System.Threading.Tasks;
using StreamJsonRpc;
using MessagePack;

namespace ConsoleApp5
{
    public class Target
    {
        public int Add(int a, int b)
        {
            return a + b;
        }

        public Parameter Test(Parameter p)
        {
            p.Param1 += "-aaa";
            p.Param2.Param2 += "-bbb";
            return p;
        }
    }

    [MessagePackObject]
    public class Parameter
    {
        [Key(0)]
        public string Param1 { get; set; }

        [Key(1)]
        public SubParameter Param2 { get; set; }
    }

    [MessagePackObject]
    public class SubParameter
    {
        [Key(0)]
        public string Param2 { get; set; }
    }

    public class Client
    {
        public async Task Start(string pipename)
        {
            var stream = new NamedPipeClientStream(".", pipename, PipeDirection.InOut, PipeOptions.Asynchronous);
            stream.Connect();
            var handler = new LengthHeaderMessageHandler(stream, stream, new MessagePackFormatter());
            using (var rpc = Utils.NewRpc(handler))
            {
                var ret = await rpc.InvokeAsync<int>("Add", new object[] { 1, 2 });
                Console.WriteLine(ret);

                var ret2 = await rpc.InvokeAsync<Parameter>("Test", new Parameter() { Param1 = "1", Param2 = new SubParameter() { Param2 = "2" } });
                Console.WriteLine(ret2.Param1);
                Console.WriteLine(ret2.Param2.Param2);
            }
        }
    }

    public class Server
    {
        public void Start(string pipename)
        {
            var stream = new NamedPipeServerStream(pipename, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
            stream.WaitForConnection();
            var handler = new LengthHeaderMessageHandler(stream, stream, new MessagePackFormatter());
            using (var rpc = Utils.NewRpc(handler, new Target()))
            {
                rpc.Completion.Wait();
            }
        }
    }

    public class Utils
    {
        public static JsonRpc NewRpc(IJsonRpcMessageHandler handler, object target = null)
        {
            var rpc = new JsonRpc(handler, target);
            try
            {
                rpc.StartListening();

                return rpc;
            }
            catch
            {
                rpc.Dispose();
                throw;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string pipename = "winiotestpipe";

            var ts = Task.Run(() => new Client().Start(pipename));
            var tc = Task.Run(() => new Server().Start(pipename));

            Task.WhenAll(tc, ts).Wait();

            Console.ReadLine();
        }
    }
}