Von C# nach C++: Wie wir die automatisierte Projektumwandlung realisiert haben – Teil 2

Entwicklung

Das Design und die Entwicklung des C#-zu-C+±Codeübersetzers wurden ausschließlich von CodePorting durchgeführt. Es erforderte viele Untersuchungen, die Anwendung mehrerer Ansätze und Tests, die sich je nach Speichermodell und anderen Aspekten unterschieden. Am Ende wurden zwei Lösungen ausgewählt. Eine davon wird derzeit für die C++-Veröffentlichungen von Aspose-Produkten verwendet.

Technologien

Nun ist es an der Zeit, die Technologien zu erklären, die wir im Codeübersetzer verwenden. Der Übersetzer ist eine Konsolenanwendung, die in C# geschrieben ist und sich leicht in Skripte einbetten lässt, die typische Sequenzen wie Übersetzen-Kompilieren-Testen ausführen. Es gibt auch eine GUI-Komponente, mit der Sie dasselbe tun können, indem Sie auf die Schaltflächen klicken.

Die Syntaxanalyse wird von der NRefactory-Bibliothek in der veralteten Generation des Übersetzers und von Roslyn in der neuen durchgeführt.

Der Übersetzer verwendet mehrere AST-Baum-Durchläufe, um Informationen zu sammeln und C+±Code zu generieren. Für C+±Code wird keine AST-Repräsentation erstellt, stattdessen behandeln wir den Ausgabe-Code in reiner Textform.

Es gibt viele Fälle, in denen zusätzliche Informationen erforderlich sind, um den Übersetzer zu feinabzustimmen. Diese Informationen werden über Optionen und Attribute übergeben. Optionen werden auf das gesamte Projekt angewendet. Normalerweise werden sie verwendet, um den Namen des Klassenexportmakros oder die in C# verwendeten bedingten Symbole beim Parsen des Codes anzugeben. Attribute werden auf die Typen und Entitäten angewendet und bieten spezifische Informationen für sie, z. B.: Markieren, welche Klassenmitglieder const oder mutable Qualifikatoren im übersetzten Code erfordern oder welche Entitäten von der Übersetzung ausgeschlossen werden sollen.

C#-Klassen und -Strukturen werden in C+±Klassen umgewandelt. Ihre Mitglieder und der Quellcode werden in engste Analoga umgewandelt. Generische Typen und Methoden werden auf C++-Vorlagen abgebildet. C#-Referenzen werden in intelligente Zeiger (shared oder weak) übersetzt. Referenzklassen sind in der Bibliothek definiert. Weitere interne Details des Codeübersetzers werden in einem separaten Artikel beschrieben.

Das aus C# nach C++ übersetzte Projekt hängt also von unserer Bibliothek anstelle von .NET-Bibliotheken ab:

C# to C++

Um die Bibliothek für den Codeübersetzer und die übersetzten Projekte zu erstellen, verwenden wir Cmake. Derzeit unterstützen wir die Compiler VS 2017 und 2019 (Windows), GCC und Clang (Linux).

Wie bereits erwähnt, handelt es sich bei den meisten unserer .NET-Implementierungen um dünne Adapter über Drittanbieterbibliotheken, darunter:

  • Skia — Grafikunterstützung.
  • Botan — Verschlüsselungsfunktionen.
  • ICU — Zeichenfolgen, Codepages und Kultursupport.
  • Libxml2 — XML-Operationen.
  • PCRE2 — Unterstützung für reguläre Ausdrücke.
  • zlib — Komprimierungsfunktionen.
  • Boost — verschiedene Zwecke.
  • Einige andere Bibliotheken.

Sowohl der Übersetzer als auch die Bibliothek sind mit vielen Tests abgedeckt. Die Bibliothekstests verwenden das GoogleTest-Framework. Die Übersetzertests sind größtenteils in NUnit/xUnit geschrieben und in mehrere Kategorien unterteilt, die sicherstellen, dass:

  • Die Ausgabe des Übersetzers auf bestimmten Eingabedaten mit dem Ziel übereinstimmt.
  • Die Ausgabe der übersetzten Programme mit dem Ziel übereinstimmt.
  • NUnit/xUnit-Tests aus den Eingabeprojekten in GoogleTest-Tests übersetzt werden und bestehen.
  • Die API der übersetzten Projekte in C++ einwandfrei funktioniert.
  • Übersetzeroptionen und -attribute wie erwartet funktionieren.

Wir verwenden GitLab als Versionskontrollsystem. Für die kontinuierliche Integration verwenden wir Jenkins. Die übersetzten Produkte sind als NuGet-Pakete und herunterladbare Archive verfügbar.

Probleme

