C++/CLI

C++/CLI ist eine von Microsoft entwickelte Variante der Programmiersprache C++, die den Zugriff auf die virtuelle Laufzeitumgebung des .Net-Frameworks mit Hilfe von speziell darauf zugeschnittenen Spracherweiterungen ermöglicht.

C++/CLI erfüllt die ebenfalls von Microsoft entwickelte Spezifikation namens Common Language Infrastructure (CLI) zur Sprach- und Plattform-neutralen Entwicklung und Ausführung von .NET-Anwendungen. Programme, die in C++/CLI geschrieben sind, können vom Compiler in CIL übersetzt und auf der virtuellen Maschine der .NET-Plattform betrieben werden.

Seit Dezember 2005 liegt ein offiziell von der Ecma ratifizierter Standard für C++/CLI vor.

Microsoft Visual Studio ab Version 2005 und das Compiler-Frontend der Edison Design Group bieten eine Implementierung von C++/CLI an.[1]

Sinn und Zweck der Erweiterungen

Ziele bei der Entwicklung von C++/CLI waren:

  • Schaffung einer eleganten Syntax, die gut zum bisherigen C++ passt. Wer bereits mit C++ vertraut ist, soll die Spracherweiterungen als möglichst natürlich empfinden.
  • Komfortable Unterstützung von Besonderheiten der CLI wie Eigenschaften, Ereignisse, generische Typen, automatische Speicherbereinigung, Referenzklassen usw.
  • Gute Unterstützung von Sprachmitteln, die im bisherigen C++ verbreitet sind, wie etwa Templates oder deterministische Deinitialisierungen, und zwar für alle Typen, einschließlich der neuartigen CLI-Klassen.
  • Kompatibilität mit bestehenden C++-Programmen durch das Einbringen von fast ausschließlich reinen Erweiterungen gegenüber ISO-C++.

Unterschiede zu Managed C++

C++/CLI ist das Ergebnis einer grundlegenden Überarbeitung von Managed C++, der ersten Version von C++ mit Spracherweiterungen für die .NET-Plattform. Managed C++ litt unter Akzeptanzproblemen, weil viele Programmierer die Syntaxerweiterungen als schwer lesbar empfanden. Beispielsweise enthielt die Syntax Bestandteile, die nicht eindeutig erkennen ließen, ob verwaltete oder nichtverwaltete Objekte erzeugt werden.[2]

Auch wirkten die mit Managed C++ eingeführten Syntaxelemente für viele Programmierer behelfsmäßig in die Sprache integriert. So waren z. B. viele Schlüsselwörter eingeführt worden, die mit zwei Unterstrichen beginnen. Zwar ist dies bei Spracherweiterungen gegenüber Standard-C++ üblich, die große Anzahl solcher Schlüsselwörter, sowie deren starke Durchdringung in Programmen, die von den Erweiterungen Gebrauch machten, wirkten jedoch störend auf das Gesamtbild der Quelltexte.

Beispiele:

Managed C++C++/CLI
__gc __interfaceinterface class
Console::WriteLine(S"{0}", __box(15));Console::WriteLine("{0}", 15);
int f()__gc[]; // Deklarationarray<int>^ f(); // Deklaration 1)
Object* A __gc[] = { __box(41), __box(42) };array<Object^>^ A = { 41, 42 };

1) Deklaration einer Funktion f, die eine CLI-Reihung (array) zurückgibt.

Im Unterschied zu Managed C++ wird die Destruktor-Syntax ~T() nicht mehr auf den Finalisierer abgebildet. Destruktor und Finalisierer werden in C++/CLI unterschieden; der Finalisierer hat jetzt die Syntax !T(). Der Destruktor ist außerdem identisch mit der Funktion Dispose (dies wurde durch technische Änderungen an der CLR ermöglicht).

Weitere Neuerungen

Weitere Neuerungen gegenüber ISO-C++ sind: verbesserte Aufzählungsklassen (enum class), Delegaten, Verpacken (boxing), Schnittstellenklassen, versiegelte Klassen, Attribute usw.

Objektzeiger

Die augenfälligste Neuerung ist die Syntax ^ für Objektzeiger (manchmal auch Handles genannt). Beispiel:

T^ whole_object_pointer = gcnew T(a, b);

Dabei ist gcnew ein Operator zur Allokation von Objekten, die von der automatischen Speicherbereinigung verwaltet werden.

Im Vergleich dazu die herkömmliche Syntax für Zeiger:

T* plain_old_pointer = new T(a, b);

Deinitialisierung und Speicherfreigabe

Anders als bei gewöhnlichen Zeigern wird beim Löschen von Handles mittels delete zwar der Destruktor aufgerufen, nicht aber der Speicher freigegeben. Stattdessen wird der vom Objekt belegte Speicher durch die automatische Speicherbereinigung an das System zurückgegeben.

