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];
}
| メンバー名 | 説明 |
|---|---|
| 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;
}