Engine & platform

Eine Einführung in die IL2CPP-Interna

JOSH PETERSON / UNITY TECHNOLOGIESSenior Software Engineer
May 6, 2015|10 Min.
Eine Einführung in die IL2CPP-Interna
Diese Website wurde aus praktischen Gründen für Sie maschinell übersetzt. Die Richtigkeit und Zuverlässigkeit des übersetzten Inhalts kann von uns nicht gewährleistet werden. Sollten Sie Zweifel an der Richtigkeit des übersetzten Inhalts haben, schauen Sie sich bitte die offizielle englische Version der Website an.

Vor fast einem Jahr haben wir begonnen, über die Zukunft der Skripterstellung in Unity zu sprechen. Das neue IL2CPP-Skript-Backend versprach, eine hochperformante, hochportable virtuelle Maschine für Unity zu entwickeln. Im Januar haben wir unsere erste Plattform mit IL2CPP, iOS 64-bit, ausgeliefert. Mit der Veröffentlichung von Unity 5 wurde eine weitere Plattform eingeführt: WebGL. Dank des Inputs unserer großen Benutzergemeinschaft haben wir viele Patch-Release-Updates für IL2CPP veröffentlicht, die den Compiler und die Laufzeit kontinuierlich verbessern. Wir haben nicht vor, IL2CPP weiter zu verbessern, aber wir dachten, es wäre vielleicht eine gute Idee, einen Schritt zurückzutreten und Ihnen ein wenig darüber zu erzählen, wie IL2CPP von innen heraus funktioniert. In den nächsten Monaten planen wir, über die folgenden Themen (und vielleicht weitere) in dieser IL2CPP Internals-Serie zu schreiben:

1. die Grundlagen - Toolchain und Kommandozeilenargumente (dieser Beitrag)

2. Eine Tour durch den generierten Code

3. Debugging-Tipps für generierten Code

4. Methodenaufrufe (normale Methoden, virtuelle Methoden, usw.)

5. Generische Implementierung der gemeinsamen Nutzung

6. P/invoke-Verpackungen für Typen und Methoden

7. Integration von Garbage Collectors

8. Prüfrahmen und Verwendung

Um diese Serie von Beiträgen zu ermöglichen, werden wir einige Details über die IL2CPP-Implementierung besprechen, die sich in Zukunft sicherlich ändern werden. Wir hoffen, dass wir dennoch einige nützliche und interessante Informationen liefern können.

Was ist IL2CPP?

Die Technologie, die wir als IL2CPP bezeichnen, besteht aus zwei verschiedenen Teilen.

  • Ein zeitlich vorausschauender Compiler (AOT)
  • Eine Laufzeitbibliothek zur Unterstützung der virtuellen Maschine

Der AOT-Compiler übersetzt Intermediate Language (IL), die Low-Level-Ausgabe von .NET-Compilern, in C++-Quellcode. Die Laufzeitbibliothek bietet Dienste und Abstraktionen wie einen Garbage Collector, plattformunabhängigen Zugriff auf Threads und Dateien sowie Implementierungen von internen Aufrufen (nativer Code, der verwaltete Datenstrukturen direkt verändert).

Der AOT-Compiler

Der IL2CPP AOT-Compiler hat den Namen il2cpp.exe. Unter Windows finden Sie es im Verzeichnis Editor\Data\il2cpp. Unter OSX befindet es sich im Verzeichnis Contents/Frameworks/il2cpp/build in der Unity-Installation.

Das Dienstprogramm il2cpp.exe ist eine verwaltete ausführbare Datei, die vollständig in C# geschrieben wurde. Während der Entwicklung von IL2CPP kompilieren wir es sowohl mit .NET- als auch mit Mono-Compilern. Das Dienstprogramm il2cpp.exe akzeptiert verwaltete Assemblies, die mit dem Mono-Compiler kompiliert wurden, der mit Unity ausgeliefert wird, und erzeugt C++-Code, den wir an einen plattformspezifischen C++-Compiler weitergeben.

