週末!プログラミング部

ソフトウェア開発ネタを中心に自分でいろいろ調べた内容を自分の勝手な解釈で思うがままに書いくためのブログ。サンプルソースコード、API、プラットフォーム、プログラミング言語、開発環境などを調査、分析して追求いく予定です。

MATLABとC#で共有メモリを使ったプロセス間通信をさせてみる

今回はMATLABC#で共有メモリを使ったプロセス間通信を試みたいと思います。

MATLAB同士で共有メモリを使ったプロセス間通信としてmemmapfileを使う例があります。
しかし、memmapfileはページファイル内のメモリマップの読み書きはできません。
また、メモリマッピング用のMATLAB関数はいまのところmemmapfileだけのようです。
したがってMATLAB単体ではページファイルのメモリマップにアクセスすることができないため、CのMapViewOfFile()やC#のSystem.IO.MemoryMappedFilesとの連携は難しそうです。

そこで以前行った、「CとC#で共有メモリを使ったプロセス間通信」と 「MATLABからCライブラリを呼び出し」を組み合わせて、 MATLABC#の間にCライブラリを挟むことで、間接的に共有メモリによるプロセス間通信をさせてみたいと思います。

共有メモリ通信を行うCライブラリを作る

まずは、ヘッダファイルです。
共有メモリ上に配置するデータ構造体やライブラリ関数のプロトタイプを定義してあります。

#ifndef SHMEM_H
#define SHMEM_H

typedef struct
{
    double   p1;
    short    p2;
    int      p3;
} SHARED_MEMORY_DATA;

__declspec(dllexport) void OpenSharedMemory(void);
__declspec(dllexport) void CloseSharedMemory(void);
__declspec(dllexport) SHARED_MEMORY_DATA* GetSharedMemoryObject(void);

#endif

続いて、共通メモリ通信を行うライブラリ実装です。
やや手抜きですがお許しください(; ・`ω・´)

#include <mex.h>
#include <windows.h>
#include "shmem.h"

static HANDLE              gSharedMemoryHandle = NULL;
static SHARED_MEMORY_DATA* gMappingObject      = NULL;

/* 共有メモリを開く */
__declspec(dllexport) void OpenSharedMemory(void)
{
    gSharedMemoryHandle = OpenFileMapping( FILE_MAP_ALL_ACCESS,
                                           FALSE,
                                           "shmem" );
    
    if( gSharedMemoryHandle == NULL )
    {
        mexErrMsgTxt("not opened map file...");
    }
    
    gMappingObject = (SHARED_MEMORY_DATA*)MapViewOfFile( gSharedMemoryHandle,
                                        FILE_MAP_ALL_ACCESS,
                                        0,
                                        0,
                                        sizeof(SHARED_MEMORY_DATA));
}

/* 共有メモリを閉じる */
__declspec(dllexport) void CloseSharedMemory(void)
{
    if(gMappingObject != NULL)
    {
        UnmapViewOfFile(gMappingObject);
    }
    
    if(gSharedMemoryHandle != NULL)
    {
        CloseHandle(gSharedMemoryHandle);
    }
}

/* 共有メモリへのポインタを取得 */
__declspec(dllexport) SHARED_MEMORY_DATA* GetSharedMemoryObject(void)
{
    return (gMappingObject);
}

/* MEX関数 */
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray*prhs[])
{
    /* 何も行わない */
}
OpenSharedMemory()

共有メモリをオープンする関数です。
共有メモリのオープンにはwindowsAPIのOpenFileMapping()とMapViewOfFile()を使用しています。
MapViewOfFile()で取得した共有メモリへのポインタはグローバル変数に保持します。
※あとは関数の返値で共有メモリがオープンできたかどうかを返そうかと考えていましたが今は手抜きしてます笑

CloseSharedMemory()

共有メモリをクローズする関数です。
グローバル変数に保持している共有メモリのハンドルやポインタを解放します。

GetSharedMemoryObject()

共有メモリへのポインタを取得を取得する関数です。
グローバル変数に保持されている共有メモリへのポインタへのポインタを返します。

動かしてみる

C#側は、「CとC#で共有メモリを使ったプロセス間通信」で作ったプログラムを再利用します。
実行手順は以下のとおりです。

(1) MATLABでCライブラリをビルドしてCライブラリをロードする。

>> mex shmem.c
>> loadlibrary('shmem','shmem.h');

(2) C#側のプログラムを起動させるて共有メモリに書き込みを行う

共有メモリ書き込み
-----------------------
567.987
0
10
-----------------------

(3) MATLABでCライブラリのOpenSharedMemory()を呼び出す。

>> calllib('shmem', 'OpenSharedMemory');

(4) MATLABでCライブラリのGetSharedMemoryObject()を呼び出した後、p.Valueで共有メモリの値を読み込んでみる。
※ここまでくれば、MATLABから共有メモリの読み書きはポインタオブジェクトを使って行うことができます。

>> p = calllib('shmem', 'GetSharedMemoryObject');
>> p.Value

ans = 

  フィールドをもつ struct:

    p1: 567.9870
    p2: 0
    p3: 10

(5) MATLABで共有メモリの値を書き換えてみる。

>> p.Value.p1 = 123.456;

(6) C#で共有メモリの値を読み取ってみる。

共有メモリ読み込み
-----------------------
123.456
0
10
-----------------------

(7) MATLABワークスペースをクリアしてCライブラリを解放する

>> clear p;
>> calllib('shmem', 'CloseSharedMemory');
>> unloadlibrary('shmem');

以下動作イメージです。 f:id:NATSU_NO_OMOIDE:20201229233524g:plain

こんな感じで動いていたら成功です!
MATLAB単体では外部プロセスと共有メモリを使用したプロセス間通信はできませんでしたが Cライブラリでワンクションを挟むことで期待したことが実現できました。
(実行速度とかの問題はさておき。)
なにかの参考になれば幸いです(・`ω´・)b