Freigeben über


Typenmarschallierung

Marshalling ist der Prozess der Transformation von Typen, wenn sie zwischen verwaltetem und systemeigenem Code wechseln müssen.

Marshalling ist erforderlich, da die Typen im verwalteten und nicht verwalteten Code unterschiedlich sind. In verwaltetem Code haben Sie beispielsweise eine string, während nicht verwaltete Zeichenfolgen .NET string-Codierung (UTF-16), ANSI Code Page-Codierung, UTF-8, null-terminated, ASCII usw. sein können. Standardmäßig versucht das P/Invoke-Subsystem, das richtige Zu tun, basierend auf dem Standardverhalten, das in diesem Artikel beschrieben wird. In situationen, in denen Sie zusätzliche Kontrolle benötigen, können Sie jedoch das MarshalAs-Attribut verwenden, um anzugeben, was der erwartete Typ auf der nicht verwalteten Seite ist. Wenn die Zeichenfolge beispielsweise als NULL-beendete UTF-8-Zeichenfolge gesendet werden soll, können Sie dies wie folgt tun:

[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);

Wenn Sie das System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute Attribut auf die Assembly anwenden, gelten die Regeln im folgenden Abschnitt nicht. Informationen dazu, wie .NET-Werte dem nativen Code zur Verfügung gestellt werden, wenn dieses Attribut angewendet wird, finden Sie unter disabled runtime marshalling.

Standardregeln für das Marshallen von häufig verwendeten Typen

Allgemein versucht die Runtime, beim Marshallen das „Richtige“ zu tun, damit Sie möglichst wenig Arbeitsaufwand haben. In den folgenden Tabellen wird beschrieben, wie jeder Typ standardmäßig gemarshallt wird, wenn er in einem Parameter oder Feld verwendet wird. Die Ganzzahl- und Zeichentypen C99/C++11 mit fester Breite werden verwendet, um sicherzustellen, dass die folgende Tabelle für alle Plattformen korrekt ist. Sie können jeden systemeigenen Typ verwenden, der die gleichen Ausrichtungs- und Größenanforderungen wie diese Typen aufweist.

Die erste Tabelle beschreibt die Zuordnungen für Typen, für die das Marshallen für P/Invoke und Feldmarshalling gleich ist.

Von Bedeutung

Verwenden Sie beim Aufrufen einer C-Funktion, die long verwendet, CLong oder CULong (.NET 6+) anstelle von C# long. Ausführliche Informationen und Problemumgehungen für frühere .NET-Versionen finden Sie unter Cross-Platform-Datentypüberlegungen.

Hinweis

Der typ wchar_t ist UTF-16 (2 Bytes) auf Windows, ist aber auf anderen Plattformen compilerdefiniert – in der Regel UTF-32 (4 Byte) unter Linux und macOS. Aus diesem Grund ist es schwierig, wchar_t* als eine plattformübergreifende ABI zu verwenden. Wenn Sie eine plattformübergreifende native API entwerfen, bevorzugen Sie char* mit einem klar definierten Codierungsvertrag (z. B. UTF-8) anstelle von wchar_t*.

Hinweis

Systemeigene char* Zeichenfolgen verwenden die Codierung, die von der Bibliothek oder Plattform definiert wird. Wenn Sie eine C-Funktion aufrufen, die das char* verwendet, stimmen Sie mit der erwarteten Codierung überein, indem Sie die richtige Zeichenfolgen-Marshallingoption auswählen, z.B. StringMarshalling.Utf8 für UTF-8, StringMarshalling.Utf16 für UTF-16 oder StringMarshalling.Custom für andere Codierungen.

C#-Schlüsselwort .NET Typ Nativer Typ
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 Entweder char oder char16_t abhängig von der Codierung des P/Invoke oder der Struktur. Weitere Informationen finden Sie in der Zeichensatzdokumentation.
System.Char Entweder char* oder char16_t* abhängig von der Codierung des P/Invoke oder der Struktur. Weitere Informationen finden Sie in der Zeichensatzdokumentation.
nint System.IntPtr intptr_t
nuint System.UIntPtr uintptr_t
.NET Zeigertypen (z. B. void*) void*
Abgeleitet vom Typ System.Runtime.InteropServices.SafeHandle void*
Abgeleitet vom Typ System.Runtime.InteropServices.CriticalHandle void*
bool System.Boolean Win32-Typ BOOL
decimal System.Decimal COM-DECIMAL-Struktur
.NET Stellvertreter Systemeigener Funktionszeiger
System.DateTime Win32-Typ DATE
System.Guid Win32-Typ GUID

