次の方法で共有


型マーシャリング

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

マーシャリングが必要なのは、マネージド コードとアンマネージド コードの型が異なるためです。 たとえば、マネージド コードには stringがあり、アンマネージ文字列は .NET string エンコード (UTF-16)、ANSI コード ページ エンコード、UTF-8、null 終端、ASCII などです。既定では、P/Invoke サブシステムは、この記事で説明されている既定の動作に基づいて適切な処理を実行しようとします。 ただし、追加の制御が必要な状況では、 MarshalAs 属性を使用して、アンマネージ側で想定される型を指定できます。 たとえば、文字列を null で終わる UTF-8 文字列として送信する場合は、次のようにできます。

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

// or

[LibraryImport("somenativelibrary.dll", StringMarshalling = StringMarshalling.Utf8)]
static extern int MethodB(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 または構造体のエンコードに応じて、 char または char16_t文字セットのドキュメントを参照してください。
System.Char P/Invoke または構造体のエンコードに応じて、 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]属性なしでは許可されません
SequentialまたはLayoutKindを持つクラス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 int64_t 1601 年 1 月 1 日の午前 0 時からのティック数を表す int64_t 1601 年 1 月 1 日の午前 0 時からのティック数を表す

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

.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 セマンティクスに一致するように既定のマーシャリング 規則を変更します。 次の表に、.NET ランタイムが COM シナリオで使用する規則を示します。

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

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

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

[LibraryImport("kernel32.dll")]
static partial void GetSystemTime(out SystemTime systemTime);

[StructLayout(LayoutKind.Sequential)]
struct 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;

構造体の既定のマーシャリングで必要な処理が行われない場合があります。 構造マーシャリングのカスタマイズに関する記事では、構造のマーシャリング方法をカスタマイズする方法について説明します。