Während der Arbeit an diesem Projekt sind uns viele verschiedene Probleme begegnet. Einige davon waren erwartet, andere wurden auf dem Weg aufgedeckt:

  1. Unterschiede im Typsystem zwischen .NET und C++.
    C++ hat keine direkte Entsprechung für den Object-Typ, und die meisten Bibliotheksklassen verfügen nicht über RTTI (Run-Time Type Information). Dadurch ist es unmöglich, .NET-Typen direkt auf STL-Typen abzubilden.
  2. Die Übersetzungsalgorithmen sind komplex.
    Es gibt viele subtile Nuancen, die im übersetzten Code berücksichtigt werden müssen. Zum Beispiel hat C# eine definierte Reihenfolge für die Auswertung der Methodenargumente, während in C++ hier undefiniertes Verhalten (UB) auftreten kann.
  3. Fehlersuche ist schwierig.
    Das Debuggen des übersetzten Codes erfordert spezifische Fähigkeiten. Nuancen wie die oben beschriebene können sich stark auf das Verhalten eines Programms auswirken und schwer zu erklärende Fehler verursachen. Andererseits können sie leicht zu versteckten Fehlern führen, die lange Zeit unentdeckt bleiben.
  4. Unterschiedliche Speicherverwaltungssysteme.
    C++ verfügt nicht über eine automatische Speicherbereinigung (Garbage Collection). Daher sind mehr Ressourcen erforderlich, um den übersetzten Code so zu gestalten, dass er sich wie der ursprüngliche verhält.
  5. Disziplin ist für C#-Entwickler erforderlich.
    C#-Entwickler müssen sich an die Einschränkungen gewöhnen, die durch den Übersetzungsprozess des Codes verursacht werden. Die Gründe für diese Einschränkungen sind vielfältig:
    • Die Sprachversion muss vom Syntaxanalysator des Übersetzers unterstützt werden.
    • Codekonstrukte, die vom Übersetzer nicht unterstützt werden, sind verboten (z. B. yield).
    • Der Code-Stil ist durch die Struktur des übersetzten Codes begrenzt (z. B. muss jedes Referenzfeld eindeutig entweder eine schwache Referenz oder eine gemeinsame Referenz sein, während dies für beliebigen C#-Code nicht unbedingt der Fall ist).
    • Die C+±Sprache legt ihre eigenen Beschränkungen fest (z. B. werden in C# statische Variablen nicht gelöscht, bevor alle Vordergrundthreads beendet sind, während dies in C++ nicht der Fall ist).
  6. Eine große Menge Arbeit.
    Der Teil der .NET-Bibliothek, der von unseren Produkten verwendet wird, ist groß genug, und es dauert viel Zeit, alle Klassen und Methoden zu implementieren.
  7. Besondere Anforderungen für Entwickler.
    Die Notwendigkeit, tief in die komplizierten Plattform-Internas einzutauchen und mit zwei oder mehr Programmiersprachen zu arbeiten, begrenzt die Anzahl der verfügbaren Kandidaten. Andererseits finden Entwickler, die sich für Compilertheorie oder andere exotische Disziplinen interessieren, leicht ihren Platz im Projekt.
  8. Die Fragilität des Systems.
    Obwohl wir Tausende von Tests und Millionen von Codezeilen haben, um den Übersetzer zu testen, stoßen wir manchmal auf Probleme, wenn Änderungen, die zur Behebung der Kompilierung eines Projekts vorgenommen wurden, das andere Projekt beeinträchtigen. Dies kann beispielsweise bei seltenen Syntaxkonstrukten und spezifischen Code-Stilen in Projekten auftreten.
  9. Hohe Einstiegshürden.
    Die meisten Aufgaben im Code-Übersetzer-Projekt erfordern eine gründliche Analyse. Aufgrund der Vielzahl von Teilsystemen und Szenarien erfordert jede neue Aufgabe, sich lange Zeit mit neuen Aspekten des Projekts vertraut zu machen.
  10. Probleme beim Schutz des geistigen Eigentums.
    Obwohl es viele fertige Lösungen gibt, um C#-Code effektiv zu verschleiern, bleibt in C++ viele Informationen in den Klassenüberschriften erhalten. Darüber hinaus können einige Definitionen nicht aus öffentlichen Überschriften entfernt werden, ohne Konsequenzen zu haben. Das Zuordnen von generischen Klassen und Methoden zu Vorlagen schafft eine weitere Schwachstelle, da es die Algorithmen offenlegt.

Trotz all dem ist das Code-Übersetzer-Projekt aus technischer Sicht sehr interessant, und seine akademische Komplexität zwingt uns, ständig etwas Neues zu lernen.

Fazit

Während der Arbeit am Code-Übersetzerprojekt ist es uns gelungen, ein System zu implementieren, das eine interessante akademische Aufgabe der Code-Übersetzung löst. Wir haben monatliche Veröffentlichungen von Aspose-Bibliotheken für die Sprache organisiert, für die sie ursprünglich nicht vorgesehen waren.

Es ist geplant, weitere Artikel über den Code-Übersetzer zu veröffentlichen. Der nächste Artikel wird den Konvertierungsprozess im Detail erläutern, einschließlich der Zuordnung konkreter C#-Konstruktionen auf C+±Äquivalente. Ein weiterer Artikel wird das Modell zur Speicherverwaltung behandeln.

Wir werden unser Bestes tun, um auf gestellte Fragen zu antworten. Wenn die Leser an anderen Aspekten der Code-Übersetzerentwicklung interessiert sind, können wir in Erwägung ziehen, weitere Artikel dazu zu schreiben.

In Verbindung stehende Artikel