構造体のマーシャリングをカスタマイズする
構造体の既定のマーシャリング規則が、必要な規則とは異なる場合があります。 .NET ランタイムには、構造体のレイアウトやフィールドのマーシャリング方法をカスタマイズできる拡張ポイントがいくつか用意されています。 構造のレイアウトのカスタマイズはすべてのシナリオでサポートされますが、フィールドのマーシャリングのカスタマイズは、ランタイム マーシャリングが有効になっているシナリオでのみサポートされます。 ランタイム マーシャリングが無効になっている場合は、すべてのフィールド マーシャリングを手動で行う必要があります。
Note
この記事では、ソース生成の相互運用のためのマーシャリングのカスタマイズについては説明しません。 P/Invoke または COM に対するソース生成の相互運用を使っている場合は、マーシャリングのカスタマイズに関するページを参照してください。
構造レイアウトをカスタマイズする
.NET には System.Runtime.InteropServices.StructLayoutAttribute 属性と System.Runtime.InteropServices.LayoutKind 列挙型が用意されており、フィールドをメモリに配置する方法をカスタマイズできます。 次のガイダンスを使用すると、一般的な問題を回避できます。
✔️ 推奨: 可能な限り LayoutKind.Sequential
を使用するようにします。
✔️ 実行: ネイティブ構造体に共用体などの明示的なレイアウトもある場合にのみ、マーシャリングで LayoutKind.Explicit
を使用します。
❌ 回避: クラスを使い、継承によって複雑なネイティブ型を表現しないようにしてください。
❌ 回避: .NET Core 3.0 より前のランタイムをターゲットにする必要がある場合、Windows 以外のプラットフォームで構造体をマーシャリングするときは LayoutKind.Explicit
を使用しないでください。 3\.0 より前の .NET Core ランタイムは、Intel または AMD 64 ビットの Windows 以外のシステム上でネイティブ関数への値による明示的な構造体の受け渡しをサポートしていません。 ただし、ランタイムはすべてのプラットフォーム上で明示的な構造体の参照渡しをサポートしています。
ブール値フィールドのマーシャリングのカスタマイズ
ネイティブ コードには、さまざまなブール表現があります。 Windows だけでも、ブール値を表現する方法が 3 つあります。 ランタイムは構造体のネイティブ定義を認識しないので、可能な最善の処理は、ブール値のマーシャリング方法を推測することです。 .NET ランタイムには、ブール値フィールドのマーシャリング方法を示す方法が用意されています。 以下の例は、.NET bool
をさまざまなネイティブ ブール型にマーシャリングする方法を示しています。
次の例に示すように、ブール値は既定でネイティブの 4 バイト Win32 BOOL
値としてマーシャリングされます。
public struct WinBool
{
public bool b;
}
struct WinBool
{
public BOOL b;
};
明示的にする場合は、UnmanagedType.Bool 値を使用して上記と同じ動作にすることができます。
public struct WinBool
{
[MarshalAs(UnmanagedType.Bool)]
public bool b;
}
struct WinBool
{
public BOOL b;
};
以下の UnmanagedType.U1
値または UnmanagedType.I1
値を使用して、b
フィールドを 1 バイトのネイティブ bool
型としてマーシャリングするようにランタイムに指示できます。
public struct CBool
{
[MarshalAs(UnmanagedType.U1)]
public bool b;
}
struct CBool
{
public bool b;
};
Windows では、UnmanagedType.VariantBool 値を使用して、ブール値を 2 バイトの VARIANT_BOOL
値にマーシャリングするようにランタイムに指示できます。
public struct VariantBool
{
[MarshalAs(UnmanagedType.VariantBool)]
public bool b;
}
struct VariantBool
{
public VARIANT_BOOL b;
};
注意
VARIANT_BOOL
は、VARIANT_TRUE = -1
と VARIANT_FALSE = 0
が他のほとんどのブール型とは異なります。 さらに、VARIANT_TRUE
と等しくないすべての値は false と見なされます。
配列フィールドのマーシャリングのカスタマイズ
.NET には、配列のマーシャリングをカスタマイズする方法もいくつか用意されています。
既定では、.NET は要素の連続したリストへのポインターとして配列をマーシャリングします。
public struct DefaultArray
{
public int[] values;
}
struct DefaultArray
{
int32_t* values;
};
COM API とやり取りする場合は、必要に応じて配列を SAFEARRAY*
オブジェクトとしてマーシャリングします。 System.Runtime.InteropServices.MarshalAsAttribute 値と UnmanagedType.SafeArray 値を使用して、配列を SAFEARRAY*
としてマーシャリングするようにランタイムに指示できます。
public struct SafeArrayExample
{
[MarshalAs(UnmanagedType.SafeArray)]
public int[] values;
}
struct SafeArrayExample
{
SAFEARRAY* values;
};
SAFEARRAY
に含まれる要素の型をカスタマイズする必要がある場合は、MarshalAsAttribute.SafeArraySubType および MarshalAsAttribute.SafeArrayUserDefinedSubType フィールドを使用して SAFEARRAY
の正確な要素の種類をカスタマイズできます。
インプレースで配列をマーシャリングする必要がある場合は、UnmanagedType.ByValArray 値を使用して、インプレースで配列をマーシャリングするようマーシャラーに指示できます。 このマーシャリングを使用するときは、ランタイムが構造体の空間を適切に割り当てられるように、MarshalAsAttribute.SizeConst フィールドに配列内の要素数の値を指定する必要もあります。
public struct InPlaceArray
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] values;
}
struct InPlaceArray
{
int values[4];
};
注意
.NET は、C99 の柔軟な配列のメンバーとして可変長配列フィールドをマーシャリングすることをサポートしていません。
文字列フィールドのマーシャリングのカスタマイズ
.NET には、文字列フィールドをマーシャリングするためのさまざまなカスタマイズも用意されています。
既定では、.NET は文字列を null で終わる文字列へのポインターとしてマーシャリングします。 エンコードは、System.Runtime.InteropServices.StructLayoutAttribute の StructLayoutAttribute.CharSet フィールドの値によって決まります。 属性が指定されていない場合、エンコードは既定で ANSI エンコードになります。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DefaultString
{
public string str;
}
struct DefaultString
{
char* str;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DefaultString
{
public string str;
}
struct DefaultString
{
char16_t* str; // Could also be wchar_t* on Windows.
};
フィールドごとに異なるエンコードを使用する必要がある場合、または単に構造体の定義内でより明示的にする場合は、System.Runtime.InteropServices.MarshalAsAttribute 属性に UnmanagedType.LPStr 値または UnmanagedType.LPWStr 値を使用できます。
public struct AnsiString
{
[MarshalAs(UnmanagedType.LPStr)]
public string str;
}
struct AnsiString
{
char* str;
};
public struct UnicodeString
{
[MarshalAs(UnmanagedType.LPWStr)]
public string str;
}
struct UnicodeString
{
char16_t* str; // Could also be wchar_t* on Windows.
};
UTF-8 エンコードを使用して文字列をマーシャリングする場合は、MarshalAsAttribute に UnmanagedType.LPUTF8Str 値を使用できます。
public struct UTF8String
{
[MarshalAs(UnmanagedType.LPUTF8Str)]
public string str;
}
struct UTF8String
{
char* str;
};
注意
UnmanagedType.LPUTF8Str を使用するには、.NET Framework 4.7 (またはそれ以降のバージョン) または .NET Core 1.1 (またはそれ以降のバージョン) のいずれかが必要です。 .NET Standard 2.0 では使用できません。
COM API を使用している場合は、文字列を BSTR
としてマーシャリングする必要があります。 UnmanagedType.BStr 値を使用すると、文字列を BSTR
としてマーシャリングできます。
public struct BString
{
[MarshalAs(UnmanagedType.BStr)]
public string str;
}
struct BString
{
BSTR str;
};
WinRT ベースの API を使用している場合は、文字列を HSTRING
としてマーシャリングする必要があります。 UnmanagedType.HString 値を使用すると、文字列を HSTRING
としてマーシャリングできます。 HSTRING
のマーシャリングは、組み込みの WinRT サポートを備えたランタイムでのみサポートされます。 WinRT のサポートは .NET 5 で削除されたので、HSTRING
のマーシャリングは .NET 5 以降ではサポートされません。
public struct HString
{
[MarshalAs(UnmanagedType.HString)]
public string str;
}
struct BString
{
HSTRING str;
};
API で、構造体でインプレースで文字列を渡す必要がある場合は、UnmanagedType.ByValTStr 値を使用できます。 ByValTStr
でマーシャリングされた文字列のエンコードは、CharSet
属性によって決まる点に注意してください。 さらに、文字列長を MarshalAsAttribute.SizeConst フィールドで渡す必要があります。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DefaultString
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string str;
}
struct DefaultString
{
char str[4];
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DefaultString
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string str;
}
struct DefaultString
{
char16_t str[4]; // Could also be wchar_t[4] on Windows.
};
10 進数フィールドのマーシャリングのカスタマイズ
Windows を使用している場合は、ネイティブの CY
または CURRENCY
構造体を使用する API がいくつかあります。 既定で、.NET の decimal
型はネイティブの DECIMAL
構造体にマーシャリングされます。 ただし、値が UnmanagedType.Currency の MarshalAsAttribute を使用して、decimal
値をネイティブの CY
値に変換するようにマーシャラーに指示することができます。
public struct Currency
{
[MarshalAs(UnmanagedType.Currency)]
public decimal dec;
}
struct Currency
{
CY dec;
};
Unions
共用体は、同じメモリ上に異なる型のデータを含めることができるデータ型です。 これは、C 言語のデータの一般的な形式です。 共用体は、.NET では LayoutKind.Explicit
を使って表すことができます。 .NET で共用体を定義するときは、構造体を使うことをお勧めします。 クラスの使用は、レイアウトの問題が生じ、予期しない動作が発生する原因となるおそれがあります。
struct device1_config
{
void* a;
void* b;
void* c;
};
struct device2_config
{
int32_t a;
int32_t b;
};
struct config
{
int32_t type;
union
{
device1_config dev1;
device2_config dev2;
};
};
public unsafe struct Device1Config
{
void* a;
void* b;
void* c;
}
public struct Device2Config
{
int a;
int b;
}
public struct Config
{
public int Type;
public _Union Anonymous;
[StructLayout(LayoutKind.Explicit)]
public struct _Union
{
[FieldOffset(0)]
public Device1Config Dev1;
[FieldOffset(0)]
public Device2Config Dev2;
}
}
System.Object
をマーシャリングする
Windows では、object
型のフィールドをネイティブ コードにマーシャリングできます。 このようなフィールドは、次の 3 つの型のいずれかにマーシャリングできます。
既定では、object
型のフィールドはオブジェクトをラップする IUnknown*
にマーシャリングされます。
public struct ObjectDefault
{
public object obj;
}
struct ObjectDefault
{
IUnknown* obj;
};
オブジェクト フィールドを IDispatch*
にマーシャリングする場合は、値が UnmanagedType.IDispatch の MarshalAsAttribute を追加します。
public struct ObjectDispatch
{
[MarshalAs(UnmanagedType.IDispatch)]
public object obj;
}
struct ObjectDispatch
{
IDispatch* obj;
};
VARIANT
としてマーシャリングする場合は、値が UnmanagedType.Struct の MarshalAsAttribute を追加します。
public struct ObjectVariant
{
[MarshalAs(UnmanagedType.Struct)]
public object obj;
}
struct ObjectVariant
{
VARIANT obj;
};
次の表は、obj
フィールドのさまざまなランタイム型が VARIANT
に格納されるさまざまな型にどのようにマップされるかを示しています。
.NET 型 | バリアント型 |
---|---|
byte |
VT_UI1 |
sbyte |
VT_I1 |
short |
VT_I2 |
ushort |
VT_UI2 |
int |
VT_I4 |
uint |
VT_UI4 |
long |
VT_I8 |
ulong |
VT_UI8 |
float |
VT_R4 |
double |
VT_R8 |
char |
VT_UI2 |
string |
VT_BSTR |
System.Runtime.InteropServices.BStrWrapper |
VT_BSTR |
object |
VT_DISPATCH |
System.Runtime.InteropServices.UnknownWrapper |
VT_UNKNOWN |
System.Runtime.InteropServices.DispatchWrapper |
VT_DISPATCH |
System.Reflection.Missing |
VT_ERROR |
(object)null |
VT_EMPTY |
bool |
VT_BOOL |
System.DateTime |
VT_DATE |
decimal |
VT_DECIMAL |
System.Runtime.InteropServices.CurrencyWrapper |
VT_CURRENCY |
System.DBNull |
VT_NULL |
.NET