Sie können sich die IL2CPP-Toolchain wie folgt vorstellen:

il2cpp Toolchain kleiner
Die Laufzeitbibliothek

Der andere Teil der IL2CPP-Technologie ist eine Laufzeitbibliothek zur Unterstützung der virtuellen Maschine. Wir haben diese Bibliothek fast ausschließlich mit C++-Code implementiert (sie enthält ein wenig plattformspezifischen Assembler-Code, aber das soll unter uns bleiben). Die Laufzeitbibliothek heißt libil2cpp und wird als statische Bibliothek geliefert, die in die ausführbare Datei des Players eingebunden ist. Einer der Hauptvorteile der IL2CPP-Technologie ist diese einfache und portable Laufzeitbibliothek.

Sie können einige Hinweise darauf finden, wie der libil2cpp-Code organisiert ist, indem Sie sich die Header-Dateien für libil2cpp ansehen, die mit Unity ausgeliefert werden (Sie finden sie im Verzeichnis Editor\Data\PlaybackEngines\webglsupport\BuildTools\Libraries\libil2cpp\include unter Windows oder im Verzeichnis Contents/Frameworks/il2cpp/libil2cpp unter OSX). Die Schnittstelle zwischen dem von il2cpp.exe erzeugten C++-Code und der Laufzeitumgebung von libil2cpp befindet sich beispielsweise in der Header-Datei codegen/il2cpp-codegen.h.

Ein wichtiger Bestandteil der Laufzeitumgebung ist der Garbage Collector. Wir liefern Unity 5 mit libgc, dem Boehm-Demers-Weiser Garbage Collector, aus. Allerdings wurde libil2cpp so entwickelt, dass wir andere Garbage Collectors verwenden können. So forschen wir zum Beispiel an einer Integration des Microsoft GC, der als Teil des CoreCLR als Open Source angeboten wurde. Mehr dazu erfahren Sie in unserem Beitrag über die Integration von Garbage Collectors zu einem späteren Zeitpunkt in dieser Serie.

Wie wird il2cpp.exe ausgeführt?

Schauen wir uns ein Beispiel an. Ich werde Unity 5.0.1 unter Windows verwenden und mit einem neuen, leeren Projekt beginnen. Damit wir zumindest ein Benutzerskript zum Konvertieren haben, füge ich diese einfache MonoBehaviour-Komponente zum Hauptkamera-Spielobjekt hinzu:

Unbekannter Blocktyp "codeBlock", bitte geben Sie einen Serializer dafür in der `serializers.types` prop an

Wenn ich für die WebGL-Plattform baue, kann ich den Process Explorer verwenden, um die Befehlszeile zu sehen, die Unity zur Ausführung von il2cpp.exe verwendet:

Unbekannter Blocktyp "codeBlock", bitte geben Sie einen Serializer dafür in der `serializers.types` prop an

Diese Befehlszeile ist ziemlich lang und furchtbar, also packen wir sie aus. Zunächst führt Unity diese ausführbare Datei aus:

Unbekannter Blocktyp "codeBlock", bitte geben Sie einen Serializer dafür in der `serializers.types` prop an

Das nächste Argument in der Befehlszeile ist das Dienstprogramm il2cpp.exe selbst.

Unbekannter Blocktyp "codeBlock", bitte geben Sie einen Serializer dafür in der `serializers.types` prop an

