C#のためのC++の配列、構造体、ポインタの変換処理

C#(.NET Framework)では、構造体(Managed)やメンバに対して属性を指定することにより、C言語やC++(Unmanaged)間でデータの相互交換を行うことができます。

MarshalAs属性を用いた構造体の作成

C#では、Cと違って定義だけでは配列の長さがわかりません。そこで、C#(.NET Framework)では、構造体やメンバに対して属性を指定することにより、ManagedとUnmanagedで相互交換を行わせる事ができるようになっている。

例えば、以下のようなC++の構造体を、C#でアクセスします。

struct Info
{
  int    index;
  char   name[128];
  int    statuses[50];
};

この場合、Managed構造体をメンバ順序もサイズも同じUnmanaged構造体を定義するには、属性を使用して以下のように定義します。

[StructLayout(LayoutKind.Sequential)]    // メンバーは定義順に格納される
public struct Info
{
  [MarshalAs(UnmanagedType.I4)]        // Signed int(4Byte)で格納(属性無くても大丈夫です)
  public    int    index;
  [MarshalAs(UnmanagedType.ByValTStr,SizeConst=128)]    // char[128]で格納
  public    string name;
  [MarshalAs(UnmanagedType.ByValArray,SizeConst=50)]    // int[50]で格納
  public    int[] statuses;
  // ↑bool, byte, char, short, int, long, sbyte, ushort,
  // uint, ulong, float, double配列の場合は属性を付けずに以下のようにしても良い
  // この場合は配列のインスタンスを作成する必要は無い。→これはunsafeブロック中でしか使えないらしい・・・
  // public fixed int statuses[50];
}
UnmanagedType 列挙体
メンバー名 説明
ByValArray MarshalAsAttribute.Value プロパティを ByValArray に設定した場合、SizeConst フィールドは、配列の要素数を示すように設定する。
ByValTStr 構造体内の C スタイルの固定サイズ文字列 (char s[5] など) と同様に機能する。 マネージ コードでのこの動作は、Microsoft Visual Basic 6.0 の動作 (終端が null でない。例 :MyString As String * 5) とは異なる。

Marshal.StructureToPtrによる構造体からポインタへの変換

Marshal.StructureToPtrを使います。ptr パラメーターが指す、事前に割り当てられたメモリ ブロックに structure の内容をコピーします。

static IntPtr ToPtr(Hoge obj)
{
    IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Hoge)));
    Marshal.StructureToPtr(obj, ptr, false);
    return ptr;
}

Marhsal.Copyを使用した構造体からバイト配列への変換

バイト配列に変換したい場合は、ポインタへ変換後、Marhsal.Copyします。アンマネージ メモリ ポインターのデータを 8 ビット符号なし整数のマネージ配列にコピーします。

static byte[] ToBytes(Hoge obj)
{
    int size = Marshal.SizeOf(typeof(Hoge));
    IntPtr ptr = Marshal.AllocHGlobal(size);
    Marshal.StructureToPtr(obj, ptr, false);

    byte[] bytes = new byte[size];
    Marshal.Copy(ptr, bytes, 0, size);
    Marshal.FreeHGlobal(ptr);
    return bytes;
}

Marshal.PtrToStructureを使用したポインタから構造体への変換

Marshal.PtrToStructureをすると、
。メモリの内容を元に構造体などを復元することができます。これでC言語などとデータをやりとりできます。構造体の配列に関し、MarshalAs属性を使用すれば扱えますが、この場合、コンパイル時にサイズを決定した固定長配列に限られます。

static Hoge ToStruct(IntPtr ptr)
{
    return (Hoge)Marshal.PtrToStructure(ptr, typeof(Hoge)); 
}

GCHandleによるバイト配列から構造体への変換

先ほどと同様にMarshal.Copyを使うこともできますが、ここではもう一つの方法としてGCHandleでバイト配列のポインタを取得し、それを用いてMarshal.PtrToStructureを行います。GCHandleは、GCHandle::Alloc によってmanagedオブジェクトのハンドルを取得します。ハンドルはIntPtrに変換でき、またそのIntPtrからハンドルに戻せるので、unmanagedクラスではポインタを持っておけば、ポインタ経由で、managedなオブジェクトへの参照ができます。

static Hoge ToStruct(byte[] bytes)
{
    GCHandle gch = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    Hoge result = (Hoge)Marshal.PtrToStructure(gch.AddrOfPinnedObject(), typeof(Hoge));
    gch.Free();
    return result;
}