英語で読む

次の方法で共有


構造体のマーシャリングをカスタマイズする

構造体の既定のマーシャリング規則が、必要な規則とは異なる場合があります。 .NET ランタイムには、構造体のレイアウトやフィールドのマーシャリング方法をカスタマイズできる拡張ポイントがいくつか用意されています。 構造のレイアウトのカスタマイズはすべてのシナリオでサポートされますが、フィールドのマーシャリングのカスタマイズは、ランタイム マーシャリングが有効になっているシナリオでのみサポートされます。 ランタイム マーシャリングが無効になっている場合は、すべてのフィールド マーシャリングを手動で行う必要があります。

注意

この記事では、ソース生成の相互運用のためのマーシャリングのカスタマイズについては説明しません。 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 値としてマーシャリングされます。

C#
public struct WinBool
{
    public bool b;
}

明示的にする場合は、UnmanagedType.Bool 値を使用して上記と同じ動作にすることができます。

C#
public struct WinBool
{
    [MarshalAs(UnmanagedType.Bool)]
    public bool b;
}

以下の UnmanagedType.U1 値または UnmanagedType.I1 値を使用して、b フィールドを 1 バイトのネイティブ bool 型としてマーシャリングするようにランタイムに指示できます。

C#
public struct CBool
{
    [MarshalAs(UnmanagedType.U1)]
    public bool b;
}

Windows では、UnmanagedType.VariantBool 値を使用して、ブール値を 2 バイトの VARIANT_BOOL 値にマーシャリングするようにランタイムに指示できます。

C#
public struct VariantBool
{
    [MarshalAs(UnmanagedType.VariantBool)]
    public bool b;
}

注意

VARIANT_BOOL は、VARIANT_TRUE = -1VARIANT_FALSE = 0 が他のほとんどのブール型とは異なります。 さらに、VARIANT_TRUE と等しくないすべての値は false と見なされます。

配列フィールドのマーシャリングのカスタマイズ

.NET には、配列のマーシャリングをカスタマイズする方法もいくつか用意されています。

既定では、.NET は要素の連続したリストへのポインターとして配列をマーシャリングします。

C#
public struct DefaultArray
{
    public int[] values;
}

COM API とやり取りする場合は、必要に応じて配列を SAFEARRAY* オブジェクトとしてマーシャリングします。 System.Runtime.InteropServices.MarshalAsAttribute 値と UnmanagedType.SafeArray 値を使用して、配列を SAFEARRAY* としてマーシャリングするようにランタイムに指示できます。

C#
public struct SafeArrayExample
{
    [MarshalAs(UnmanagedType.SafeArray)]
    public int[] values;
}

SAFEARRAY に含まれる要素の型をカスタマイズする必要がある場合は、MarshalAsAttribute.SafeArraySubType および MarshalAsAttribute.SafeArrayUserDefinedSubType フィールドを使用して SAFEARRAY の正確な要素の種類をカスタマイズできます。

インプレースで配列をマーシャリングする必要がある場合は、UnmanagedType.ByValArray 値を使用して、インプレースで配列をマーシャリングするようマーシャラーに指示できます。 このマーシャリングを使用するときは、ランタイムが構造体の空間を適切に割り当てられるように、MarshalAsAttribute.SizeConst フィールドに配列内の要素数の値を指定する必要もあります。

C#
public struct InPlaceArray
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public int[] values;
}

注意

.NET は、C99 の柔軟な配列のメンバーとして可変長配列フィールドをマーシャリングすることをサポートしていません。

文字列フィールドのマーシャリングのカスタマイズ

.NET には、文字列フィールドをマーシャリングするためのさまざまなカスタマイズも用意されています。

既定では、.NET は文字列を null で終わる文字列へのポインターとしてマーシャリングします。 エンコードは、System.Runtime.InteropServices.StructLayoutAttributeStructLayoutAttribute.CharSet フィールドの値によって決まります。 属性が指定されていない場合、エンコードは既定で ANSI エンコードになります。

