Quellgenerierung für Plattformaufrufe

In .NET 7 wurde ein Quellgenerator für Plattformaufrufe eingeführt, der LibraryImportAttribute in C#-Code erkennt.

Wenn die Quellgenerierung nicht verwendet wird, generiert das integrierte Interoperabilitätssystem in der .NET-Runtime zur Laufzeit einen Zwischensprache-Stub (JIT-Stream von Zwischenspracheanweisungen), um den Übergang von verwaltetem zu nicht verwaltetem Code zu erleichtern. Im folgenden Code werden das Definieren und das Aufrufen eines Plattformaufrufs (P/Invoke) gezeigt, der diesen Mechanismus verwendet:

[DllImport(
    "nativelib",
    EntryPoint = "to_lower",
    CharSet = CharSet.Unicode)]
internal static extern string ToLower(string str);

// string lower = ToLower("StringToConvert");

Der Zwischensprache-Stub übernimmt das Marshallen von Parametern und Rückgabewerten sowie das Aufrufen des nicht verwalteten Codes unter Berücksichtigung von Einstellungen in DllImportAttribute, die sich auf das Aufrufen des nicht verwalteten Codes auswirken (beispielsweise SetLastError). Da dieser Zwischensprache-Stub zur Laufzeit generiert wird, ist er für Szenarien mit AOT-Compiler (Ahead-Of-Time) oder Zwischensprachekürzung nicht verfügbar. Die Generierung der Zwischensprache ist ein wichtiger Kostenfaktor, der beim Marshallen berücksichtigt werden muss. Diese Kosten können in Bezug auf die Anwendungsleistung und die Unterstützung potenzieller Zielplattformen gemessen werden, die möglicherweise keine dynamische Codegenerierung ermöglichen. Beim Anwendungsmodell für natives AOT wird der gesamte Code im Voraus direkt als nativer Code vorkompiliert, um Probleme mit der dynamischen Codegenerierung zu vermeiden. Die Verwendung von DllImport ist keine Option für Plattformen, die vollständige native AOT-Szenarien erfordern. Daher ist die Verwendung anderer Ansätze (z. B. Quellgenerierung) besser geeignet. Das Debuggen der Marshalllogik in DllImport-Szenarien ist ebenfalls nicht zu unterschätzen.

Der Plattformaufruf-Quellgenerator, der im .NET 7 SDK enthalten und standardmäßig aktiviert ist, sucht in einer Methode vom Typ static und partial nach LibraryImportAttribute, um die Quellgenerierung von Marshallcode zur Kompilierzeit auszulösen. Dadurch muss zur Laufzeit kein Zwischensprache-Stub generiert werden, und der Plattformaufruf kann inline erfolgen. Analysetools und Codekorrekturen sind ebenfalls enthalten, um die Migration vom integrierten System zum Quellgenerator sowie die allgemeine Nutzung zu unterstützen.

Grundlegende Verwendung

LibraryImportAttribute wird ähnlich verwendet wie DllImportAttribute. Das vorherige Beispiel kann für die Verwendung der Plattformaufruf-Quellgenerierung konvertiert werden. Hierzu wird LibraryImportAttribute verwendet und die Methode als partial (anstelle von extern) markiert:

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower",
    StringMarshalling = StringMarshalling.Utf16)]
internal static partial string ToLower(string str);

Während der Kompilierung wird der Quellgenerator ausgelöst, um eine Implementierung der ToLower-Methode zu generieren, die das Marshallen des string-Parameters und des Rückgabewerts als UTF-16 übernimmt. Da es sich beim Marshallen nun um generierten Quellcode handelt, kann die Logik in einem Debugger untersucht und schrittweise durchlaufen werden.

MarshalAs

Der Quellgenerator berücksichtigt auch MarshalAsAttribute. Der vorherige Code kann auch wie folgt geschrieben werden:

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower")]
[return: MarshalAs(UnmanagedType.LPWStr)]
internal static partial string ToLower(
    [MarshalAs(UnmanagedType.LPWStr)] string str);

Einige Einstellungen für MarshalAsAttribute werden nicht unterstützt. Der Quellgenerator gibt einen Fehler aus, wenn Sie versuchen, nicht unterstützte Einstellungen zu verwenden. Weitere Informationen finden Sie unter Unterschiede zu DllImport.

Aufrufkonvention

Verwenden Sie UnmanagedCallConvAttribute, um die Aufrufkonvention anzugeben. Beispiel:

[LibraryImport(
    "nativelib",
    EntryPoint = "to_lower",
    StringMarshalling = StringMarshalling.Utf16)]
[UnmanagedCallConv(
    CallConvs = new[] { typeof(CallConvStdcall) })]
internal static partial string ToLower(string str);

Unterschiede zu DllImport

LibraryImportAttribute ist in den meisten Fällen als einfache Konvertierung von DllImportAttribute gedacht, es gibt jedoch einige bewusste Änderungen:

  • Für CallingConvention gibt es keine Entsprechung in LibraryImportAttribute. UnmanagedCallConvAttribute sollte stattdessen verwendet werden.
  • CharSet (für CharSet) wurde durch StringMarshalling (für StringMarshalling) ersetzt. ANSI wurde entfernt, und UTF-8 ist jetzt als erstklassige Option verfügbar.
  • Für BestFitMapping und ThrowOnUnmappableChar gibt es keine Entsprechung. Diese Felder waren nur beim Marshallen einer ANSI-Zeichenfolge unter Windows relevant. Der generierte Code zum Marshallen einer ANSI-Zeichenfolge weist das entsprechende Verhalten von BestFitMapping=false und ThrowOnUnmappableChar=false auf.
  • Für ExactSpelling gibt es keine Entsprechung. Dieses Feld war eine Windows-orientierte Einstellung und hatte keine Auswirkungen auf Windows-fremde Betriebssysteme. Der Methodenname oder EntryPoint muss exakt der Schreibweise des Einstiegspunktnamens entsprechen. Dieses Feld wurde in der Vergangenheit im Zusammenhang mit den Suffixen A und W aus der Win32-Programmierung verwendet.
  • Für PreserveSig gibt es keine Entsprechung. Dieses Feld war eine Windows-orientierte Einstellung. Der generierte Code wandelt die Signatur immer direkt um.
  • Das Projekt muss mit AllowUnsafeBlocks als unsicher gekennzeichnet werden.

Es gibt auch Unterschiede bei der Unterstützung einiger Einstellungen in MarshalAsAttribute, beim standardmäßigen Marshallen bestimmter Typen sowie bei anderen interoperabilitätsbezogenen Attributen. Weitere Informationen finden Sie in der Dokumentation zu Kompatibilitätsunterschieden.

Siehe auch