Von Managed C++ nach C++/CLI


Thomas Wölfer
Thomas Wölfer

21. Januar 2009


In VS 2008 ist die Syntax von Managed C++ bereits deprecated - höchste Zeit, auf C++/CLI umzustellen. Die ausführliche Referenz dazu befindet sich hier: C++/CLI Migration Primer - ich denke für die meinsten Fälle kommt man aber mit einer Kurzfassung aus:

Zeiger
Ein Vorteil von C++/CLI über die "managed Extensions" ist die Tatsache, das viele Dinge viel schöner hingeschrieben werden können - und auch besser zu erkennen sind. Das geht mit Zeigern los: Ein Zeiger in den Managed Heap wird nun mit ^ statt mit * markiert:  Faselwurst^ pWurst zeigt also auf eine Instanz vom Typ Faselwurst, die sich im Managed Heap befindet.

Typen
Bisher wurde eine Klasse als gc__ class TypeName definiert - jetzt heist es statt dessen "ref class TypeName".

Arrays
Bisher war ein Array aus Instanzen im Managed Heap ein gc__[] variablenName TypeName. Das heist nun array<Type>(Dimension). Ein Zeiger auf so ein Array ist also ein array<Type>^ varName.

Properties
Die neue Syntax für Properties ähnelt doch sehr stark der von C#:
property int Zahl
{
    int get()
    {
    }
    void set( int v)
    {
        member = v;
    }
}

Interface-Implementierungen
Implementiert eine Klasse ein Interface, dann müssen die implementierten Member nun "virtual" sein.

Die bisher aufgeführten Änderungen sind weiter nicht dramatisch, sonfern man weiss, wie man die Dinge hinschreiben muss: Wirklich falsch machen kann man eigentlich nichts, denn der Compiler bewirft einen schon mit passenden Fehlermeldungen. Das ist in einem besonderen Fall aber nicht so, und dabei geht es um die Destruktoren. Das Problem: Der Compiler meckert hier nichts an, denn man kann zwar weiterhin die "alte" Destruktor-Notation verwenden: Dummerweise hat sich aber deren Semantik verändert. Macht man die Sache aber falsch und reagiert nicht auf die Semantik-Änderung, gibt es Speicher-Lecks im nativen Heap. Das passiert, wenn man eine C++/CLI Instanz hat, die als Member Zeiger auf den nativen Heap hat. Die Instanz an sich wird bei Notwendigkeit brav vom GC eingesammelt und verworfen. Dabei wird der normale Destruktor aber nicht unbedingt aufgerufen - zumindest nicht, wenn man nicht weiter tätig wird. Resultat: Leck. Das war früher nicht so - daher muss man auf diese Problematik gesondert achten. Die ausführliche Referenz zum Thema findet sich hier.

Im Wesentlichen läuft es aber auf folgendes hinaus: Damit die Destruktoren der C++/CLI Typen "richtig" aufgerufen werden, braucht man immer ein Konstrukt dieser Art:

ref class MyType
{
private:
   int* pNative;

// ..... weitere implementierungs-Details in deren Zuge pNative auf allozierten nativen Speicher zeigt.

public:
   // Das wird im IL ein Dispose()
   ~MyType() { Destruct(); }
   // Das wird im IL ein Finalizer.
   !MyType() { Destruct(); }

private:
   void Destruct()
   {
      if( pNative != 0)
      {
           delete pNative;
           pNative = 0;
      }
   }
};

Nur als Hinweis: Das ist so nicht unbedingt die effizienteste Methode, die Sache zu regeln - daher ist es u.U. ganz sinnvoll den oben verlinkten Artikel doch einmal genauer zu studieren.