次の方法で共有


既定のマーシャリング動作

相互運用マーシャリングは、メソッド パラメーターに関連付けられているデータがマネージド メモリとアンマネージド メモリの間を通過する場合の動作を決定するルールに対して動作します。 これらの組み込みルールは、データ型変換などのマーシャリング アクティビティ、呼び出し先が渡されたデータを変更して呼び出し元にそれらの変更を返すことができるかどうか、およびマーシャラーがパフォーマンスの最適化を提供する状況を制御します。

このセクションでは、相互運用マーシャリング サービスの既定の動作特性を識別します。 配列、ブール型、char 型、デリゲート、クラス、オブジェクト、文字列、構造体のマーシャリングに関する詳細情報が表示されます。

ジェネリック型のマーシャリングはサポートされていません。 詳細については、「 ジェネリック型を使用した相互運用」を参照してください

相互運用マーシャラーを使用したメモリ管理

相互運用マーシャラーは常に、アンマネージ コードによって割り当てられたメモリの解放を試みます。 この動作は COM メモリ管理規則に準拠していますが、ネイティブ C++ を管理する規則とは異なります。

プラットフォーム呼び出しを使用するときにネイティブの C++ 動作 (メモリ解放なし) が予想される場合、混乱が発生する可能性があります。プラットフォーム呼び出しでは、ポインターのメモリが自動的に解放されます。 たとえば、C++ DLL から次のアンマネージ メソッドを呼び出しても、メモリは自動的に解放されません。

アンマネージ シグネチャ

BSTR MethodOne (BSTR b) {  
     return b;  
}  

ただし、プラットフォーム呼び出しプロトタイプとしてメソッドを定義し、各 BSTR 型を String 型に置き換え、 MethodOneを呼び出すと、共通言語ランタイムは b を 2 回解放しようとします。 マーシャリング動作は、文字列型ではなくIntPtr型を使用して変更できます。

ランタイムでは、常に Windows の CoTaskMemFree メソッドと、他のプラットフォームの free メソッドを使用してメモリを解放します。 使用しているメモリが Windows の CoTaskMemAlloc メソッドまたは他のプラットフォームの malloc メソッドで割り当てられていない場合は、 IntPtr を使用し、適切なメソッドを使用して手動でメモリを解放する必要があります。 同様に、Kernel32.dllから GetCommandLine 関数を使用してカーネル メモリへのポインターを返す場合など、メモリを解放しない状況で、メモリの自動解放を回避できます。 メモリを手動で解放する方法の詳細については、 バッファーのサンプルを参照してください。

クラスの既定のマーシャリング

クラスは COM 相互運用によってのみマーシャリングでき、常にインターフェイスとしてマーシャリングされます。 場合によっては、クラスのマーシャリングに使用されるインターフェイスは、クラス インターフェイスと呼ばれます。 クラス インターフェイスを好みのインターフェイスでオーバーライドする方法については、「 クラス インターフェイスの概要」を参照してください。

COM にクラスを渡す

マネージド クラスが COM に渡されると、相互運用マーシャラーは自動的に COM プロキシでクラスをラップし、プロキシによって生成されたクラス インターフェイスを COM メソッド呼び出しに渡します。 プロキシは、クラス インターフェイスのすべての呼び出しをマネージド オブジェクトに委任します。 プロキシは、クラスによって明示的に実装されていない他のインターフェイスも公開します。 プロキシは、クラスの代わりに IUnknownIDispatch などのインターフェイスを自動的に実装します。

.NET コードにクラスを渡す

コクラスは、通常、COM のメソッド引数として使用されません。 代わりに、通常、既定のインターフェイスがコクラスの代わりに渡されます。

