マーシャリング (Marshaling)

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

Marshalクラス

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

メモリの割り当て

[System.Security.SecurityCritical]
public static IntPtr AllocHGlobal (
    int cb // メモリ内の必要バイト数
    );
AllocHGlobal(Int32) - Marshal.AllocHGlobal メソッド (System.Runtime.InteropServices) | Microsoft Learn

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

[System.Security.SecurityCritical]
public static void FreeHGlobal (
    IntPtr hglobal
    );
Marshal.FreeHGlobal(IntPtr) メソッド (System.Runtime.InteropServices) | Microsoft Learn

マネージドとアンマネージドとのコピー

配列

マネージド配列から、アンマネージド配列へのコピー
[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 Learn
アンマネージド配列から、マネージド配列へのコピー
[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 Learn
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
アンマネージドからアンマネージドへのコピー

これはマーシャリングとは無関係ですが、Buffer.MemoryCopy()やCopyMemory()で、アンマネージド間でコピーできます。c# - Copy data from from IntPtr to IntPtr - Stack Overflow

文字列

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

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

メソッド ANSIへの変換
StringToHGlobalAuto() 必要ならば変換する
StringToHGlobalAnsi() 変換する
StringToHGlobalUni() 変換しない
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

同様の処理は、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 Learn

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

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

ポインタの取得

メソッド  
GetComInterfaceForObject(Object, Type)  
GetIUnknownForObject(Object)  
GetIDispatchForObject(Object)  
   

オブジェクトの解放

Release()

指定のインターフェイスの、参照カウントを減少できます。

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

インターフェイスを指定するpUnkは、GetComInterfaceForObject()などから取得できます。

戻り値は、参照カウントの新しい値です。

共通言語ランタイム (CLR) がCOMオブジェクトの参照カウントを管理するため、通常はこのメソッドを直接使う必要はありません。カスタム マーシャラ (custom marshaler) を試すときに、オブジェクトの有効期間 (lifetime) を手動で管理するような場合に使用します。Remarks - Marshal.Release(IntPtr) Method (System.Runtime.InteropServices) | Microsoft Learn

指定のオブジェクトが使用中の場合、制御が戻らないことがあります。

ReleaseComObject()

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

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

戻り値は、RCWの参照カウントの新しい値です。RCWはラップされたCOMオブジェクトへの参照を1つだけ保持するため、この値は通常0です。

指定のCOMオブジェクトが使用中の場合、制御が戻らないことがあります。

FinalReleaseComObject()

このメソッドの呼び出しは、戻り値が0になるまでReleaseComObject()をくり返し呼ぶのと同じです。Remarks - Marshal.FinalReleaseComObject(Object) Method (System.Runtime.InteropServices) | Microsoft Learn

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

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

refによる参照渡し

特別な処理なくマーシャリング可能なデータならば、refで参照渡しすることで暗黙的にマーシャリングされます。c# - .NET Interop IntPtr vs. ref - Stack Overflow

参考

参考書

Microsoft Learnから検索