.NET Native und Kompilierung

Windows-Desktopanwendungen, die auf .NET Framework abzielen, werden in einer bestimmten Programmiersprache geschrieben und in Zwischensprache (IL) kompiliert. Zur Laufzeit ist ein Just-in-Time-Compiler (JIT) dafür verantwortlich, die IL direkt vor der ersten Ausführung einer Methode in systemeigenen Code für den lokalen Computer zu kompilieren. Im Gegensatz dazu konvertiert die .NET Native-Toolkette den Quellcode zur Kompilierzeit in systemeigenen Code. In diesem Artikel wird .NET Native mit anderen Kompilierungstechnologien verglichen, die für .NET Framework Apps verfügbar sind, und bietet auch einen praktischen Überblick darüber, wie .NET Native systemeigenen Code erzeugt, der Ihnen helfen kann, zu verstehen, warum Ausnahmen, die in Code kompiliert werden, .NET Native nicht in JIT-kompilierten Code auftreten.

Generieren nativer Binärdateien

Eine Anwendung, die auf .NET Framework ausgerichtet ist und nicht mithilfe der .NET Native Toolkette kompiliert wird, besteht aus der Anwendungsassembly, die folgendes enthält:

  • Metadaten, die die Assembly, ihre Abhängigkeiten, die enthaltenen Typen und ihre Member beschreiben. Die Metadaten werden für die Reflektion und den spät gebunden Zugriff sowie in einigen Fällen auch vom Compiler und den Buildtools verwendet.

  • Implementierungscode. Dieser besteht aus Opcodes der Zwischensprache (Intermediate Language, IL). Zur Laufzeit übersetzt der JIT-Compiler (Just-In-Time) den Implementierungscode für die Zielplattform in systemeigenen Code.

Zusätzlich zur Hauptanwendungsassembly erfordert eine App folgendes:

  • Alle zusätzlichen Klassenbibliotheken oder Assemblys von Drittanbietern, die für die App erforderlich sind. Diese Assemblys schließen gleichermaßen die Assembly, ihre Typen und Member beschreibende Metadaten sowie die Zwischensprache (IL) ein, die alle Typmember implementiert.

  • Die .NET Framework-Klassenbibliothek. Hierbei handelt es sich um eine Auflistung von Assemblys, die während der Installation des .NET Frameworks auf dem lokalen System installiert werden. Die in der .NET Framework-Klassenbibliothek einbezogenen Assemblys umfassen einen vollständigen Satz von Metadaten und Implementierungscode.

  • Die Common Language Runtime. Hierbei handelt es sich um eine Auflistung von DLLs (Dynamic Link Library), die Dienste wie die Folgenden ausführen: Laden von Assemblys, Speicherverwaltung und Garbage Collection, Ausnahmebehandlung, JIT-Kompilierung (Just-In-Time), Remoting und Interoperabilität. Wie die Klassenbibliothek wird die Common Language Runtime im Rahmen der .NET Framework-Installation auf dem lokalen System installiert.

Beachten Sie, dass die gesamte Common Language Runtime sowie die Metadaten und die Zwischensprache (Intermediate Language, IL) für alle Typen in anwendungsspezifischen Assemblys, Assemblys von Drittanbietern und Systemassemblys vorhanden sein müssen, damit die App erfolgreich ausgeführt werden kann.

Just-in-Time-Kompilierung

Die Eingabe für die .NET Native Toolkette ist die UWP-App, die vom C#- oder Visual Basic-Compiler erstellt wurde. Mit anderen Worten, die .NET Native Toolkette beginnt die Ausführung, wenn der Sprachcompiler die Kompilierung einer UWP-App abgeschlossen hat.

Tipp

Da es sich bei der Eingabe für .NET Native um die in verwaltete Assemblys geschriebene Zwischensprache (Intermediate Language) und die Metadaten handelt, können Sie weiterhin benutzerdefinierten Code generieren oder andere benutzerdefinierte Operationen mithilfe von Bildvorstufen- oder Postbuild-Ereignissen oder durch Ändern der MSBuild-Projektdatei ausführen.

Kategorien von Tools, die die Zwischensprache ändern und dadurch die .NET-Toolkette daran hindern, die Zwischensprache einer App zu analysieren, werden jedoch nicht unterstützt. Obfuskatoren sind die bemerkenswertesten Tools dieses Typs.

