LoginSignup
12
15

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-11-18

前置き

 とあるアプリを作成するのに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#でレガシーな事をする方向けのまとめ

12
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
15