Die übrigen Befehlszeilenargumente werden an il2cpp.exe übergeben, nicht an mono.exe. Schauen wir sie uns an. Zunächst übergibt Unity fünf Flags an il2cpp.exe:

  • --copy-level=None
  • Geben Sie an, dass il2cpp.exe keine speziellen Dateikopien des generierten C++-Codes durchführen soll.
  • --enable-generic-sharing
  • Dies ist eine Funktion zur Reduzierung der Code- und Binärgröße. IL2CPP wird die Implementierung von generischen Methoden gemeinsam nutzen, wenn dies möglich ist.
  • --enable-unity-event-support
  • Spezielle Unterstützung, um sicherzustellen, dass der Code für Unity-Ereignisse, auf die über Reflection zugegriffen wird, korrekt generiert wird.
  • --output-format=Compact
  • Generieren Sie C++-Code in einem Format, das weniger Zeichen für Typ- und Methodennamen benötigt. Dieser Code ist schwer zu debuggen, da die Namen im AWL-Code nicht erhalten bleiben, aber er lässt sich oft schneller kompilieren, da der C++-Compiler weniger Code zu analysieren hat.
  • --extra-types.file="C:\Program Files\Unity\Editor\Data\il2cpp\il2cpp_default_extra_types.txt"
  • Verwenden Sie die standardmäßige (und leere) Datei "extra types". Diese Datei kann in ein Unity-Projekt eingefügt werden, damit il2cpp.exe weiß, welche generischen oder Array-Typen zur Laufzeit erstellt werden, aber nicht im IL-Code vorhanden sind.

Es ist wichtig zu beachten, dass diese Befehlszeilenargumente in späteren Versionen geändert werden können und werden. Wir sind noch nicht an einem Punkt, an dem wir einen stabilen und unterstützten Satz von Kommandozeilenargumenten für il2cpp.exe haben. Schließlich haben wir eine Liste von zwei Dateien und einem Verzeichnis in der Befehlszeile:

  • "C:\Benutzer\Josh Peterson\Dokumente\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\Assembly-CSharp.dll"
  • "C:\Benutzer\Josh Peterson\Dokumente\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\UnityEngine.UI.dll"
  • "C:\Benutzer\Josh Peterson\Dokumente\IL2CPP Blog Example\Temp\StagingArea\Data\il2cppOutput"

Das Dienstprogramm il2cpp.exe akzeptiert eine Liste aller IL-Assemblies, die es konvertieren soll. In diesem Fall handelt es sich um die Assembly, die mein einfaches MonoBehaviour enthält, Assembly-CSharp.dll, und die GUI Assembly, UnityEngine.UI.dll. Es ist zu beachten, dass hier einige auffällige Baugruppen fehlen. Mein Skript verweist eindeutig auf die UnityEngine.dll, und diese verweist mindestens auf die mscorlib.dll und vielleicht auf andere Assemblies. Wo sind sie? Tatsächlich löst il2cpp.exe diese Assemblies intern auf. Sie können in der Befehlszeile angegeben werden, sind aber nicht notwendig. Unity muss nur die Root-Assemblies (die von keiner anderen Assembly referenziert werden) explizit erwähnen.

Das letzte Argument in der Befehlszeile von il2cpp.exe ist das Verzeichnis, in dem die C++-Ausgabedateien erstellt werden sollen. Wenn Sie neugierig sind, schauen Sie sich die erzeugten Dateien in diesem Verzeichnis an, sie werden Gegenstand des nächsten Beitrags in dieser Reihe sein. Zuvor sollten Sie jedoch in den WebGL-Build-Einstellungen die Option "Development Player" auswählen. Dadurch wird das Kommandozeilenargument --output-format=Compact entfernt und Sie erhalten bessere Typ- und Methodennamen im generierten C++-Code.

Versuchen Sie, verschiedene Optionen in den WebGL- oder iOS-Player-Einstellungen zu ändern. Sie sollten in der Lage sein, verschiedene Kommandozeilenoptionen zu sehen, die an il2cpp.exe übergeben werden, um verschiedene Schritte der Codegenerierung zu ermöglichen. Wenn Sie beispielsweise die Einstellung "Ausnahmen aktivieren" in den WebGL-Player-Einstellungen auf den Wert "Voll" ändern, werden die Argumente --emit-null-checks, --enable-stacktrace und --enable-array-bounds-check zur Befehlszeile von il2cpp.exe hinzugefügt.

Was kann IL2CPP nicht?

