Marshallen von Typen
Das Marshallen bezeichnet den Vorgang zum Umwandeln von Typen, wenn diese zwischen verwaltetem und nativem Code wechseln müssen.
Das Marshallen ist erforderlich, weil sich die Typen in verwaltetem und nicht verwaltetem Code unterscheiden. In verwaltetem Code verfügen Sie z. B. über 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 basierend auf dem Standardverhalten die richtige Aktion auszuführen. Dieses wird im vorliegenden Artikel beschrieben. In Situationen, in denen Sie zusätzliche Kontrolle benötigen, können Sie das MarshalAs-Attribut verwenden, um anzugeben, welcher Typ auf der nicht verwalteten Seite erwartet wird. Wenn Sie beispielsweise die Zeichenfolge als nicht mit NULL endende UTF-8-Zeichenfolge senden möchten, können Sie dies folgendermaßen erreichen:
[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);
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. Die folgenden Tabellen beschreiben das standardmäßige Marshallen der einzelnen Typen bei Verwendung in einem Parameter oder Feld. Die Integer- und Zeichentypen mit fester Breite von C99/C++11 werden verwendet, um sicherzustellen, dass die folgende Tabelle für alle Plattformen richtig ist. Sie können jeden nativen Typen verwenden, der die gleichen Anforderungen an Ausrichtung und Größe aufweist wie diese Typen.
Die erste Tabelle beschreibt die Zuordnungen für verschiedene 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 |
Je nach Codierung des P/Invoke oder der Struktur char oder char16_t . Informationen dazu finden Sie in der Dokumentation zum Zeichensatz. |
System.Char |
Je nach Codierung des P/Invoke oder der Struktur char* oder char16_t* . Informationen dazu finden Sie in der Dokumentation zum Zeichensatz. |
|
nint |
System.IntPtr |
intptr_t |
nuint |
System.UIntPtr |
uintptr_t |
.NET-Zeigertypen (z. B. void* ) |
void* |
|
Von System.Runtime.InteropServices.SafeHandle abgeleiteter Typ |
void* |
|
Von System.Runtime.InteropServices.CriticalHandle abgeleiteter Typ |
void* |
|
bool |
System.Boolean |
Win32-BOOL -Typ |
decimal |
System.Decimal |
COM-DECIMAL -Struktur |
.NET-Delegat | Nativer Funktionszeiger | |
System.DateTime |
Win32-DATE -Typ |
|
System.Guid |
Win32-GUID -Typ |
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 [MarshalAs] -Attribut 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 standardmäßigen 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 [MarshalAs] -Attribut 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 |
Für einige Typen ist das Marshalling nur als Parameter möglich, nicht als Felder. Diese Typen werden in der folgenden Tabelle aufgeführt:
.NET-Typ | Nativer Typ (nur Parameter) |
---|---|
System.Text.StringBuilder |
Entweder char* oder char16_t* , je nach CharSet von P/Invoke. Informationen dazu finden Sie in der Dokumentation zum Zeichensatz. |
System.ArgIterator |
va_list (nur auf Windows x86/x64/arm64) |
System.Runtime.InteropServices.ArrayWithOffset |
void* |
System.Runtime.InteropServices.HandleRef |
void* |
Wenn diese Standardwerte nicht exakt Ihren Vorstellungen entsprechen, können Sie angeben, auf welche Weise das Marshalling von Parametern durchgeführt werden soll. Im Artikel Marshallen von Parametern erfahren Sie, wie Sie das Marshalling verschiedener Parametertypen anpassen.
Standardmarshalling in COM-Szenarios
Wenn Sie Methoden in COM-Objekten in .NET aufrufen, ändert die .NET-Runtime die standardmäßigen Marshallingregeln, um der allgemeinen COM-Semantik zu entsprechen. In der folgenden Tabelle werden die Regeln aufgeführt, die die .NET-Runtime in COM-Szenarien verwendet:
.NET-Typ | Nativer Typ (COM-Methodenaufrufe) |
---|---|
System.Boolean |
VARIANT_BOOL |
StringBuilder |
LPWSTR |
System.String |
BSTR |
Delegattypen | _Delegate* in .NET Framework. In .NET Core sowie .NET 5 und höher nicht zulässig. |
System.Drawing.Color |
OLECOLOR |
.NET-Array | SAFEARRAY |
System.String[] |
SAFEARRAY aus BSTR s |
Marshallen von Klassen und Strukturen
Ein weiterer Aspekt des Marshallens von Typen ist die Übergabe einer Struktur an eine nicht verwaltete Methode. Einige der nicht verwalteten Methoden erfordern beispielsweise eine Struktur als Parameter. In diesen Fällen müssen Sie eine entsprechende Struktur oder eine Klasse im verwalteten Bereich erstellen, um sie als Parameter zu verwenden. Allerdings reicht das Definieren der Klasse nicht aus, Sie müssen dem Marshaller außerdem mitteilen, wie Felder in der Klasse der nicht verwalteten Struktur zuzuordnen sind. Hierbei ist das StructLayout
-Attribut nützlich.
[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);
}
Der obige Code zeigt ein einfaches Beispiel für einen Aufruf in der GetSystemTime()
-Funktion. Der interessante Teil befindet sich in Zeile 4. Das Attribut gibt an, dass die Felder der Klasse sequenziell der Struktur auf der anderen (nicht verwalteten) Seite zugeordnet werden sollen. Dies bedeutet, dass die Benennung der Felder nicht wichtig ist, sondern nur deren Reihenfolge, da diese 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;
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.