Beim Konvertieren einer App aus der Zwischensprache in nativen Code führt die .NET Native-Toolkette Vorgänge wie die Folgenden aus:

  • Für bestimmte Codepfade wird Code ersetzt, der auf Reflektion und Metadaten mit statischem systemeigenem Code beruht. Wenn ein Werttyp z. B. die ValueType.Equals-Methode nicht außer Kraft setzt, verwendet der Standardtest zur Ermittlung der Gleichheit die Reflektion, um FieldInfo-Objekte abzurufen, die die Felder des Werttyps darstellen. Anschließend werden die Feldwerte von zwei Instanzen verglichen. Beim Kompilieren in nativen Code ersetzt die .NET Native-Toolkette den Reflektionscode und die Metadaten durch einen statischen Vergleich der Feldwerte.

  • Dabei wird nach Möglichkeit versucht, alle Metadaten zu beseitigen.

  • In die Assemblys der endgültigen App wird nur der Implementierungscode einbezogen, der tatsächlich von der App aufgerufen wird. Dies betrifft vor allem Code in Bibliotheken von Drittanbietern und in der .NET Framework-Klassenbibliothek. Eine Anwendung hängt demzufolge nicht mehr von Bibliotheken von Drittanbietern oder der vollständigen .NET Framework-Klassenbibliothek ab. Der Code in Bibliotheken von Drittanbietern und in .NET Framework-Klassenbibliotheken ist jetzt stattdessen im Hinblick auf die Anwendung lokal verfügbar.

  • Dadurch wird die vollständige Common Language Runtime (CLR) durch eine umgestaltete Common Language Runtime ersetzt, die in erster Linie den Garbage Collector enthält. Die umgestaltete Common Language Runtime befindet sich in einer Bibliothek namens "mrt100_app.dll", die für die Anwendung lokal verfügbar und nur wenige hundert Kilobyte groß ist. Dies ist dadurch möglich, dass die statische Verknüpfung den Bedarf an vielen der von der Common Language Runtime ausgeführten Dienste beseitigt.

    Hinweis

    .NET Native verwendet denselben Garbage Collector als Standard-CLR (Common Language Runtime). Beim .NET Native Garbage Collector ist die Garbage Collection im Hintergrund standardmäßig aktiviert. Weitere Informationen zur Garbage Collection finden Sie unter Grundlagen der Garbage Collection.

Wichtig

.NET Native kompiliert eine gesamte Anwendung in eine systemeigene Anwendung. Es ist Ihnen dabei nicht gestattet, einzelne Assemblys, die eine Klassenbibliothek enthalten, in nativen Code zu kompilieren, damit diese unabhängig vom verwalteten Code aufgerufen werden können.

Die von der .NET Native-Toolkette erzeugte resultierende App wird im Debug- oder Releaseverzeichnis Ihres Projektverzeichnisses in ein Verzeichnis namens „ilc.out“ geschrieben. Die App besteht aus den folgenden Dateien:

  • <appName>.exe, eine stub ausführbare Datei, die einfach die Steuerung an einen speziellen Main Export in <appName>.dll überträgt.

  • <appName>.dll, eine Windows-Bibliothek mit dynamischem Link, die den gesamten Anwendungscode enthält, sowie Code aus der .NET Framework Klassenbibliothek und allen Drittanbieterbibliotheken, von denen Sie abhängig sind. Sie enthält auch Unterstützungscode, z. B. den für die Zusammenarbeit mit Windows und zum Serialisieren von Objekten in der App erforderlichen Code.

  • "mrt100_app.dll": Eine umgestaltete Common Language Runtime, die Laufzeitdienste wie die Garbage Collection bereitstellt.

Alle Abhängigkeiten werden vom APPX-Manifest der App erfasst. Zusätzlich zur EXE- und DLL-Datei der Anwendung sowie zur Datei "mrt100_app.dll", die direkt im AppX-Paket gebündelt werden, sind zwei weitere Dateien enthalten:

  • „msvcr140_app.dll“: Die von der Datei „mrt100_app.dll“ verwendete C-Laufzeitbibliothek (CRT). Sie ist über einen Frameworkverweis in das Paket einbezogen.

  • "mrt100.dll": Diese Bibliothek enthält Funktionen, die die Leistung der Datei "mrt100_app.dll" verbessern können, obwohl die Funktionalität der Datei "mrt100_app.dll" nicht verhindert wird, wenn diese Bibliothek nicht vorhanden ist. Wenn die Bibliothek verfügbar ist, wird sie auf dem lokalen Computer aus dem Verzeichnis "system32" geladen.

Da die .NET Native-Toolkette den Implementierungscode in Ihrer App nur verknüpft, wenn ihr bekannt ist, dass die App den Code tatsächlich aufruft, werden die Metadaten oder der Implementierungscode möglicherweise nicht in die App einbezogen, die in den folgenden Szenarien erforderlich sind:

  • Reflektion.

  • Dynamischer oder spät gebundener Aufruf

  • Serialisierung und Deserialisierung

  • COM-Interop

Fehlen die erforderlichen Metadaten oder der Implementierungscode zur Laufzeit, löst die .NET Native Common Language Runtime eine Ausnahme aus. Sie können diese Ausnahmen verhindern und sicherstellen, dass die .NET Native-Toolkette die erforderlichen Metadaten und den Implementierungscode enthält, indem Sie eine Laufzeitanweisungsdatei (eine XML-Datei) verwenden, die die Programmelemente bezeichnet, deren Metadaten oder Implementierungscode zur Laufzeit verfügbar sein müssen, und ihnen eine Laufzeitrichtlinie zuweist. Nachfolgend finden Sie die Standard-Runtime-Direktivendatei, die einem UWP-Projekt hinzugefügt wird, das von der .NET Native Toolkette kompiliert wird:

<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
  <Application>
    <Assembly Name="*Application*" Dynamic="Required All" />
  </Application>
</Directives>

Auf diese Weise werden sämtliche Typen und ihre Member in allen Assemblys im Anwendungspaket für die Reflektion und den dynamischen Aufruf aktiviert. Die Reflektion oder dynamische Aktivierung von Typen in Assemblys der .NET Framework-Klassenbibliothek werden jedoch nicht aktiviert. In vielen Fällen ist dies ausreichend.

Siehe auch