C#の相互運用(C#からネイティブDLLの呼び出しの場合)で構造体の中に構造体配列のポインタを持つ関数のマーシャリングのやり方です。
ネイティブDLL側の宣言
ネイティブのDLL側の宣言は以下のようになっているとします。
// Sample.h // 外部に公開する関数 extern "C" __declspec(dllexport) int WINAPI foo(MyStructTop* top) { top->len = 3; top->childs = (MyStructChild*)malloc(sizeof(MyStructChild) * top->len); for (int i = 0; i < top->len; i++) { top->childs[i].a = i; top->childs[i].b = i + 1; top->childs[i].c = i + 2; } return 0x10; } // メモリ開放用の関数 extern "C" __declspec(dllexport) void WINAPI freeObj(void* p) { free(p); }
中身でmallocしているので、最後にメモリを解放するためにfree関数をラップ下freeObjも併せて公開しています。
内容は動作確認のためのサンプルとして適当に構造体を3つ確保しています。
上記の引数の構造体は以下のようになっています。
// 子要素の構造体 typedef struct { int a; int b; int c; } MyStructChild; // 親要素の構造体 typedef struct { // 割り当てた構造体のサイズ int len; // 動的に確保した子要素の配列の先頭ポインタ MyStructChild* childs; } MyStructTop;
C#側の実装方法
C#側は上記の関数と同じ名前の関数をDllImportで宣言します。
// // 外部に公開する関数 // extern "C" __declspec(dllexport) int WINAPI foo(MyStructTop* top) [DllImport("NativeDLL.dll", EntryPoint = "foo")] public static extern int Foo(ref MyStructTop top); // refつけないとポインタ扱いにならない // // メモリ開放用の関数 // extern "C" __declspec(dllexport) void WINAPI free(void* p) [DllImport("NativeDLL.dll", EntryPoint = "freeObj")] public static extern int FreeObj(IntPtr target);
foo(MyStructTop* top) は IntPtr ではなく ref を付けて構造体をそのまま渡すように宣言するのがポイントです。
次に、ネイティブ側の構造体と同じサイズの構造体をC#側でも宣言します。
[StructLayout(LayoutKind.Sequential)] public struct MyStructTop { // // 親要素の構造体 // typedef struct // { // // 割り当てた構造体のサイズ // int len; // // 動的に確保した子要素の配列の先頭ポインタ // MyStructChild* childs; // } MyStructTop; public int Length; public IntPtr Childs; } [StructLayout(LayoutKind.Sequential)] public struct MyStructChild { // // 子要素の構造体 // typedef struct // { // int a; // int b; // int c; // } MyStructChild; public int A; public int B; public int C; }
サイズさえ合っていれば大丈夫です。ネイティブ側で「MyStructChild* childs;」と宣言していた個所は、「IntPtr Childs」でポインタを受け取るようにしています。
次に「IntPtr Childs」の構造体ポインタを.NETの構造体への変換の仕方です。
public static void Main(string[] args) { // まずはref付きで普通に呼び出す var top = new MyStructTop(); Foo(ref top); // ポインタの子要素の.NET側の入れ物を同じサイズで用意する var childs = new MyStructChild[top.Length]; // 構造体のネイティブのサイズを取得 int size = Marshal.SizeOf(typeof(MyStructChild)); for (int i = 0; i < top.Length; i++) { // サイズと位置があっていればOKの精神でポインタを加算 IntPtr address = top.Childs + size * i; childs[i] = Marshal.PtrToStructure<MyStructChild>(address); } // 最後にメモリを開放する FreeObj(top.Childs); Console.WriteLine(top.Length); for(int i = 0; i < top.Length; i++) { Console.WriteLine($"A = {childs[i].A}, B = {childs[i].B}, C ={childs[i].C}"); } > 実行結果 > 3 > A = 0, B = 1, C =2 > A = 1, B = 2, C =3 > A = 2, B = 3, C =4 }
これで、出力結果もあっているので無事変換できました。
MyStructChildのサイズをtop.Childsのアドレスに.NET上で足し算で加算します。コメントにも書きましたが、ここら辺はサイズさえ合っていればOKの精神です。
また、.NETの構造体に移し終えたらChildsのポインタは解放しておきます。