Mittwoch, 2. Mai 2018

RAII - Mauszeiger für VCL + FMX

RAII und plattformunabhängige Bildschirmcursor

Umsetzung von plattformunabhängige Bildschirmzeigern für die Maus mit Hilfe von Resource Acquisition Is Initialization (RAII)


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.
;