C#
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DefaultString
{
    public string str;
}
C#
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DefaultString
{
    public string str;
}

フィールドごとに異なるエンコードを使用する必要がある場合、または単に構造体の定義内でより明示的にする場合は、System.Runtime.InteropServices.MarshalAsAttribute 属性に UnmanagedType.LPStr 値または UnmanagedType.LPWStr 値を使用できます。

C#
public struct AnsiString
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string str;
}
C#
public struct UnicodeString
{
    [MarshalAs(UnmanagedType.LPWStr)]
    public string str;
}

UTF-8 エンコードを使用して文字列をマーシャリングする場合は、MarshalAsAttributeUnmanagedType.LPUTF8Str 値を使用できます。

C#
public struct UTF8String
{
    [MarshalAs(UnmanagedType.LPUTF8Str)]
    public string str;
}

注意

UnmanagedType.LPUTF8Str を使用するには、.NET Framework 4.7 (またはそれ以降のバージョン) または .NET Core 1.1 (またはそれ以降のバージョン) のいずれかが必要です。 .NET Standard 2.0 では使用できません。

COM API を使用している場合は、文字列を BSTR としてマーシャリングする必要があります。 UnmanagedType.BStr 値を使用すると、文字列を BSTR としてマーシャリングできます。

C#
public struct BString
{
    [MarshalAs(UnmanagedType.BStr)]
    public string str;
}

WinRT ベースの API を使用している場合は、文字列を HSTRING としてマーシャリングする必要があります。 UnmanagedType.HString 値を使用すると、文字列を HSTRING としてマーシャリングできます。 HSTRING のマーシャリングは、組み込みの WinRT サポートを備えたランタイムでのみサポートされます。 WinRT のサポートは .NET 5 で削除されたので、HSTRING のマーシャリングは .NET 5 以降ではサポートされません。

C#
public struct HString
{
    [MarshalAs(UnmanagedType.HString)]
    public string str;
}

API で、構造体でインプレースで文字列を渡す必要がある場合は、UnmanagedType.ByValTStr 値を使用できます。 ByValTStr でマーシャリングされた文字列のエンコードは、CharSet 属性によって決まる点に注意してください。 さらに、文字列長を MarshalAsAttribute.SizeConst フィールドで渡す必要があります。

C#
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DefaultString
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string str;
}
C#
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DefaultString
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string str;
}

10 進数フィールドのマーシャリングのカスタマイズ

Windows を使用している場合は、ネイティブの CY または CURRENCY 構造体を使用する API がいくつかあります。 既定で、.NET の decimal 型はネイティブの DECIMAL 構造体にマーシャリングされます。 ただし、値が UnmanagedType.CurrencyMarshalAsAttribute を使用して、decimal 値をネイティブの CY 値に変換するようにマーシャラーに指示することができます。

C#
public struct Currency
{
    [MarshalAs(UnmanagedType.Currency)]
    public decimal dec;
}

Unions

共用体は、同じメモリ上に異なる型のデータを含めることができるデータ型です。 これは、C 言語のデータの一般的な形式です。 共用体は、.NET では LayoutKind.Explicit を使って表すことができます。 .NET で共用体を定義するときは、構造体を使うことをお勧めします。 クラスの使用は、レイアウトの問題が生じ、予期しない動作が発生する原因となるおそれがあります。

C#
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* にマーシャリングされます。

C#
public struct ObjectDefault
{
    public object obj;
}

オブジェクト フィールドを IDispatch* にマーシャリングする場合は、値が UnmanagedType.IDispatchMarshalAsAttribute を追加します。

C#
public struct ObjectDispatch
{
    [MarshalAs(UnmanagedType.IDispatch)]
    public object obj;
}

VARIANT としてマーシャリングする場合は、値が UnmanagedType.StructMarshalAsAttribute を追加します。

C#
public struct ObjectVariant
{
    [MarshalAs(UnmanagedType.Struct)]
    public object 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