Help us understand the problem. What is going on with this article?

C#,C++ アプリケーション間で共有メモリに構造体を読み書きしてみる

前置き

当記事は、前回の記事「C++アプリケーションとC#アプリケーションで値をやり取りしてみる」 の続編です。

前回の記事では、動的に確保した共有メモリで直接値を読み書きしましたが、
そのままではかなり不便です。

そこで今回は共有メモリに構造体を書き込み、そこから読み書きしてみたいと思います。

Untitled Diagram (4).png

実践 - C#側

まずは、C# .NET Framework WinForm 側で簡易的なフォームを作成し、単体で実践してみます。
ソースコードは以下の通りです。

Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace QuiitaShareMemoryStruct
{
    public partial class Form1 : Form
    {
        ////////////////////////////////////////////////////////////////////////
        // 必要なものを定義
        ////////////////////////////////////////////////////////////////////////
        MemoryMappedFile share_mem = null;
        MemoryMappedViewAccessor accessor = null;

        ////////////////////////////////////////////////////////////////////////
        // 共有メモリ名
        ////////////////////////////////////////////////////////////////////////
        const string sharedMemoryName = "MySharedMemory";

        ////////////////////////////////////////////////////////////////////////
        // 使用する構造体
        ////////////////////////////////////////////////////////////////////////
        public struct _MY_DATA_STRUCT
        {
            public int myInt32;
            public float myFloat;
            public bool myBool;
        }

        ////////////////////////////////////////////////////////////////////////
        // コンストラクタ
        ////////////////////////////////////////////////////////////////////////
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }

        ////////////////////////////////////////////////////////////////////////
        // 「共有メモリを読む」ボタンをクリックしたときに発生するイベント
        ////////////////////////////////////////////////////////////////////////
        private void btnRead_Click(object sender, EventArgs e)
        {
            var result = ReadSharedMemoryAsStruct<_MY_DATA_STRUCT>(sharedMemoryName);
            MessageBox.Show(string.Format("MyInt32: {0}\nMyFloat: {1}\nMyBool: {2}", result.myInt32, result.myFloat, result.myBool));
        }

        ////////////////////////////////////////////////////////////////////////
        // 「共有メモリに書き込み」ボタンをクリックしたときに発生するイベント
        ////////////////////////////////////////////////////////////////////////
        private void btnWrite_Click(object sender, EventArgs e)
        {
            _MY_DATA_STRUCT myStruct = new _MY_DATA_STRUCT();
            myStruct.myInt32 = (int)numericUpDown1.Value;
            float res;
            myStruct.myFloat = float.TryParse(textBox1.Text, out res) ? res : 999.0f;
            myStruct.myBool = checkBox1.Checked;
            if (!WriteSharedMemoryAsStruct<_MY_DATA_STRUCT>(sharedMemoryName, myStruct, true))
            {
                MessageBox.Show("書き込みに失敗しました。");
            }
            else
            {
                MessageBox.Show("書き込みに成功しました。");
            }
        }

        ////////////////////////////////////////////////////////////////////////
        // 共有メモリを構造体として読み取るジェネリック関数
        ////////////////////////////////////////////////////////////////////////
        private T ReadSharedMemoryAsStruct<T>(string sharedMemoryName, bool createOrOpen = false)
        where T : struct
        {
            //構造体を定義
            T result = new T();

            try
            {
                if (share_mem == null)
                {
                    if(createOrOpen)
                    {
                        share_mem = MemoryMappedFile.CreateOrOpen(sharedMemoryName, 1024 * 10);
                    }
                    else
                    {
                        share_mem = MemoryMappedFile.OpenExisting(sharedMemoryName);
                    }
                }

                if (accessor == null)
                {
                    accessor = share_mem.CreateViewAccessor();
                }

                accessor?.Read(0, out result);
                return result;
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine("共有メモリが見つかりませんでした");
                return result;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return result;
            }
        }

        ////////////////////////////////////////////////////////////////////////
        // 共有メモリに構造体として書き込むジェネリック関数
        ////////////////////////////////////////////////////////////////////////
        private bool WriteSharedMemoryAsStruct<T>(string sharedMemoryName, T targetStruct, bool createOrOpen = false)
        where T : struct
        {
            try
            {
                if (createOrOpen)
                {
                    share_mem = MemoryMappedFile.CreateOrOpen(sharedMemoryName, 1024 * 10);
                }
                else
                {
                    share_mem = MemoryMappedFile.OpenExisting(sharedMemoryName);
                }

                if (accessor == null)
                {
                    accessor = share_mem.CreateViewAccessor();
                }
                accessor.Write(0, ref targetStruct);

                return true;
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine("共有メモリが見つかりませんでした");
                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
        }

        ////////////////////////////////////////////////////////////////////////
        // リソースの破棄
        ////////////////////////////////////////////////////////////////////////
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            accessor?.Dispose();
            share_mem?.Dispose();
        }
    }
}

