型のマーシャリング
マーシャリングとは、マネージド コードとネイティブ コードの間でやり取りする必要がある場合に型を変換するプロセスです。
マネージド コードとアンマネージド コード内の型は異なるため、マーシャリングが必要です。 たとえば、マネージド コードには 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] 属性が必要です |
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 の 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[] |
BSTR の SAFEARRAY |
クラスと構造体のマーシャリング
型のマーシャリングの別の側面は、構造体をアンマネージ メソッドに渡す方法です。 たとえば、一部のアンマネージ メソッドでは、パラメーターとして構造体が必要です。 このような場合、環境のマネージド部分に対応する構造体またはクラスを作成し、それをパラメーターとして使用する必要があります。 ただし、クラスを定義するだけでは不十分で、マーシャラーに、クラス内のフィールドをアンマネージ構造体にマップする方法を指示する必要もあります。 ここで 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;
構造体の既定のマーシャリングでは、必要な処理が実行されないことがあります。 「構造体のマーシャリングのカスタマイズ」の記事では、構造体のマーシャリング方法をカスタマイズする手順が説明されています。
.NET