Resource Acquisition Is Initialization (RAII) gilt als eines der Schlüsselkonzepte von C++, und es ermöglicht uns tatsächlich fehlerfreier zu programmieren. Dabei ist es wirklich einfach und leicht umzusetzen. Ins Deutsche übersetzt bedeutet es Ressourcenbelegung ist Initialisierung. Dabei wird die Verwendung einer beliebigen Ressource an den Gültigkeitsbereich einer Variable eines dafür definierten Types geknüpft. Bei der Variablendefinition wird die Ressource angefordert, endet die Lebensdauer der Variable, weil der Block geschlossen wurde, wird die Ressource selbständig freigegeben. Damit kann nicht nur die Freigabe von Speicher automatisiert, und damit auf einen in den coffee based Programmiersprachen gehypten Garbage Collector verzichtet werden, wir können auch Handles, Mutexe und andere Ressourcen damit kontrollieren.
Dabei bedient man sich einfach der Konstruktoren und Destruktoren dieses Datentyps und kann notwendige Werte als Datenelement speichern. Dieses Konzept kann in den meisten anderen Sprachen so nicht umgesetzt werden. Die Kritiker behaupten allerdings, man würde dieses so nicht benötigen, verweisen hier oft auf den Garbage Collector einer virtuellen Maschine. Aber alle vom Programm verwendeten Ressourcen müssen freigegeben werden, und wenn ein Programmierer mit der Kontrolle des Speichers überfordert ist, wie sollte man annehmen können, dass genau dieser mit Mutexen und Handles besser klar kommt? Und wenn die Lösung so einfach ist, wie in diesem Fall mit Hilfe von RAII, warum setzen Programmierer lieber Konstruktionen anderer Sprachen ein, im Falle des C++Builders von Delphi?
Viele RAII- Typen sind auch schon vordefiniert und können von uns verwendet werden. Dazu zählen zum Beispiel alle Smartpointer, aber auch die Lockguards der Mutexe. Viele C++ Klassen schliessen auch automatisch Ressourcen, zum Beispiel Dateistreams.
Ein Ziel dieses Blogs soll ja sein, zu erklären, wie plattformunabhängiges Programmieren funktioniert, und konkreter, wie einfach die beiden unterschiedlichen Frameworks VCL und FMX in einem Programm verwendet werden können. Leider fehlt diese Unterstützung seitens von Embarcadero, und wie man in der aktuellen c't im Artikel "Eine IDE, sie zu knechten" lesen konnte, gelangen gerade Programmierer von Embarcadero in eine Abhängigkeit. Wenn ja, dann könnte es an einem geschlossenen Mikrokosmos liegen, in dem diese sich oft befinden.
Deshalb werde ich RAII in diesem Blogpost am Beispiel von Mauszeigern erklären. Und wer von uns hat nicht schon mal erlebt, dass eine Funktion beendet wurde, und der Sanduhr- Mauszeiger war noch aktiv. Vielleicht wurde eine Exception geworfen oder wir haben schlichtweg nur einen Fehler gemacht und es in der Eile vergessen. Außerdem müssen wir den bisherigen Mauszeiger zwischenspeichern, um ihn am Ende zurücksetzen zu können. Die von Delphi kommende, und leider in Beispielen von Embarcadero immer wieder verwendete try .. finally - Konstruktion, ist in C++- Programmen überflüssig und oft auch dumm.
Beginnen wir mit der VCL und schauen uns diese Klasse an. Für die verwendete Bildschirmvariable und den Typ TCursor benötigen wir die Include- Datei <Vcl.Forms.hpp>, in der beides deklariert wurde. Bei diesem Beispiel werden der Konstruktor und Destruktor implizit implementiert, so dass eine Only Header Klasse entsteht, die in späterem Verlauf sehr einfach verwendet werden kann, da für die Benutzung keine dynamischen oder statischen Bibliotheken benötigt werden müssen.
#ifndef MyFormH
#define MyFormH
#include <vcl.forms.hpp>
class TMyWait {
private:
TCursor old_cursor;
public:
TMyWait(void) {
old_cursor = Screen->Cursor;
Screen->Cursor = crHourGlass;
}
~TMyWait(void) {
Screen->Cursor = old_cursor;
}
};
#endif
Im Konstruktor wird der bisherige Mauszeiger in der privaten Variable "old_cursor" (zwischen-) gespeichert. Erst dann wird der Mauszeiger auf die Sanduhr umgestellt. Im Destruktor wird dann die private Variable verwendet, um den vorherigen Zustand wieder herzustellen. Nun müssen wir nur die Headerdatei einbinden und eine Variable von diesem Typ definieren, wenn wir einen Sanduhr- Mauszeiger benötigen, und wenn der Block endet, wird der Bildschirmzeiger selbständig zurückgesetzt. Unabhängig davon ab wir diesen Block planmäßig oder durch eine ausgelöste Exception verlassen.
Wenn wir dieses nun in eine Anwendung mit dem Fire Monkey Framework einbinden, werden wir sehr viele Fehler bekommen. Schon die Headerdatei läßt das ganze scheitern, aber es gibt die Screen- Variable auch nicht, die wir verwendet haben existiert nicht, und damit auch nicht die Methoden zum Auslesen und Setzen des Mauszeigers. Nun könnten wir die betreffenden Stellen alle ändern, und eine neue Datei erzeugen. Aber unser Ziel ist ja eine gemeinsame Schnittstelle und damit auch nur eine Datei. Dafür gibt es in C++ immer noch den Weg der bedingten Übersetzung mit dem Präprozessor. Mit C++17 gibt es mit if constexpr einen neuen und eleganteren Weg. Leider wird der Standard C++17 mit dem aktuellen C++Builder noch nicht unterstützt.
Noch müssen wir aber den bisherigen Weg gehen. Leider wird gibt es keinen Compiler- Schalter, der festlegt, welches Framework in der Projektdatei gewählt wurde. Wir haben deshalb in unseren Projekten selber Schalter definiert. Für VCL- Projekte definieren wir den Schalter BUILD_WITH_VCL, für FMX- Projekte dem Muster folgend den Schalter BUILD_WITH_FMX. Diesen benutzen wir den Projekteigenschaften.
Diesen Schalter können wir jetzt verwenden, um mittels bedingter Übersetzung die vom Framework abhängigen Teile zu weitern.
#ifndef MyFormH
#define MyFormH
#if !defined BUILD_WITH_VCL && !defined BUILD_WITH_FMX
#error Für diese Anwendung muss eine Framework- Variante ausgewählt werden
#endif
#if defined BUILD_WITH_VCL
#include <vcl.forms.hpp>
#endif
#if defined BUILD_WITH_FMX
#include <fmx.forms.hpp>
#endif
class TMyWait {
private:
TCursor old_cursor;
public:
TMyWait(void) {
#if defined BUILD_WITH_VCL
old_cursor = Screen->Cursor;
Screen->Cursor = crHourGlass;
#else
_di_IFMXCursorService cs;
if(TPlatformServices::Current->SupportsPlatformService(__uuidof(IFMXCursorService), (void*)&cs)) {
old_cursor = cs->GetCursor();
cs->SetCursor(crHourGlass);
}
#endif
}
~TMyWait(void) {
#if defined BUILD_WITH_VCL
Screen->Cursor = old_cursor;
#else
_di_IFMXCursorService cs;
if(TPlatformServices::Current->SupportsPlatformService(__uuidof(IFMXCursorService), (void*)&cs)) {
cs->SetCursor(old_cursor);
}
#endif
}
};
#endif
Nun ist der Quelltext etwas umfangreicher. Speziell erscheint die FMX- Variante unnötig kompliziert. Hier wird via einem Delphi- Interface auf eine COM- Klasse auf den CursorService zugegriffen, da die Screen- Variable in dieser Form nicht mehr existiert. Aber es ist immer noch keine Magie. Wird diese Datei nun in einem Projekt mit der VCL übersetzt wird die bisherige Variante verwendet, im Falle von FMX die neue. Wenn keine der beiden Varianten gewählt ist, wird ein Fehler beim Übersetzen erzeugt. Damit sieht eine Behandlungsmethode in beiden Fällen gleich aus.
#include <myform.h>
void __fastcall TfrmMain::Button1Click(TObject *Sender) {
TMyWait wait;
// to something here ...
}
Nun sind vielleicht einige abgeschreckt durch die bedingte Übersetzung. Aber dabei muss man bedenken, dass man dieses nur einmal macht, dann aber in vielen Projekten immer wieder einsetzen kann, ohne dass die Teammitglieder in die Tiefen von VCL und FMX eintauchen müssen. Nur ein Teammitglied, oder ein kleines Team in einem größeren Unternehmen, muss sich mit der Syntax und Fallstricken des Embarcadero Frameworks beschäftigen. Und sollte eine Entscheidung zum Wechsel auf einen anderen Compiler und damit auch ein anderes Framework erfolgen, muss wiederum nur dieses Mitglied / Team, oder ein zugekaufter Werkstudent, die Anpassungen vornehmen. Alle anderen können an den eigentlichen Businessaufgaben weiterarbeiten, der Quellcode bleibt ansonsten unverändert. Auch ist es einfacher, das Teams mit verschiedenen Entwicklungsumgebungen parallel in einem Unternehmen arbeiten.
Damit schützt ein Ansatz, wie er mit diesem Blogpost begonnen wurde, ihre Investitionen in der Zukunft. Das ist sicher ein gutes Argument für den Geschäftsführer oder Vorstand ihrer Firma, und ein Beleg, dass das aktive Marketing eben nur eins ist, Marketing mit dem Ziel C++ Entwicklungen zu bashen. Ein sauber geschriebenes C++ Programm ist auch heute noch ohne große Veränderungen auf jeder Plattform übersetzbar, wenn der Architekt sein Handwerk versteht. C++ ist eine allgemein einsetzbare hochleistungsfähige und standardisierte Programmiersprache, bieten ein breites Spektrum an Abstraktionen, und ist auf jeder Plattform verfügbar. Sie gehört keinem Unternehmen. Und es spricht gar nichts, aber auch wirklich nichts gegen den Einsatz des Embarcadero C++Builders. Speziell, wenn im Herbst die Lücke im Standard geschlossen und C++17 unterstützt wird.
Keine Kommentare
Dieses Seminar richtet sich primär an Einsteiger, die über Programmierkenntnisse, vorzugsweise in C++ verfügen und auf den C++Builder wechseln wollen. Es ist aber auch für Umsteiger von einer wesentlich älteren Version geeignet.
3 Tage
8 Teilnehmer
Dieses Seminar richtet sich an alle Entwickler, die ihre bestehenden Anwendungen vom klassischen C++- Compiler zum neuen, auf Clang basierenden Compiler im C++- Builder umstellen wollen.
3 Tage
8 Teilnehmer
In diesem Seminar erhalten Sie eine kompakte Einführung in die jeweils aktuelle Version des C++Builder (aktuell 10.3.3). Der C++Builder ist ein plattformübergreifendes Entwicklungstool, mit der Sie performante, native Anwendungen für die Zielplattformen Windows (32bit, 64bit), Mac OS (32bit), iOS und Android entwickeln kann.
5 Tage
8 Teilnehmer
Wie der Titel der Schulung sagt, geht es hier um die Entwicklung von nativen Anwendungen für mobile Endgeräte (App) mit dem C++Builder (aktuell 10.3.3).
2 Tage
8 Teilnehmer
Dieser Kurs richtet sich an C++ Entwickler, die den C++Builder nutzen und ihre Kenntnisse vertiefen wollen. Dabei geht es neben der Verwendung der Komponenten und den Eigenschaften der Sprache C++, besonders auch darum, beides vernünftig zu verbinden.
3 Tage
8 Teilnehmer
Dieses Seminar richtet sich an C++ Programmierer, die den Zugriff auf Datenbanken in ihren Anwendungen erweitern wollen. In diesem Seminar werden die wichtigsten Komponenten des C++ Builders für Datenbankzugriff erläutert, aber es wird auch gezeigt, wie sie diese in eine saubere Anwendungsarchitektur integrieren können.
3 Tage
8 Teilnehmer
Dieses Seminar richtet sich an C++ Programmierer, die Erfahrungen mit dem den Datenbankkomponenten des C++ Builders gesammelt haben, und nun skalierbare Datenbankanwendungen entwickeln wollen. In diesem Seminar wird die Bedeutung von ANSI C++ - Schnittstellen zwischen der Businesslogik einer Anwendung und der Datenbank gezeigt.
3 Tage
6 Teilnehmer
Dieser Kurs richtet sich an Softwareentwickler, die die Elemente der Programmiersprache C als Basis für einen späteren Einstieg in die objektorientierte Programmierung mit C++ lernen wollen.
3 Tage
8 Teilnehmer
Dieses Seminar richtigt sich an die Programmierer, die von C nach C++ umsteigen wollen. Es werden die wichtigsten Erweiterungen der Sprache C++, und die Unterschiede zu C ausführlich besprochen.
3 Tage
5 Teilnehmer
Dieses Seminar richtigt sich an die Programmierer, die von C nach C++ umsteigen wollen. Es werden die wichtigsten Erweiterungen der Sprache C++, und die Unterschiede zu C ausführlich besprochen.
5 Tage
8 Teilnehmer
Wir wurden oft gefragt, ob wir eine Schulung "mit allem" machen könnten. Ein größeres Beispiel, beginnend mit einer Idee, daraus eine Architektur entwickeln, und dann unabhängig in C++ implementieren. Die Erweiterungen nur verwenden, ohne das diese unsere Struktur und Architektur bestimmen.Und wenn es geht, auch noch mit Entwicklung eines Zugriffs für mobile Anwendungen verbinden.
5 Tage
8 Teilnehmer
In diesem Seminar lernen Sie, welche Open Source- Tools Sie für Ihre tägliche Arbeit mit dem aktuellen C++ Builder 10.3.2 nutzen können. Dabei geht es um Lösungen zu den Themen Dokumentieren, Metriken, Codeanalyse und Codeverwaltung.
2 Tage
6 Teilnehmer