Im Unterschied zu anderen Sprachen mit automatischer Speicherbereinigung (z. B. C# oder Java) wird hier also die problematische Zusammenfassung der Verwaltung von Speicher und anderen Ressourcen voneinander getrennt: Speicher und andere Ressourcen werden nicht mehr zusammen mit Hilfe der Speicherbereinigung freigegeben (also keine deterministische Deinitialisierung); siehe Finalisierung.

Als automatische Variablen anlegbare CLI-Objekte

Eine weitere technische Neuerung und einer der wichtigsten Unterschiede zu anderen Sprachen mit automatischer Speicherbereinigung sind die als automatische Variablen (d. h. auf dem Stack) anlegbaren CLI-Objekte. Die Lebensdauer von automatischen Variablen endet in dem Augenblick, in welchem sie ihren Gültigkeitsbereich verlassen.

Im Zusammenspiel mit den neuartigen Objektzeigern bleiben in C++ dadurch häufig angewandte Programmiertechniken wie RAII (Abkürzung für engl. resource acquisition is initialization) auch für die mit der automatischen Speicherbereinigung verwalteten CLI-Objekte möglich. Fehleranfällige Kodiertechniken, wie sie aus anderen Programmiersprachen bekannt sind, lassen sich damit vermeiden.

Dazu ein Beispiel in C++/CLI:

void Uebertragung()
{
  MessageQueue source("server\\sourceQueue");
  String^ mqname = safe_cast<String^>(source.Receive().Body);

  MessageQueue dest1("server\\" + mqname), dest2("backup\\" + mqname);
  Message^ message = source.Receive();
  dest1.Send(message);
  dest2.Send(message);
}

Beim Verlassen der Funktion Uebertragung (mit return oder beim Auftreten einer Ausnahme) rufen Objekte implizit ihre Funktion Dispose auf, und zwar in umgekehrter Reihenfolge zu ihrer Konstruktion. Im obigen Beispiel also wird zuerst der Destruktor von dest2 aufgerufen, dann der von dest1 und zuletzt der von source, da diese Objekte in der Reihenfolge source, dest1, dest2 konstruiert wurden.

Wenn ein automatisches Objekt seinen Gültigkeitsbereich verlässt, oder beim Löschen mit delete wird sein Destruktor aufgerufen. Der Compiler unterdrückt dann den Aufruf der normalerweise von der automatischen Speicherverwaltung angestoßenen Finalisierungsfunktion.

Der Wegfall von Finalisierungsfunktionen kann sich insgesamt positiv auf die Ausführungsgeschwindigkeit auswirken, hilft aber noch andere Probleme zu vermeiden; zu Problemen bei Verwendung der Finalisierungsfunktion siehe Finalisierung.

Im Unterschied zu C++/CLI muss beispielsweise in Java oder C# zur Ressourcenfreigabe eine entsprechende Funktion (in C# Dispose, in Java meist close) immer explizit aufgerufen werden. Beide Sprachen haben daher spezielle Syntaxkonstrukte entwickelt, mit denen solche Aufrufe sichergestellt werden sollen: In C# sind das die sogenannten using-Blöcke, in Java das try-with-resources-Konstrukt. Solche Blöcke nehmen dem Programmierer zwar die Sicherstellung von Dispose-Aufrufen beim Verlassen des Blocks ab, müssen aber immer mitangegeben werden und sind daher in dieser Hinsicht immer noch fehleranfälliger als die deterministische Deinitialisierung von C++/CLI.

Vergleich mit anderen .NET-Sprachen

Eine Besonderheit von C++/CLI ist die Mischbarkeit von Code, der auf der virtuellen Maschine läuft, und Code, der direkt auf der CPU ausgeführt wird. Beide Arten von Programmcode können in einer einzigen Programmdatei zusammengestellt werden.[3] Mit dieser Möglichkeit nimmt C++/CLI bislang eine Sonderstellung unter den .NET-Sprachen ein.

Weblinks

Einzelnachweise

  1. C++ Front End. (PDF; 529 kB) Internal Documentation (excerpt); Version 6.5. In: edg.com. Edison Design Group, 21. Juni 2023, abgerufen am 8. August 2023 (englisch).
  2. Kenny Kerr: C++: The Most Powerful Language for .NET Framework. In: learn.microsoft.com. Microsoft, Juli 2004, abgerufen am 21. Oktober 2010 (englisch).
  3. Tyler Whitney et al.: Mixed (native and managed) assemblies. In: learn.microsoft.com. Microsoft, 3. August 2021, abgerufen am 8. August 2023 (englisch).