マーシャリング (Marshaling)

相互運用マーシャリング (Interop Marshaling) によって、マネージ型とアンマネージ型との間でメソッドの引数と戻り値を変換できます。

Marshalクラス

Marshalクラスからは、アンマネージコードを扱うときに利用できるメソッドが数多く提供されます。

[SecurityCriticalAttribute]
public static IntPtr AllocHGlobal(
    int cb // メモリ内の必要バイト数
)
[SecurityCriticalAttribute] public static IntPtr AllocHGlobal( int cb ) | MSDN

cbバイト分のメモリを確保し、そのメモリへのポインタを取得できます。確保したメモリは、Marshal.FreeHGlobal()で必ず解放します。

配列

Copy()

マネージ配列から、アンマネージ配列へのコピー
[SecurityCriticalAttribute]
public static void Copy(
    int[] source,       // コピー元の配列
    int startIndex,     // コピー元の配列の開始位置
    IntPtr destination, // コピー先のポインタ
    int length          // コピーする要素数
)
Copy(Int32[], Int32, IntPtr, Int32) - Marshal.Copy Method (System.Runtime.InteropServices) | Microsoft Docs
アンマネージ配列から、マネージ配列へのコピー。
[System.Security.SecurityCritical]
public static void Copy (
    IntPtr source,     // コピー元のポインタ
    int[] destination, // コピー先の配列
    int startIndex,    // コピー先の配列の開始位置
    int length         // コピーする要素数
    );
Copy(IntPtr, Int32[], Int32, Int32) - Marshal.Copy Method (System.Runtime.InteropServices) | Microsoft Docs
int[] arary1 = { 1, 2, 3 };

int size = Marshal.SizeOf(arary1[0]) * arary1.Length;
IntPtr ptr = Marshal.AllocHGlobal(size); // 配列を格納できる大きさのメモリを割り当てる

try
{
    // マネージ配列を、アンマネージ配列へコピーする
    Marshal.Copy(arary1, 0, ptr, arary1.Length);

    unsafe
    {
        // アンマネージ配列に格納された値を確認する
        int p0 = *(int*)ptr;       // 1
        int p1 = *((int*)ptr + 1); // 2
        int p2 = *((int*)ptr + 2); // 3
    }

    // アンマネージ配列を、マネージ配列へコピーする
    int[] arary2 = new int[arary1.Length];
    Marshal.Copy(ptr, arary2, 0, arary1.Length);
}
finally
{
    // アンマネージ配列のためのメモリを解放する
    Marshal.FreeHGlobal(ptr);
}
使用例 - Marshal.Copy メソッド (Int32[], Int32, IntPtr, Int32) (System.Runtime.InteropServices) | MSDN

文字列

[SecurityCriticalAttribute]
public static IntPtr StringToHGlobalAuto(
    string s // コピーする文字列
)
[SecurityCriticalAttribute] public static IntPtr StringToHGlobalAuto( string s ) | MSDN

マネージ文字列の内容を、アンマネージ メモリーへコピーできます。このメソッドは必要なときにANSIに変換しますが、これを強制したいならばStringToHGlobalAnsi()を用います。

メソッド ANSIへの変換
StringToHGlobalAuto 必要ならば変換する
StringToHGlobalAnsi 変換する
StringToHGlobalUni 変換しない

C#

String managed = "ABC";

IntPtr ptr = Marshal.StringToHGlobalAuto(managed);
unsafe
{
    char* str = (char*)(ptr.ToPointer());

    // str[0] … 65 'A'  char
    // str[1] … 66 'B'  char
    // str[2] … 67 'C'  char
    // str[3] … 0 '\0'  char
}
Marshal.FreeHGlobal(ptr);

C++/CLI

String^ managed = gcnew String("ABC");

IntPtr ptr = Marshal::StringToHGlobalAuto(managed);
System::Char* str = static_cast<System::Char*>(ptr.ToPointer());

// str[0] … 65 'A'  wchar_t
// str[1] … 66 'B'  wchar_t
// str[2] … 67 'C'  wchar_t
// str[3] … 0 '\0'  wchar_t

Marshal::FreeHGlobal(ptr);

これは、次のように記述しても同じです。

System::Char* str = static_cast<System::Char*>(Marshal::StringToHGlobalAuto(managed).ToPointer());

// ...

Marshal::FreeHGlobal(IntPtr(str)); // ポインタを取得してから渡す

C++/CLIのcharは8ビットであり、C#の16ビットであるそれとは異なるため、charにキャストすると、

// str[0] … 65 'A'  char
// str[1] … 0 '\0'  char
// str[2] … 66 'B'  char
// str[3] … 0 '\0'  char
// str[4] … 67 'C'  char
// str[5] … 0 '\0'  char

のように変換されます。これに対処するにはStringToHGlobalAnsi()で、ANSI形式に変換した上でコピーします。

char* str = static_cast<char*>(Marshal::StringToHGlobalAnsi(managed).ToPointer());

オブジェクト

[System.Security.SecurityCritical]
[System.Runtime.InteropServices.ComVisible(true)]
public static void StructureToPtr (
    object structure, // コピー元のオブジェクト
    IntPtr ptr,       // コピー先のポインタ
    bool fDeleteOld
    );
StructureToPtr(Object, IntPtr, Boolean) - Marshal.StructureToPtr メソッド (System.Runtime.InteropServices) | Microsoft Docs

マネージ オブジェクトのデータを、アンマネージ メモリーへコピーできます。

fDeleteOldをtrueとすると、データをコピーする前にDestroyStructure()が呼ばれptrの領域が開放されます。そこに有効なデータが含まれているときにfalseとすると、メモリリークを引き起こす恐れがあります。

解放

[System.Security.SecurityCritical]
public static int Release (IntPtr pUnk);
Marshal.Release(IntPtr) Method (System.Runtime.InteropServices) | Microsoft Docs

指定のインターフェイスの、参照カウントを減少できます。戻り値で、その参照カウントの新しい値が返されます。

[System.Security.SecurityCritical]
public static int ReleaseComObject (object o);
Marshal.ReleaseComObject(Object) Method (System.Runtime.InteropServices) | Microsoft Docs

指定のCOMオブジェクトに関連付けられているランタイム呼び出し可能ラッパー (Runtime Callable Wrapper : RCW) の、参照カウントを減少できます。

クラスと構造体のデータメンバの配置

C++のクラスや構造体のラッパーを作成するときには、そのデータメンバのメモリ内での配置を明示するためにStructLayout属性を指定する必要があります。

参考

参考書

MSDN (Microsoft Developer Network) から検索