28 Dezember 2024

Portierung von C#-Code nach C++: Speicherverwaltungsmodelle

Unser Framework, CodePorting.Translator Cs2Cpp, ermöglicht die Veröffentlichung von Bibliotheken, die für die .NET-Plattform entwickelt wurden, in C++. In diesem Artikel werden wir erläutern, wie wir es geschafft haben, die Speichermodelle dieser beiden Sprachen zu vereinheitlichen und den korrekten Betrieb des übersetzten Codes in einer nicht verwalteten Umgebung sicherzustellen.

Sie werden erfahren, welche intelligenten Zeiger wir verwenden und warum wir eigene Implementierungen dafür entwickeln mussten. Darüber hinaus werden wir den Prozess der Vorbereitung von C#-Code für die Portierung aus der Perspektive des Objektlebenszyklusmanagements, einige der Herausforderungen, denen wir begegneten, und die spezifischen Diagnosemethoden, die wir in unserer Arbeit anwenden müssen, erläutern.

Speicherverwaltungsmodell in C#

C#-Code wird in einer verwalteten Umgebung mit Garbage Collection ausgeführt. Für die Zwecke der Übersetzung bedeutet dies in erster Linie, dass ein C#-Programmierer, im Gegensatz zu einem C++-Entwickler, von der Notwendigkeit befreit ist, zugewiesenen Heap-Speicher, der nicht mehr verwendet wird, an das System zurückzugeben. Dies wird vom Garbage Collector (GC) erledigt—einem Bestandteil der CLR-Umgebung, der periodisch prüft, welche Objekte im Programm noch verwendet werden, und diejenigen bereinigt, auf die keine aktiven Verweise mehr bestehen.

Ein aktiver Verweis ist:

  1. Im Stack: lokale Variablen, Methodenargumente;
  2. Im statischen Datenbereich: statische Felder und Eigenschaften;
  3. In einem Objekt, auf das aktive Verweise bestehen.

Der Algorithmus des Garbage Collectors wird üblicherweise als Durchlaufen des Objektgraphen beschrieben, beginnend bei den im Stack und in den statischen Feldern befindlichen Verweisen, und dann Löschen von allem, was in der vorherigen Phase nicht erreicht wurde. Um zu verhindern, dass der Verweisgraph während des GC-Betriebs ungültig wird, wird ein Stop-the-World-Mechanismus implementiert. Diese Beschreibung mag vereinfacht sein, ist jedoch für unsere Zwecke ausreichend.

Im Gegensatz zu intelligenten Zeigern ist der Ansatz der Garbage Collection frei von Problemen mit Kreuz- oder zyklischen Verweisen. Wenn zwei Objekte sich gegenseitig referenzieren, möglicherweise über eine Anzahl von Zwischenobjekten, hindert dies den GC nicht daran, sie zu löschen, wenn die gesamte Gruppe, bekannt als Isolationsinsel, keine aktiven Verweise mehr hat. Folglich haben C#-Programmierer keine Vorbehalte dagegen, Objekte jederzeit und in beliebigen Kombinationen miteinander zu verknüpfen.

Datentypen in C# werden in Referenz- und Werttypen unterteilt. Instanzen von Werttypen befinden sich immer im Stack, im statischen Speicher oder direkt in den Feldern von Strukturen und Objekten. Im Gegensatz dazu werden Instanzen von Referenztypen immer auf dem Heap erstellt, während im Stack, im statischen Speicher und in Feldern nur ihre Verweise (Adressen) gespeichert werden. Werttypen umfassen Strukturen, primitive arithmetische Werte und Verweise. Referenztypen umfassen Klassen und, historisch, Delegaten. Die einzigen Ausnahmen von dieser Regel sind Fälle des Boxings, bei denen eine Struktur oder ein anderer Werttyp für die Verwendung in einem spezifischen Kontext in den Heap kopiert wird.

Der Zeitpunkt der Zerstörung eines Objekts ist nicht definiert—die Sprache garantiert nur, dass er nicht vor dem Entfernen des letzten aktiven Verweises auf das Objekt eintritt. Wenn im konvertierten Code das Objekt sofort beim Löschen des letzten aktiven Verweises zerstört wird, stört dies den Betrieb des Programms nicht.

Ein weiterer wichtiger Punkt bezieht sich auf die Unterstützung generischer Typen und Methoden in C#. C# erlaubt das einmalige Schreiben generischer Typen und deren anschließende Verwendung sowohl mit Referenz- als auch mit Werttypen-Parametern. Wie später gezeigt wird, erweist sich dieser Punkt als wichtig.

C# zu C++ Typabbildungsmodell

Einige Worte darüber, wie wir C#-Typen in C-Typen abbilden. Da das CodePorting.Translator Cs2Cpp Framework für das Portieren von Bibliotheken und nicht von Anwendungen gedacht ist, besteht eine wichtige Anforderung darin, die API des ursprünglichen .NET-Projekts so genau wie möglich zu reproduzieren. Daher konvertieren wir C#-Klassen, -Schnittstellen und -Strukturen in C-Klassen, die die entsprechenden Basistypen erben.

Zum Beispiel wird folgender Code:

interface I1 {}
interface I2 {}
interface I3 : I2 {}
class A {}
class B : A, I1 {}
class C : B, I2 {}
class D : C, I3 {}
class Generic<T> { public T value; }
struct S {}

so übersetzt:

class I1 : public virtual System::Object {};
class I2 : public virtual System::Object {};
class I3 : public virtual I2 {};
class A : public virtual System::Object {};
class B : public A, public virtual I1 {};
class C : public B, public virtual I2 {};
class D : public C, public virtual I3 {};
template <typename T> class Generic { public: T value; };
class S : public System::Object {};

Die Klasse System::Object ist eine Systemklasse, die in der Unterstützungsbibliothek des Übersetzers deklariert ist und außerhalb des konvertierten Projekts liegt. Klassen und Schnittstellen erben virtuell von ihr, um das Diamantproblem zu vermeiden. Strukturen im übersetzten C++-Code erben von System::Object, während sie in C# durch System.ValueType von ihm erben, aber dieses zusätzliche Erben wird zur Optimierung entfernt. Generische Typen und Methoden werden in Template-Klassen und -Methoden übersetzt.

Für Instanzen der übersetzten Typen müssen dieselben Garantien gelten, die auch in C# gegeben wurden. Klasseninstanzen müssen auf dem Heap erstellt werden, und ihre Lebensdauer muss von der Lebensdauer der aktiven Verweise auf sie bestimmt werden. Strukturinstanzen müssen auf dem Stack erstellt werden, außer in Fällen des Boxings. Delegates sind ein spezieller und relativ trivialer Fall, der außerhalb des Umfangs dieses Artikels liegt.

Fortsetzung folgt

Wir haben betrachtet, wie das Speicherverwaltungsmodell in C# den Codekonvertierungsprozess zu C++ beeinflusst. Im nächsten Teil des Artikels werden wir diskutieren, wie die Entscheidung über die Methode zur Verwaltung der Lebensdauer von heap-allozierten Objekten getroffen wurde und wie dieser Ansatz umgesetzt wurde.

Verwandte Nachrichten

Verwandte Videos

In Verbindung stehende Artikel