解説

ReadSharedMemoryAsStruct() 関数

こちらは、該当共有メモリに対して構造体として読み取る関数です。

まず初めに、関数の結果として出力する構造体メンバ変数を定義します。
今回はジェネリック関数として定義しましたので、ここでいうTは構造体であるということが保証されています。

where T : struct

T result = new T();

次に、MemoryMappedFileMemoryMappedViewAccessornullであれば新規生成し、
アクセサを使用して該当共有メモリに対して書き込みを行います。
第一引数はpositionつまりオフセットです。
今回は構造体のみを書き込むので、0とします。
第二引数で構造体を受け取ります。

accessor.Read(0, out res);

使用例:

var result = ReadSharedMemoryAsStruct<_YOUR_STRUCT>("yourSharedMemoryName");

WriteSharedMemoryAsStruct() 関数

こちらは、該当共有メモリに対して構造体として書き込む関数です。

まず初めに、読み取りと同じようにヌルチェックを行います。

(省略)

アクセサを使用して該当共有メモリに対して書き込みを行います。
第一引数はpositionつまりオフセットです。
今回は構造体のみを書き込むので、0とします。
第二引数には、関数で受け取った構造体T targetStructの参照を渡します。

accessor.Write(0, ref targetStruct);

リソースの開放

使い終わったリソースはきちんと解放してあげます。

accessor?.Dispose();
share_mem?.Dispose();

実行結果

fはパースしてくれないらしく、float.TryParse()に失敗して999が書き込まれちゃってますが、
無事成功しました。

eqhsn-z2z96.gif

実践 - C++側

C++側でも単体で実行してみます。

main.cpp
#include <iostream>
#include <Windows.h>

#define CONSOLE_PAUSE() system("pause")
#define VOID void
#define BOOLEAN bool

////////////////////////////////////////////////////////////////////////
// 構造体
////////////////////////////////////////////////////////////////////////
typedef struct _MY_DATA_STRUCT
{
    int myInt32;
    float myFloat;
    bool myBool;
}MY_DATA_STRUCT;

////////////////////////////////////////////////////////////////////////
// 必要なものを定義
////////////////////////////////////////////////////////////////////////
constexpr auto SHARED_MEMORY_NAME = L"MySharedMemory";
constexpr auto SHARED_MEMORY_SIZE = 1024 * 10;
static HANDLE hSharedMemory = NULL;
MY_DATA_STRUCT* response;

