マーシャリング は、マネージド コードとネイティブ コードの間で型をクロスする必要がある場合に型を変換するプロセスです。
マーシャリングが必要なのは、マネージド コードとアンマネージド コードの型が異なるためです。 たとえば、マネージド コードには 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.LPUTF8Str)] 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]属性なしでは許可されません |
LayoutKindをSequentialまたは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 の char* に応じて、char16_t* または CharSet のいずれか。
文字セットのドキュメントを参照してください。 |
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 属性が役立ちます。
using System;
using System.Runtime.InteropServices;
Win32Interop.GetSystemTime(out Win32Interop.SystemTime systemTime);
Console.WriteLine(systemTime.Year);
internal static partial class Win32Interop
{
[LibraryImport("kernel32.dll")]
internal static partial void GetSystemTime(out SystemTime systemTime);
[StructLayout(LayoutKind.Sequential)]
internal ref 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;
}
}
前のコードは、 GetSystemTime() 関数を呼び出す簡単な例を示しています。 面白い部分は 13 行目にあります。 この属性は、クラスのフィールドを、もう一方の (アンマネージ) 側の構造体に順番にマップすることを指定します。 つまり、次の例に示すように、アンマネージド構造体に対応する必要があるため、フィールドの名前付けは重要ではなく、順序だけが重要です。
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
時々、構造体のデフォルトのマーシャリングが期待通りに動作しない場合があります。 構造マーシャリングのカスタマイズに関する記事では、構造のマーシャリング方法をカスタマイズする方法について説明します。
.NET