インターフェイスがマネージド コードに渡されると、相互運用マーシャラーはインターフェイスを適切なラッパーでラップし、ラッパーをマネージド メソッドに渡す役割を担います。 使用するラッパーの決定が難しい場合があります。 COM オブジェクトのすべてのインスタンスには、オブジェクトが実装するインターフェイスの数に関係なく、1 つの一意のラッパーがあります。 たとえば、5 つの異なるインターフェイスを実装する 1 つの COM オブジェクトには、ラッパーが 1 つだけ存在します。 同じラッパーは、5 つのインターフェイスすべてを公開します。 COM オブジェクトの 2 つのインスタンスが作成されると、ラッパーの 2 つのインスタンスが作成されます。

ラッパーが有効期間を通じて同じ型を維持するには、相互運用マーシャラーは、オブジェクトによって公開されたインターフェイスがマーシャラーを初めて通過する際に、正しいラッパーを識別する必要があります。 マーシャラーは、オブジェクトが実装するインターフェイスのいずれかを調べることで、オブジェクトを識別します。

たとえば、マーシャラーは、マネージド コードに渡されたインターフェイスをラップするためにクラス ラッパーを使用する必要があると判断します。 インターフェイスがマーシャラーを初めて通過すると、マーシャラーはインターフェイスが既知のオブジェクトから送信されているかどうかを確認します。 このチェックは、次の 2 つの状況で発生します。

  • インターフェイスは、他の場所の COM に渡された別のマネージド オブジェクトによって実装されています。 マーシャラーは、マネージド オブジェクトによって公開されているインターフェイスを簡単に識別でき、実装を提供するマネージド オブジェクトとインターフェイスを照合できます。 その後、マネージド オブジェクトがメソッドに渡され、ラッパーは必要ありません。

  • 既にラップされているオブジェクトは、インターフェイスを実装しています。 これが当てはまるかどうかを判断するために、マーシャラーはオブジェクトの IUnknown インターフェイスを照会し、返されたインターフェイスを、既にラップされている他のオブジェクトのインターフェイスと比較します。 インターフェイスが別のラッパーと同じ場合、オブジェクトは同じ ID を持ち、既存のラッパーがメソッドに渡されます。

インターフェイスが既知のオブジェクトからでない場合、マーシャラーは次の処理を行います。

  1. マーシャラーは、 IProvideClassInfo2 インターフェイスのオブジェクトを照会します。 指定された場合、マーシャラーは IProvideClassInfo2.GetGUID から返された CLSID を使用して、インターフェイスを提供するコクラスを識別します。 CLSID を使用すると、アセンブリが以前に登録されている場合、マーシャラーはレジストリからラッパーを見つけることができます。

  2. マーシャラーは、 IProvideClassInfo インターフェイスのインターフェイスを照会します。 指定した場合、マーシャラーは IProvideClassInfo.GetClassinfo から返された ITypeInfo を使用して、インターフェイスを公開するクラスの CLSID を決定します。 マーシャラーは CLSID を使用してラッパーのメタデータを見つけることができます。

  3. マーシャラーがまだクラスを識別できない場合は、 インターフェイスを System.__ComObject という汎用ラッパー クラスでラップします。

デリゲートの既定のマーシャリング

マネージド デリゲートは、呼び出し元のメカニズムに基づいて、COM インターフェイスまたは関数ポインターとしてマーシャリングされます。

  • プラットフォーム呼び出しの場合、デリゲートは既定でアンマネージ関数ポインターとしてマーシャリングされます。

  • COM 相互運用機能の場合、デリゲートは既定で _Delegate 型の COM インターフェイスとしてマーシャリングされます。 _Delegate インターフェイスは Mscorlib.tlb タイプ ライブラリで定義され、デリゲートが参照するメソッドを呼び出すDelegate.DynamicInvoke メソッドが含まれています。

次の表に、マネージド デリゲート データ型のマーシャリング オプションを示します。 MarshalAsAttribute属性は、デリゲートをマーシャリングするためのいくつかのUnmanagedType列挙値を提供します。

列挙型 アンマネージ形式の説明
UnmanagedType.FunctionPtr アンマネージ関数ポインター。
UnmanagedType.Interface Mscorlib.tlb で定義されている _Delegate型のインターフェイス。

