yotiky Tech Blog

とあるエンジニアの備忘録

.NET - MemoryMappedFile を使ったプロセス間通信

別々のプロセスで起動したアプリケーションやライブラリで相互に通信する方法のサンプルコードです。今回は共有メモリ、MemoryMappedFile を使います。
.NET Framework 4.0 以降に導入された技術のようです。

今回のサンプルは1つ目の Console アプリで入力した文字列を、2つ目の Console アプリ、WPF アプリ、Unity でそれぞれ読み取って表示するものになります。

f:id:yotiky:20200806021022g:plain

目次

概要

処理の概要を説明すると、同じ名前で MemoryMappedFile を開くと、異なるプロセスでも同じメモリ領域にアクセスできるようになり、 MemoryMappedViewAccesor や MemoryMappedViewStream を通して値の読み書きができます。

MemoryMappedFile には、ファイルに関連付けて操作が終了するとファイルに保存される永続化と、メモリでのみ保持され操作が終了するとGCで開放される非永続化があります。

利用できるデータ型はプリミティブ型( String 除く)と Array あたりです。読み書きの方法は Stream の扱いに近い感じでしょうか。

バイナリにシリアライズしてしまえば何でも送れそうです。 最小限のことをライトにやるには使えるかもしれないので覚えておいても良いでしょう。

MemoryMappedFile のより詳しい情報は公式をご覧下さい。

docs.microsoft.com

実装例

Console

送信側の Console アプリのサンプルコードです。

static void Main(string[] args)
{
    Console.WriteLine("Please enter a message, and then press Enter.");

    using (var sharedMemory = MemoryMappedFile.CreateNew("SharedMemory", 1024))
    {
        while (true)
        {
            var str = Console.ReadLine();
            var data = str.ToCharArray();
            using (var accessor = sharedMemory.CreateViewAccessor())
            {
                accessor.Write(0, data.Length);
                accessor.WriteArray(sizeof(int), data, 0, data.Length);
            }
        }
    }
}

続いて受信側の Console アプリです。同じ値が取れたら間引いてます。

f:id:yotiky:20200806020955g:plain

static void Main(string[] args)
{
    using (var sharedMemory = MemoryMappedFile.OpenExisting("SharedMemory"))
    {
        var latestMsg = string.Empty;
        while (true)
        {
            using (var accessor = sharedMemory.CreateViewAccessor())
            {
                var size = accessor.ReadInt32(0);
                var data = new char[size];
                accessor.ReadArray<char>(sizeof(int), data, 0, data.Length);
                var str = new string(data);
                if (latestMsg != str)
                {
                    latestMsg = str;
                    Console.WriteLine("Data :" + str);
                }
            }

            Thread.Sleep(100);
        }
    }
}

WPF

受信側の WPF アプリのサンプルコードです。 Loadのイベントで処理して、XAML で追加した textBlock に表示しています。

f:id:yotiky:20200806021158g:plain

private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    using (var sharedMemory = MemoryMappedFile.OpenExisting("SharedMemory"))
    {
        await Task.Run(() =>
        {
            var latestMsg = string.Empty;
            while (true)
            {
                using (var accessor = sharedMemory.CreateViewAccessor())
                {
                    var size = accessor.ReadInt32(0);
                    var data = new char[size];
                    accessor.ReadArray<char>(sizeof(int), data, 0, data.Length);
                    var str = new string(data);
                    if (latestMsg != str)
                    {
                        latestMsg = str;
                        Dispatcher.Invoke(() => textBlock.Text += ("Data :" + str + "\r\n"));
                    }
                }

                Thread.Sleep(100);
            }
        });
    }
}

Unity

最後は受信側の Unity アプリのサンプルコードです。 (動作確認は Editor でのみ)

f:id:yotiky:20200806021125g:plain

async void Start()
{
    var sharedMemory = MemoryMappedFile.OpenExisting("SharedMemory");

    await Task.Run(() =>
    {
        var latestMsg = string.Empty;
        while (true)
        {
            using (var accessor = sharedMemory.CreateViewAccessor())
            {
                var size = accessor.ReadInt32(0);
                var data = new char[size];
                accessor.ReadArray<char>(sizeof(int), data, 0, data.Length);
                var str = new string(data);
                if (latestMsg != str)
                {
                    latestMsg = str;
                    Debug.Log("Data :" + str);
                }
            }

            Thread.Sleep(100);
        }
    });
}

サンプルプロジェクト

ソースコード一式は以下においてあります。

github.com

参考

以下のサイトを参考にさせていただきました。ありがとうございます。