Ich möchte auf eine der Herausforderungen hinweisen, die wir mit IL2CPP nicht angenommen haben, und wir könnten nicht glücklicher darüber sein, dass wir sie ignoriert haben. Wir haben nicht versucht, die C#-Standardbibliothek mit IL2CPP neu zu schreiben. Wenn Sie ein Unity-Projekt erstellen, das das IL2CPP-Skript-Backend verwendet, ist der gesamte Code der C#-Standardbibliothek in mscorlib.dll, System.dll usw. genau der gleiche Code, der für das Mono-Skript-Backend verwendet wird.

Wir verlassen uns auf den Code der C#-Standardbibliothek, der den Benutzern bereits bekannt ist und in Unity-Projekten gut getestet wurde. Wenn wir also einen Fehler im Zusammenhang mit IL2CPP untersuchen, können wir ziemlich sicher sein, dass der Fehler entweder im AOT-Compiler oder in der Laufzeitbibliothek liegt, und nirgendwo sonst.

Wie wir IL2CPP entwickeln, testen und ausliefern

Seit der ersten öffentlichen Veröffentlichung von IL2CPP in der Version 4.6.1p5 im Januar haben wir 6 Vollversionen und 7 Patch-Versionen (für die Versionen 4.6 und 5.0 von Unity) veröffentlicht. Wir haben mehr als 100 Fehler behoben, die in den Versionshinweisen aufgeführt sind.

Um diese kontinuierliche Verbesserung zu ermöglichen, entwickeln wir intern nur gegen eine Version des IL2CPP-Codes, die sich auf dem neuesten Stand des Stammzweigs von Unity befindet, der für Alpha- und Beta-Versionen verwendet wird. Kurz vor jeder Veröffentlichung portieren wir die IL2CPP-Änderungen in den jeweiligen Versionszweig, führen unsere Tests durch und überprüfen, ob alle von uns behobenen Fehler in dieser Version korrigiert sind. Unsere QA- und Sustained-Engineering-Teams haben unglaubliche Arbeit geleistet, um die Auslieferung in diesem Tempo zu ermöglichen. Das bedeutet, dass unsere Benutzer nie mehr als eine Woche von den neuesten Korrekturen für IL2CPP-Fehler entfernt sind.

Unsere Benutzergemeinschaft hat sich durch die Einsendung vieler hochwertiger Fehlerberichte als unschätzbar wertvoll erwiesen. Wir sind dankbar für alle Rückmeldungen unserer Nutzer, die uns helfen, IL2CPP kontinuierlich zu verbessern, und wir freuen uns auf weitere Rückmeldungen.

Das Entwicklungsteam, das an IL2CPP arbeitet, hat eine starke "Test-first"-Mentalität. Wir arbeiten oft mit Test Driven Design-Verfahren und fügen selten eine Pull-Anfrage ohne gute Tests zusammen. Diese Strategie eignet sich gut für eine Technologie wie IL2CPP, bei der wir klare Inputs und Outputs haben. Das bedeutet, dass die überwiegende Mehrheit der Fehler, die wir sehen, kein unerwartetes Verhalten sind, sondern eher unerwartete Fälle (z.B. ist es möglich, einen 64-Bit-IntPtr als 32-Bit-Array-Index zu verwenden, wodurch Clang mit einem C++-Compilerfehler scheitert, und echter Code tut dies tatsächlich!) Dieser Unterschied ermöglicht es uns, Fehler schnell und mit einem hohen Maß an Vertrauen zu beheben.

Mit Hilfe unserer Community arbeiten wir hart daran, IL2CPP so stabil und schnell wie möglich zu machen. Übrigens, wenn Sie etwas davon begeistert, wir stellen ein (nur so am Rande).

Mehr dazu in Kürze

Ich fürchte, ich habe hier zu viel Zeit damit verbracht, zukünftige Blogbeiträge anzukündigen. Wir haben eine Menge zu sagen, und es passt einfach nicht alles in einen Beitrag. Beim nächsten Mal werden wir den von il2cpp.exe erzeugten Code untersuchen, um zu sehen, wie Ihr Projekt für den C++-Compiler tatsächlich aussieht.