DelegateTestInterfaceのメソッドを COM タイプ ライブラリにエクスポートするコード例を次に示します。 ref (または ByRef) キーワードでマークされたデリゲートのみが In/Out パラメーターとして渡されることに注意してください。

using System;  
using System.Runtime.InteropServices;  
  
public interface DelegateTest {  
void m1(Delegate d);  
void m2([MarshalAs(UnmanagedType.Interface)] Delegate d);
void m3([MarshalAs(UnmanagedType.Interface)] ref Delegate d);
void m4([MarshalAs(UnmanagedType.FunctionPtr)] Delegate d);
void m5([MarshalAs(UnmanagedType.FunctionPtr)] ref Delegate d);
}  

タイプ ライブラリ表現

importlib("mscorlib.tlb");  
interface DelegateTest : IDispatch {  
[id(…)] HRESULT m1([in] _Delegate* d);  
[id(…)] HRESULT m2([in] _Delegate* d);  
[id(…)] HRESULT m3([in, out] _Delegate** d);  
[id()] HRESULT m4([in] int d);  
[id()] HRESULT m5([in, out] int *d);  
   };  

他のアンマネージ関数ポインターを逆参照できるのと同様に、関数ポインターは逆参照できます。

この例では、2 つのデリゲートが UnmanagedType.FunctionPtrとしてマーシャリングされると、結果は intintへのポインターになります。 デリゲート型はマーシャリングされているため、ここで int は、メモリ内のデリゲートのアドレスである void (void*) へのポインターを表します。 つまり、この結果は 32 ビットの Windows システムに固有です。ここで int は関数ポインターのサイズを表しているためです。

アンマネージ コードによって保持されているマネージド デリゲートへの関数ポインターへの参照は、共通言語ランタイムがマネージド オブジェクトに対してガベージ コレクションを実行することを妨げるものではありません。

たとえば、SetChangeHandler メソッドに渡されるcb オブジェクトへの参照が、Test メソッドの有効期間を超えてcb維持されないため、次のコードは正しくありません。 cb オブジェクトがガベージ コレクションされると、SetChangeHandlerに渡される関数ポインターは無効になります。

public class ExternalAPI {  
   [DllImport("External.dll")]  
   public static extern void SetChangeHandler(  
      [MarshalAs(UnmanagedType.FunctionPtr)]ChangeDelegate d);  
}  
public delegate bool ChangeDelegate([MarshalAs(UnmanagedType.LPWStr) string S);  
public class CallBackClass {  
   public bool OnChange(string S){ return true;}  
}  
internal class DelegateTest {  
   public static void Test() {  
      CallBackClass cb = new CallBackClass();  
      // Caution: The following reference on the cb object does not keep the
      // object from being garbage collected after the Main method
      // executes.  
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));
   }  
}  

予期しないガベージ コレクションを補うために、呼び出し元は、アンマネージ関数ポインターが使用されている限り、 cb オブジェクトが維持されるようにする必要があります。 必要に応じて、次の例に示すように、関数ポインターが不要になったときにアンマネージ コードにマネージド コードに通知させることができます。

internal class DelegateTest {  
   CallBackClass cb;  
   // Called before ever using the callback function.  
   public static void SetChangeHandler() {  
      cb = new CallBackClass();  
      ExternalAPI.SetChangeHandler(new ChangeDelegate(cb.OnChange));  
   }  
   // Called after using the callback function for the last time.  
   public static void RemoveChangeHandler() {  
      // The cb object can be collected now. The unmanaged code is
      // finished with the callback function.  
      cb = null;  
   }  
}  

値型の既定のマーシャリング

整数や浮動小数点数などのほとんどの値型は blittable であり、マーシャリングは必要ありません。 他 の blittable 以外の 型は、マネージド メモリとアンマネージド メモリで異なる表現を持ち、マーシャリングを必要とします。 それでも他の型では、相互運用境界を越えて明示的な書式設定が必要です。