Einige Marshallingkategorien weisen unterschiedliche Standardwerte auf, wenn Sie das Marshalling als Parameter oder Struktur durchführen.

.NET Typ Nativer Typ (Parameter) Nativer Typ (Feld)
.NET Array Ein Zeiger auf den Anfang eines Arrays aus nativen Darstellungen der Arrayelemente Ohne Attribut [MarshalAs] nicht zulässig
Eine Klasse mit einem LayoutKind-Wert vom Typ Sequential oder Explicit Ein Zeiger auf die native Darstellung der Klasse Die native Darstellung der Klasse

Die folgende Tabelle enthält die Standard-Marshallingregeln, die nur für Windows gelten. Auf Nicht-Windows-Plattformen können Sie diese Typen nicht marshallen.

.NET Typ Nativer Typ (Parameter) Nativer Typ (Feld)
System.Object VARIANT IUnknown*
System.Array COM-Schnittstelle Ohne Attribut [MarshalAs] nicht zulässig
System.ArgIterator va_list Nicht zulässig
System.Collections.IEnumerator IEnumVARIANT* Nicht zulässig
System.Collections.IEnumerable IDispatch* Nicht zulässig
System.DateTimeOffset int64_t – repräsentiert die Anzahl von Takten seit dem 1. Januar 1601 um Mitternacht int64_t – repräsentiert die Anzahl von Takten seit dem 1. Januar 1601 um Mitternacht

Einige Typen können nur als Parameter und nicht als Felder gemarshallt werden. Diese Typen sind in der folgenden Tabelle aufgeführt:

.NET Typ Nativer Typ (nur Parameter)
System.Text.StringBuilder Entweder char* oder char16_t*, je nach CharSet des P/Invoke. Weitere Informationen finden Sie in der Zeichensatzdokumentation.
System.ArgIterator va_list (nur auf Windows x86/x64/arm64)
System.Runtime.InteropServices.ArrayWithOffset void*
System.Runtime.InteropServices.HandleRef void*

Wenn diese Standardeinstellungen nicht genau das tun, was Sie möchten, können Sie anpassen, wie Parameter zugeordnet werden. Der Artikel zum Marshalling von Parametern führt Sie durch das Anpassen, wie verschiedene Parametertypen ge marshallt werden.

Standardmarshalling in COM-Szenarios

Wenn Sie Methoden für COM-Objekte in .NET aufrufen, ändert die .NET Laufzeit die Standard-Marshallregeln so, dass sie mit allgemeinen COM-Semantiken übereinstimmen. In der folgenden Tabelle sind die Regeln aufgeführt, die die .NET-Laufzeit in COM-Szenarien verwendet:

.NET Typ Nativer Typ (COM-Methodenaufrufe)
System.Boolean VARIANT_BOOL
StringBuilder LPWSTR
System.String BSTR
Delegattypen _Delegate* im .NET Framework. Unzulässig in .NET Core und .NET 5+.
System.Drawing.Color OLECOLOR
.NET Array SAFEARRAY
System.String[] SAFEARRAY aus BSTRs

Marshallen von Klassen und Strukturen

Ein weiterer Aspekt des Marshallens von Typen ist die Übergabe einer Struktur an eine nicht verwaltete Methode. Beispielsweise erfordern einige der nicht verwalteten Methoden eine Struktur als Parameter. In diesen Fällen müssen Sie eine entsprechende Struktur oder eine Klasse in verwalteten Teilen der Welt erstellen, um sie als Parameter zu verwenden. Das Definieren der Klasse reicht jedoch nicht aus, Sie müssen auch den Marshaller anweisen, wie Felder in der Klasse der nicht verwalteten Struktur zugeordnet werden. Hier wird das StructLayout Attribut nützlich.

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;
    }
}

Der vorherige Code zeigt ein einfaches Beispiel für das Aufrufen der GetSystemTime() Funktion. Das interessante Bit befindet sich in Zeile 13. Das Attribut gibt an, dass die Felder der Klasse sequenziell der Struktur auf der anderen Seite (nicht verwaltet) zugeordnet werden sollen. Dies bedeutet, dass die Benennung der Felder nicht wichtig ist, nur ihre Reihenfolge ist wichtig, da sie der nicht verwalteten Struktur entsprechen muss, wie im folgenden Beispiel gezeigt:

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

Manchmal führt das standardmäßige Marshalling für Ihre Struktur nicht zum gewünschten Ergebnis. Im Artikel Anpassen des Marshallens für Strukturen erfahren Sie, wie Sie das Marshalling für Ihre Struktur anpassen.