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

.Net(C# WPF)アプリとWin32アプリ(C++)でファイルマッピングを使って構造体データをプロセス間通信させる

More than 3 years have passed since last update.

前置き

 とあるアプリを作成するのにWin32フックDLLと.Netのクライアントアプリ間でデータを連携させる必要があって、このデータ連携にファイルマッピングを採用することにしました。

 ネットの断片的な資料を漁って、別の切り口の断片的な情報を浮かべようと思います(ぉぃ)

ファイルマッピング今昔

 Win32APIだとCreateFileMapping / OpenFileMappingで得たハンドルからMapViewOfFileを使ってポインタを得て構造体にキャストして使えて簡単です。

 .Net4.0未満の場合はそれらAPIをマーシャリングする所から始めるメンドクサーメソッドだったけど、.Net4.0からはMemoryMappedFileで多少イージーになったようでありがたいです。

サンプル

C#のアプリ(WPF)とCのアプリ(今回はWin32SDKをベタで使うタイプ)でサンプルを作ったものを抜粋します。

WPFアプリ
namespace IPCwithCSandCPPSample
{
    /// <summary>
    /// テストクラス:ファイルマッピングへマッピングするためのクラス
    /// C++側で閲覧するためデータ構造が同一になるように配慮すること
    /// </summary>
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]
    public class Test1
    {
        public int A;
        public bool B;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
        public string C;
        public Test2 D = new Test2();
    }

    /// <summary>
    /// テストクラス2
    /// </summary>
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]
    public class Test2
    {
        public int AA;
    }

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private MemoryMappedFile mmf;
        private MemoryMappedViewStream mmvs;
        private Test1 testclass = new Test1();

        public MainWindow()
        {
            InitializeComponent();

            // 頻繁に開け閉めしなくてもよいので起動時に作成
            mmf = MemoryMappedFile.CreateOrOpen("TestMap", (uint)Marshal.SizeOf(typeof(Test1)));
            mmvs = mmf.CreateViewStream();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // UIからデータを取り込む(右辺は全部WPFのテキストボックスです)
            testclass.A = int.Parse(this.A.Text);
            testclass.B = bool.Parse(this.B.Text);
            testclass.C = this.C.Text;
            testclass.D.AA = int.Parse(this.D.Text);

            // 構造体の体裁を整えたデータクラスをファイルマッピングのストリームに流すためにバイト列に変換する
            int size = Marshal.SizeOf(typeof(Test1));
            byte[] bytes = new byte[size];
            IntPtr ptr = Marshal.AllocCoTaskMem(size);
            Marshal.StructureToPtr(testclass, ptr, false);
            Marshal.Copy(ptr, bytes, 0, size);
            Marshal.FreeCoTaskMem(ptr);

            // ファイルマッピングに書く
            mmvs.Write(bytes, 0, size);
            mmvs.Seek(0, System.IO.SeekOrigin.Begin);
        }
    }
}
Win32APIアプリ
/// <summary>
/// テストクラス2
/// </summary>
typedef struct {
    int AA;
}Test2;

/// <summary>
/// テストクラス:ファイルマッピングへマッピングされているクラス
/// </summary>
typedef struct {
    int A;
    BOOL B;
    wchar_t C[256];
    Test2 D;
}Test1;

HANDLE g_Handle = NULL;
Test1 *g_pTest = NULL;

/// <summary>
/// Opens the mapping.
/// </summary>
void OpenMapping()
{
    if (g_Handle != NULL)
    {
        return;
    }

    // C#で作るのでこちらは開くだけ
    g_Handle = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, L"TestMap");
    if (g_Handle == NULL)
    {
        return;
    }

    // マップファイルのアクセスはキャストして構造体から行える
    g_pTest = (Test1*)MapViewOfFile(g_Handle, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(Test1));
    if (g_pTest == NULL)
    {
        CloseHandle(g_Handle);
        return;
    }
}

/// <summary>
/// Closes the mapping.
/// </summary>
void CloseMapping()
{
    if (g_Handle == NULL)
    {
        return;
    }

    CloseHandle(g_Handle);
}

/// <summary>
/// Gets the mapping structure to string.
/// </summary>
/// <param name="string">The string.</param>
void GetMappingStructToString(wchar_t * string)
{
    if (g_pTest == NULL)
    {
        return;
    }

    wsprintf(string, L"%d %d %s %d\n", g_pTest->A, g_pTest->B, g_pTest->C, g_pTest->D.AA);
}

/// <summary>
/// 200msに一回共有メモリを読んで表示を更新するよウィンドウ
/// </summary>
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static int timerID;
    static wchar_t string[256];

    switch (message)
    {
    case WM_CREATE:
        timerID = SetTimer(hwnd, -1, 200, NULL);
        OpenMapping();
        break;
    case WM_TIMER:
        OpenMapping();
        GetMappingStructToString(string);
        InvalidateRect(hwnd, NULL, true);
        break;
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        // TODO: 描画コードをここに追加してください...
        {
            RECT temp;
            GetClientRect(hwnd, &temp);
            DrawText(hdc, string, lstrlen(string), &temp, DT_TOP | DT_LEFT | DT_WORDBREAK);
        }
        EndPaint(hwnd, &ps);
        break;
    case WM_DESTROY:
        CloseMapping();
        KillTimer(hwnd, timerID);
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}

共有する構造体に関して

  • ファイルマッピングの中身はバイト列だ、と言うことを頭に入れて構造体を構成する
  • C#ではクラスでもStructLayout出来るが上記は考慮すること

 一つ目は当然のことですね。アドレス渡されても渡された側は見られません。

typedef struct {
    int A;
    BOOL B;
    wchar_t C[256];
    Test2 D;
}Test1;

こんな感じの構造体にした場合は

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]
public class Test1
{
    public int A;
    public bool B;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
    public string C;
    public Test2 D = new Test2();
}

こんな感じで大丈夫。

注意点

  • C#のboolをStructureToPtrするとレイアウト情報にPack=1と設定しても4バイトになるので、C99のboolで受けるならアライメント調整が必要
  • 多分2バイトや3バイトも強制4バイトアライメントになる予感(知らんけど)
  • CharSet = CharSet.Unicode (ここ数年のWin32SDKベタアプリ側はデフォでUnicode)
  • 配列、文字列なんかは上限値必須

C#側の書き込み側の肝

// 構造体の体裁を整えたデータクラスをファイルマッピングのストリームに流すためにバイト列に変換する
int size = Marshal.SizeOf(typeof(Test1));
byte[] bytes = new byte[size];
IntPtr ptr = Marshal.AllocCoTaskMem(size);
Marshal.StructureToPtr(testclass, ptr, false);
Marshal.Copy(ptr, bytes, 0, size);
Marshal.FreeCoTaskMem(ptr);

// ファイルマッピングに書く
mmvs.Write(bytes, 0, size);
mmvs.Seek(0, System.IO.SeekOrigin.Begin);
  • 構造体→バイト列→ストリームWrite の手順が必要
  • Marshal.AllocCoTaskMemはMarshal.AllocHGlobalでも速度的にはあまり変化がない模様

 mmvs.Seek(testclass.ToBytes(), 0, testclass.Length)と出来るように組んでおくと良いかも

参考:

C#でレガシーな事をする方向けのまとめ

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
ユーザーは見つかりませんでした