このセクションでは、次の書式設定された値型について説明します。

このトピックでは、書式設定された型の説明に加えて、通常とは異なるマーシャリング動作を持つ システム値型 を識別します。

書式設定された型は、メモリ内のメンバーのレイアウトを明示的に制御する情報を含む複合型です。 メンバー レイアウト情報は、 StructLayoutAttribute 属性を使用して提供されます。 レイアウトには、次の LayoutKind 列挙値のいずれかを指定できます。

  • LayoutKind.Auto

    共通言語ランタイムが、効率を高めるために型のメンバーを自由に並べ替え可能であることを示します。 ただし、アンマネージド コードに値型が渡されると、メンバーのレイアウトは予測可能になります。 このような構造体をマーシャリングしようとすると、自動的に例外が発生します。

  • LayoutKind.Sequential

    型のメンバーが、マネージド型定義に表示される順序と同じ順序でアンマネージ メモリに配置されることを示します。

  • LayoutKind.Explicit

    各フィールドに指定された FieldOffsetAttribute に従ってメンバーがレイアウトされることを示します。

プラットフォーム呼び出しで使用される値型

次の例では、 Point 型と Rect 型は 、StructLayoutAttribute を使用してメンバー レイアウト情報を提供します。

Imports System.Runtime.InteropServices  
<StructLayout(LayoutKind.Sequential)> Public Structure Point  
   Public x As Integer  
   Public y As Integer  
End Structure  
<StructLayout(LayoutKind.Explicit)> Public Structure Rect  
   <FieldOffset(0)> Public left As Integer  
   <FieldOffset(4)> Public top As Integer  
   <FieldOffset(8)> Public right As Integer  
   <FieldOffset(12)> Public bottom As Integer  
End Structure  
using System.Runtime.InteropServices;  
[StructLayout(LayoutKind.Sequential)]  
public struct Point {  
   public int x;  
   public int y;  
}
  
[StructLayout(LayoutKind.Explicit)]  
public struct Rect {  
   [FieldOffset(0)] public int left;  
   [FieldOffset(4)] public int top;  
   [FieldOffset(8)] public int right;  
   [FieldOffset(12)] public int bottom;  
}  

アンマネージ コードにマーシャリングすると、これらの書式設定された型は C スタイルの構造体としてマーシャリングされます。 これにより、構造体引数を持つアンマネージ API を簡単に呼び出す方法が提供されます。 たとえば、次のように、 POINT および RECT 構造体を Microsoft Windows API PtInRect 関数に 渡すことができます。

BOOL PtInRect(const RECT *lprc, POINT pt);  

次のプラットフォーム呼び出し定義を使用して構造体を渡すことができます。

Friend Class NativeMethods
    Friend Declare Auto Function PtInRect Lib "User32.dll" (
        ByRef r As Rect, p As Point) As Boolean
End Class
internal static class NativeMethods
{
   [DllImport("User32.dll")]
   internal static extern bool PtInRect(ref Rect r, Point p);
}

アンマネージ API はRECTへのポインターを関数に渡すことが想定されているため、Rect値型を参照渡しする必要があります。 Point値型は、アンマネージ API がスタックでPOINTが渡されることを想定しているため、値渡しされます。 この微妙な違いは非常に重要です。 参照は、ポインターとしてアンマネージ コードに渡されます。 値は、スタック上のアンマネージ コードに渡されます。

書式設定された型が構造体としてマーシャリングされている場合、その型内のフィールドにのみアクセスできます。 型にメソッド、プロパティ、またはイベントがある場合、アンマネージ コードからアクセスできません。

