型のマーシャリング

マーシャリングとは、マネージド コードとネイティブ コードの間でやり取りする必要がある場合に型を変換するプロセスです。

マネージド コードとアンマネージド コード内の型は異なるため、マーシャリングが必要です。 マネージド コードで、たとえば、String があるとします。アンマネージド環境では、文字列は Unicode ("ワイド")、Unicode 以外、Null 終了、ASCII などです。既定で、P/Invoke サブシステムは、この記事で説明する既定の動作に基づいて適切な処理を実行しようと試みます。 しかし、追加の制御が必要な状況では、MarshalAs 属性を採用して、アンマネージ側で期待する型を指定します。 たとえば、文字列を null で終わる ANSI 文字列として送信させる場合は、次のように指定できます。

[DllImport("somenativelibrary.dll")]
static extern int MethodA([MarshalAs(UnmanagedType.LPStr)] string parameter);

System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute 属性をアセンブリに適用した場合、次のセクションの規則は適用されません。 この属性が適用されるときに .NET の値がネイティブ コードに公開される方法については、ランタイム マーシャリングの無効化に関するドキュメントをご覧ください。

共通型をマーシャリングする場合の既定の規則

一般的に、ランタイムは、マーシャリングするときにユーザーの必要な作業量が最小限になるように "適切な処理" を実行しようと試みます。 次の表は、パラメーターまたはフィールドで使用されるときに、各型が既定でどのようにマーシャリングされるかを示しています。 次の表がすべてのプラットフォームで正しくなるように、C99/C++ 11 固定幅の整数と文字型が使用されています。 このような型と同じ配置とサイズの要件を持つ任意のネイティブ型を使用できます。

この最初の表は、マーシャリングが P/Invoke とフィールド マーシャリングの両方で同じであるさまざまな型のマッピングを示しています。

C# キーワード .NET 型 ネイティブ型
byte System.Byte uint8_t
sbyte System.SByte int8_t
short System.Int16 int16_t
ushort System.UInt16 uint16_t
int System.Int32 int32_t
uint System.UInt32 uint32_t
long System.Int64 int64_t
ulong System.UInt64 uint64_t
char System.Char P/Invoke または構造体の CharSet に応じて、char または char16_t のいずれか。 文字セットのドキュメントを参照してください。
System.Char P/Invoke または構造体の CharSet に応じて、char* または char16_t* のいずれか。 文字セットのドキュメントを参照してください。
nint System.IntPtr intptr_t
nuint System.UIntPtr uintptr_t
.NET ポインター型 (例: void*) void*
System.Runtime.InteropServices.SafeHandle の派生型 void*
System.Runtime.InteropServices.CriticalHandle の派生型 void*
bool System.Boolean Win32 BOOL
decimal System.Decimal COM DECIMAL 構造体
.NET デリゲート ネイティブ関数ポインター
System.DateTime Win32 DATE
System.Guid Win32 GUID

パラメーターまたは構造体としてマーシャリングする場合、マーシャリングの一部のカテゴリでは既定が異なります。

.NET 型 ネイティブ型 (パラメーター) ネイティブ型 (フィールド)
.NET 配列 配列要素のネイティブ表現の配列の先頭へのポインター。 使用するには [MarshalAs] 属性が必要です
LayoutKindSequential または Explicit のクラス クラスのネイティブ表現へのポインター クラスのネイティブ表現

次の表には、Windows のみの既定のマーシャリング規則が含まれています。 Windows 以外のプラットフォームでは、このような型をマーシャリングすることはできません。

.NET 型 ネイティブ型 (パラメーター) ネイティブ型 (フィールド)
System.Object VARIANT IUnknown*
System.Array COM インターフェイス 使用するには [MarshalAs] 属性が必要です
System.ArgIterator va_list 使用できません
System.Collections.IEnumerator IEnumVARIANT* 使用できません
System.Collections.IEnumerable IDispatch* 使用できません
System.DateTimeOffset 1601 年 1 月 1 日午前 0 時以降のティック数を表す int64_t 1601 年 1 月 1 日午前 0 時以降のティック数を表す int64_t

一部の型は、フィールドとしてではなくパラメーターとしてのみマーシャリングできます。 このような型の一覧を次の表に示します。

.NET 型 ネイティブ型 (パラメーターのみ)
System.Text.StringBuilder P/Invoke の CharSet に応じて、char* または char16_t* のいずれか。 文字セットのドキュメントを参照してください。
System.ArgIterator va_list (Windows x86/x64/arm64 のみ)
System.Runtime.InteropServices.ArrayWithOffset void*
System.Runtime.InteropServices.HandleRef void*

これらの既定値が目的と合わない場合は、パラメーターのマーシャリング方法をカスタマイズできます。 パラメーターのマーシャリングの記事では、さまざまなパラメーターの型をマーシャリングする方法のカスタマイズ手順について説明しています。

COM シナリオでの既定のマーシャリング

.NET で COM オブジェクトのメソッドを呼び出している場合、.NET ランタイムによって共通の COM セマンティクスを満たすように、既定のマーシャリング ルールが変更されます。 次の表では、COM シナリオで .NET ランタイムによって使用される規則が一覧にされます。

.NET 型 ネイティブ型 (COM メソッドの呼び出し)
System.Boolean VARIANT_BOOL
StringBuilder LPWSTR
System.String BSTR
デリゲート型 .NET Framework では _Delegate*。 .NET Core および .NET 5+ では許可されていません。
System.Drawing.Color OLECOLOR
.NET 配列 SAFEARRAY
System.String[] BSTRSAFEARRAY

クラスと構造体のマーシャリング

型のマーシャリングの別の側面は、構造体をアンマネージ メソッドに渡す方法です。 たとえば、一部のアンマネージ メソッドでは、パラメーターとして構造体が必要です。 このような場合、環境のマネージド部分に対応する構造体またはクラスを作成し、それをパラメーターとして使用する必要があります。 ただし、クラスを定義するだけでは不十分で、マーシャラーに、クラス内のフィールドをアンマネージ構造体にマップする方法を指示する必要もあります。 ここで StructLayout 属性が役立ちます。

[DllImport("kernel32.dll")]
static extern void GetSystemTime(SystemTime systemTime);

[StructLayout(LayoutKind.Sequential)]
class SystemTime {
    public ushort Year;
    public ushort Month;
    public ushort DayOfWeek;
    public ushort Day;
    public ushort Hour;
    public ushort Minute;
    public ushort Second;
    public ushort Millisecond;
}

public static void Main(string[] args) {
    SystemTime st = new SystemTime();
    GetSystemTime(st);
    Console.WriteLine(st.Year);
}

上のコードは、GetSystemTime() 関数を呼び出す簡単な例を示しています。 興味深いビットは 4 行目にあります。 この属性は、他方 (アンマネージ) の側でクラスのフィールドを構造体に順番にマップする必要があることを指定します。 このことは、次の例に示すアンマネージ構造体に対応する必要があるため、フィールドの名前付けは重要でなく、それらの順番だけが重要であることを意味します。

typedef struct _SYSTEMTIME {
  WORD wYear;
  WORD wMonth;
  WORD wDayOfWeek;
  WORD wDay;
  WORD wHour;
  WORD wMinute;
  WORD wSecond;
  WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;

構造体の既定のマーシャリングでは、必要な処理が実行されないことがあります。 「構造体のマーシャリングのカスタマイズ」の記事では、構造体のマーシャリング方法をカスタマイズする手順が説明されています。