相互運用マーシャリングは、メソッド パラメーターに関連付けられているデータがマネージド メモリとアンマネージド メモリの間を通過する場合の動作を決定するルールに対して動作します。 これらの組み込みルールは、データ型変換などのマーシャリング アクティビティ、呼び出し先が渡されたデータを変更して呼び出し元にそれらの変更を返すことができるかどうか、およびマーシャラーがパフォーマンスの最適化を提供する状況を制御します。
このセクションでは、相互運用マーシャリング サービスの既定の動作特性を識別します。 配列、ブール型、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 メソッド呼び出しに渡します。 プロキシは、クラス インターフェイスのすべての呼び出しをマネージド オブジェクトに委任します。 プロキシは、クラスによって明示的に実装されていない他のインターフェイスも公開します。 プロキシは、クラスの代わりに IUnknown や IDispatch などのインターフェイスを自動的に実装します。
.NET コードにクラスを渡す
コクラスは、通常、COM のメソッド引数として使用されません。 代わりに、通常、既定のインターフェイスがコクラスの代わりに渡されます。
インターフェイスがマネージド コードに渡されると、相互運用マーシャラーはインターフェイスを適切なラッパーでラップし、ラッパーをマネージド メソッドに渡す役割を担います。 使用するラッパーの決定が難しい場合があります。 COM オブジェクトのすべてのインスタンスには、オブジェクトが実装するインターフェイスの数に関係なく、1 つの一意のラッパーがあります。 たとえば、5 つの異なるインターフェイスを実装する 1 つの COM オブジェクトには、ラッパーが 1 つだけ存在します。 同じラッパーは、5 つのインターフェイスすべてを公開します。 COM オブジェクトの 2 つのインスタンスが作成されると、ラッパーの 2 つのインスタンスが作成されます。
ラッパーが有効期間を通じて同じ型を維持するには、相互運用マーシャラーは、オブジェクトによって公開されたインターフェイスがマーシャラーを初めて通過する際に、正しいラッパーを識別する必要があります。 マーシャラーは、オブジェクトが実装するインターフェイスのいずれかを調べることで、オブジェクトを識別します。
たとえば、マーシャラーは、マネージド コードに渡されたインターフェイスをラップするためにクラス ラッパーを使用する必要があると判断します。 インターフェイスがマーシャラーを初めて通過すると、マーシャラーはインターフェイスが既知のオブジェクトから送信されているかどうかを確認します。 このチェックは、次の 2 つの状況で発生します。
インターフェイスは、他の場所の COM に渡された別のマネージド オブジェクトによって実装されています。 マーシャラーは、マネージド オブジェクトによって公開されているインターフェイスを簡単に識別でき、実装を提供するマネージド オブジェクトとインターフェイスを照合できます。 その後、マネージド オブジェクトがメソッドに渡され、ラッパーは必要ありません。
既にラップされているオブジェクトは、インターフェイスを実装しています。 これが当てはまるかどうかを判断するために、マーシャラーはオブジェクトの IUnknown インターフェイスを照会し、返されたインターフェイスを、既にラップされている他のオブジェクトのインターフェイスと比較します。 インターフェイスが別のラッパーと同じ場合、オブジェクトは同じ ID を持ち、既存のラッパーがメソッドに渡されます。
インターフェイスが既知のオブジェクトからでない場合、マーシャラーは次の処理を行います。
マーシャラーは、 IProvideClassInfo2 インターフェイスのオブジェクトを照会します。 指定された場合、マーシャラーは IProvideClassInfo2.GetGUID から返された CLSID を使用して、インターフェイスを提供するコクラスを識別します。 CLSID を使用すると、アセンブリが以前に登録されている場合、マーシャラーはレジストリからラッパーを見つけることができます。
マーシャラーは、 IProvideClassInfo インターフェイスのインターフェイスを照会します。 指定した場合、マーシャラーは IProvideClassInfo.GetClassinfo から返された ITypeInfo を使用して、インターフェイスを公開するクラスの CLSID を決定します。 マーシャラーは CLSID を使用してラッパーのメタデータを見つけることができます。
マーシャラーがまだクラスを識別できない場合は、 インターフェイスを 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としてマーシャリングされると、結果は int
と int
へのポインターになります。 デリゲート型はマーシャリングされているため、ここで 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.Int32 は long 型の 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 タイプ ライブラリのアンマネージ型 DATE、 GUID、 DECIMAL、 および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);
};
こちらも参照ください
.NET