クラスは、固定メンバー レイアウトがある場合、C スタイルの構造体としてアンマネージ コードにマーシャリングすることもできます。 クラスのメンバー レイアウト情報も、 StructLayoutAttribute 属性と共に提供されます。 固定レイアウトの値型と固定レイアウトのクラスの主な違いは、アンマネージド コードにマーシャリングする方法です。 値型は (スタック上の) 値によって渡されるため、呼び出し先によって型のメンバーに加えられた変更は呼び出し元には表示されません。 参照型は参照によって渡されます (型への参照はスタックで渡されます)。したがって、呼び出し先によって型の blittable 型メンバーに加えられたすべての変更が呼び出し元によって表示されます。

参照型に blittable 以外の型のメンバーがある場合、変換は 2 回必要です。最初に引数がアンマネージ側に渡されたときと、呼び出しから 2 回目が返されたときです。 このオーバーヘッドが増加するため、呼び出し元が呼び出し先によって行われた変更を表示する場合は、In/Out パラメーターを明示的に引数に適用する必要があります。

次の例では、 SystemTime クラスはシーケンシャル メンバー レイアウトを持ち、Windows API GetSystemTime 関数に渡すことができます。

<StructLayout(LayoutKind.Sequential)> Public Class SystemTime  
   Public wYear As System.UInt16  
   Public wMonth As System.UInt16  
   Public wDayOfWeek As System.UInt16  
   Public wDay As System.UInt16  
   Public wHour As System.UInt16  
   Public wMinute As System.UInt16  
   Public wSecond As System.UInt16  
   Public wMilliseconds As System.UInt16  
End Class  
[StructLayout(LayoutKind.Sequential)]  
   public class SystemTime {  
   public ushort wYear;
   public ushort wMonth;  
   public ushort wDayOfWeek;
   public ushort wDay;
   public ushort wHour;
   public ushort wMinute;
   public ushort wSecond;
   public ushort wMilliseconds;
}  

GetSystemTime 関数は次のように定義されます。

void GetSystemTime(SYSTEMTIME* SystemTime);  

GetSystemTime の同等のプラットフォーム呼び出し定義は次のとおりです。

Friend Class NativeMethods
    Friend Declare Auto Sub GetSystemTime Lib "Kernel32.dll" (
        ByVal sysTime As SystemTime)
End Class
internal static class NativeMethods
{
   [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
   internal static extern void GetSystemTime(SystemTime st);
}

SystemTimeは値型ではなくクラスであるため、SystemTime引数は参照引数として型指定されていないことに注意してください。 値型とは異なり、クラスは常に参照によって渡されます。

次のコード例は、SetXY というメソッドを持つ別のPoint クラスを示しています。 型にはシーケンシャル レイアウトがあるため、アンマネージ コードに渡して構造体としてマーシャリングできます。 ただし、オブジェクトが参照渡しであっても、 SetXY メンバーはアンマネージ コードから呼び出すことができません。

<StructLayout(LayoutKind.Sequential)> Public Class Point  
   Private x, y As Integer  
   Public Sub SetXY(x As Integer, y As Integer)  
      Me.x = x  
      Me.y = y  
   End Sub  
End Class  
[StructLayout(LayoutKind.Sequential)]  
public class Point {  
   int x, y;  
   public void SetXY(int x, int y){
      this.x = x;  
      this.y = y;  
   }  
}  

COM 相互運用機能で使用される値型

書式設定された型は、COM 相互運用メソッド呼び出しにも渡すことができます。 実際、タイプ ライブラリにエクスポートすると、値型は自動的に構造体に変換されます。 次の例に示すように、 Point 値型は、 Pointという名前の型定義 (typedef) になります。 タイプ ライブラリ内の別の場所にある Point 値型への参照はすべて、 Point typedef に置き換えられます。

タイプ ライブラリ表現

typedef struct tagPoint {  
   int x;  
   int y;  
} Point;  
interface _Graphics {  
   …  
   HRESULT SetPoint ([in] Point p)  
   HRESULT SetPointRef ([in,out] Point *p)  
   HRESULT GetPoint ([out,retval] Point *p)  
}  

COM インターフェイスを介してマーシャリングするときに、値とプラットフォーム呼び出しへの参照をマーシャリングするために使用されるのと同じ規則が使用されます。 たとえば、 Point 値型のインスタンスが .NET Framework から COM に渡されると、 Point は値渡しされます。 Point値型が参照渡しの場合、Pointへのポインターがスタックに渡されます。 相互運用マーシャラーは、どちらの方向でも高レベルの間接参照 (Point **) をサポートしていません。

エクスポートされたタイプ ライブラリは明示的なレイアウトを表すことができないため、LayoutKind列挙値が Explicit に設定されている構造体は COM 相互運用で使用できません。

システム値の型

System名前空間には、ランタイム プリミティブ型のボックス化された形式を表す複数の値型があります。 たとえば、値型 System.Int32 構造体は、ボックス化された 形式のELEMENT_TYPE_I4を表します。 他の書式設定された型と同様に、これらの型を構造体としてマーシャリングする代わりに、ボックス化されたプリミティブ型と同じ方法でマーシャリングします。 したがって、System.Int32long 型の 1 つのメンバーを含む構造体としてではなく、ELEMENT_TYPE_I4としてマーシャリングされます。 次の表に、プリミティブ型のボックス化表現である System 名前空間の値型の一覧を示します。

システム値の型 要素型
System.Boolean ELEMENT_TYPE_BOOLEAN
System.SByte ELEMENT_TYPE_I1
System.Byte ELEMENT_TYPE_UI1
System.Char ELEMENT_TYPE_CHAR
System.Int16 ELEMENT_TYPE_I2
System.UInt16 ELEMENT_TYPE_U2
System.Int32 ELEMENT_TYPE_I4
System.UInt32 ELEMENT_TYPE_U4
System.Int64 ELEMENT_TYPE_I8
System.UInt64 ELEMENT_TYPE_U8
System.Single ELEMENT_TYPE_R4
System.Double ELEMENT_TYPE_R8
System.String ELEMENT_TYPE_STRING
System.IntPtr ELEMENT_TYPE_I
System.UIntPtr ELEMENT_TYPE_U

System 名前空間内の他のいくつかの値型は、異なる方法で処理されます。 アンマネージ コードには既にこれらの型の形式が確立されているため、マーシャラーにはマーシャリングのための特別な規則があります。 次の表に、 System 名前空間の特殊な値型と、マーシャリング先のアンマネージ型を示します。

システム値の型 IDL 型
System.DateTime 日付
System.Decimal 小数
System.Guid GUIDの
System.Drawing.Color OLE_COLOR

次のコードは、Stdole2 タイプ ライブラリのアンマネージ型 DATEGUIDDECIMALおよびOLE_COLOR の定義を示しています。

タイプ ライブラリ表現

typedef double DATE;  
typedef DWORD OLE_COLOR;  
  
typedef struct tagDEC {  
    USHORT    wReserved;  
    BYTE      scale;  
    BYTE      sign;  
    ULONG     Hi32;  
    ULONGLONG Lo64;  
} DECIMAL;  
  
typedef struct tagGUID {  
    DWORD Data1;  
    WORD  Data2;  
    WORD  Data3;  
    BYTE  Data4[ 8 ];  
} GUID;  

次のコードは、マネージド IValueTypes インターフェイスの対応する定義を示しています。

Public Interface IValueTypes  
   Sub M1(d As System.DateTime)  
   Sub M2(d As System.Guid)  
   Sub M3(d As System.Decimal)  
   Sub M4(d As System.Drawing.Color)  
End Interface  
public interface IValueTypes {  
   void M1(System.DateTime d);  
   void M2(System.Guid d);  
   void M3(System.Decimal d);  
   void M4(System.Drawing.Color d);  
}  

タイプ ライブラリ表現

[…]  
interface IValueTypes : IDispatch {  
   HRESULT M1([in] DATE d);  
   HRESULT M2([in] GUID d);  
   HRESULT M3([in] DECIMAL d);  
   HRESULT M4([in] OLE_COLOR d);  
};  

こちらも参照ください