Freigeben über


Marshallen von Typen

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 verfügen Sie beispielsweise über eine string, während nicht verwaltete Zeichenfolgen .NET-Codierung string (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 beim Anwenden dieses Attributs für nativen Code verfügbar gemacht werden, finden Sie unter Deaktiviertes Runtimemarshalling.

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.

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-Delegat (Delegierter) 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 unter 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 unter 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 .NET-Runtimes in COM-Szenarien verwenden:

.NET-Typ Nativer Typ (COM-Methodenaufrufe)
System.Boolean VARIANT_BOOL
StringBuilder LPWSTR
System.String BSTR
Delegattypen _Delegate* in .NET Framework. In .NET Core und .NET 5+ unzulässig.
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.