////////////////////////////////////////////////////////////////////////
// 共有メモリを作成する関数
////////////////////////////////////////////////////////////////////////
BOOLEAN CreateSharedMemory(const wchar_t* sharedMemoryName, DWORD size)
{
    if (hSharedMemory)
    {
        return FALSE;
    }

    hSharedMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, sharedMemoryName);
    if (!hSharedMemory || hSharedMemory == INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
// ハンドルの初期化
////////////////////////////////////////////////////////////////////////
BOOLEAN InitializeSharedMemory()
{
    hSharedMemory = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHARED_MEMORY_NAME);

    if (!hSharedMemory || hSharedMemory == INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
// ハンドルの破棄
////////////////////////////////////////////////////////////////////////
VOID UnInitializeSharedMemory()
{
    if (hSharedMemory || hSharedMemory != INVALID_HANDLE_VALUE)
    {
        CloseHandle(hSharedMemory);
    }
}

////////////////////////////////////////////////////////////////////////
// 共有メモリの読み取り
////////////////////////////////////////////////////////////////////////
BOOLEAN ReadSharedMemory()
{
    if (!hSharedMemory)
    {
        return FALSE;
    }

    response = (MY_DATA_STRUCT*)MapViewOfFile(hSharedMemory, FILE_MAP_ALL_ACCESS, NULL, NULL, SHARED_MEMORY_SIZE);
    if (response == NULL || !response)
    {
        return FALSE;
    }

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
// 共有メモリの書き込み
////////////////////////////////////////////////////////////////////////
BOOLEAN WriteSharedMemory(_MY_DATA_STRUCT writeit)
{
    if (!response || !hSharedMemory)
    {
        return FALSE;
    }

    *response = writeit;

    return TRUE;
}

int main()
{
    SetConsoleTitleA("Title");

    std::cout << "Hello SharedMemory!\nなにかキーを押してください。";
    std::cin.get();

    if (!CreateSharedMemory(SHARED_MEMORY_NAME, SHARED_MEMORY_SIZE))
    {
        std::cout << "共有メモリの作成に失敗しました。\n";
        goto FINISH;
    }

    if (!InitializeSharedMemory())
    {
        std::cout << "共有メモリの初期化に失敗しました。\n";
        goto FINISH;
    }

    if (!ReadSharedMemory())
    {
        std::cout << "共有メモリの読み取りに失敗しました。\n";
        goto FINISH;
    }

    _MY_DATA_STRUCT s;
    s.myInt32 = 333;
    s.myFloat = 634.f;
    s.myBool = TRUE;

    if (!WriteSharedMemory(s))
    {
        std::cout << "共有メモリの書き込みに失敗しました。\n";
        UnInitializeSharedMemory();
        CONSOLE_PAUSE();
    }

    printf("=== Read ===\nmyInt32: %u\nmyFloat: %f\nmyBool: %u\n", response->myInt32, response->myFloat, response->myBool);

    FINISH:
    UnInitializeSharedMemory();
    CONSOLE_PAUSE();
}

解説

CreateSharedMemory() 関数

CreateFileMapping()でハンドルを生成します。

HANDLE hSharedMemory = 
CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, sharedMemoryName);

InitializeSharedMemory() 関数

OpenFileMapping()でハンドルを開きます。

HANDLE hSharedMemory =
OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, SHARED_MEMORY_NAME);

UnInitializeSharedMemory() 関数

リソースの開放です。

CloseHandle(hSharedMemory);

ReadSharedMemory() 関数

共有メモリから構造体を読み取ります。

auto response =
(MY_DATA_STRUCT*)MapViewOfFile
(hSharedMemory, FILE_MAP_ALL_ACCESS, NULL, NULL, SHARED_MEMORY_SIZE);

WriteSharedMemory() 関数

共有メモリに構造体を書き込みます。

_MY_DATA_STRUCT writeit;
*response = writeit;

実行結果

_MY_DATA_STRUCT s;
s.myInt32 = 333;
s.myFloat = 634.f;
s.myBool = TRUE;

WriteSharedMemory(s);

printf("=== Read ===\nmyInt32: %u\nmyFloat: %f\nmyBool: %u\n", 
response->myInt32, response->myFloat, response->myBool);

無事成功しました。

image.png

C#とC++クロスでやってみる

こちらも問題なく動作しました。

image.png

最後に

最後までご覧いただきありがとうございました。
コードミス/誤字・脱字や間違った情報がございましたら、お手数ですがコメント欄にてご指摘いただけますと幸いです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした