RAII and platform independent screen cursors

RAII and platform independent screen cursors

Implementation of platform-independent screen pointers for the mouse using Resource Acquisition Is Initialization (RAII)

RAII and platform independent screen cursors

Implementation of platform-independent screen pointers for the mouse using Resource Acquisition Is Initialization (RAII)

Resource Acquisition Is Initialization (RAII) is considered one of the key concepts of C++, and it actually allows us to program more error-free. It is really simple and easy to implement. Translated into German it means resource acquisition is initialization. In this process the usage of any resource is linked to the scope of a variable of a type defined for it. When defining a variable, the resource is requested. When the lifetime of the variable ends because the block has been closed, the resource is released independently. This not only allows us to automate the release of memory, and thus to dispense with a garbage collector hyped in the coffee-based programming languages, but also to control handles, mutexes and other resources.

You simply use the constructors and destructors of this data type and can store necessary values as data elements. This concept cannot be implemented in most other languages. Critics claim, however, that it is not needed in this way, often referring to the garbage collector of a virtual machine. But all resources used by the program must be freed, and if a programmer is overwhelmed with the control of the memory, how can one assume that exactly this programmer can handle mutexes and handles better? And if the solution is as simple as using RAII in this case, why do programmers prefer to use constructions of other languages, in the case of Delphi's C++Builder?

Many RAII types are also predefined and can be used by us. These include all smartpointers, but also the lockguards of the mutexes. Many C++ classes also close resources automatically, for example file streams. 

One goal of this blog should be to explain how platform independent programming works, and more concretely, how easily the two different frameworks VCL and FMX can be used in a program. Unfortunately, Embarcadero lacks this support, and as you could read in the recent c't article "An IDE to enslave them", Embarcadero programmers in particular are becoming dependent on Embarcadero. If so, it could be due to a closed microcosm in which they often find themselves.

Therefore I will explain RAII in this blog post using the example of mouse pointers. And who among us hasn't experienced a function being terminated and the hourglass mouse pointer was still active. Maybe an exception was thrown or we simply made a mistake and forgot it in a hurry. We also have to cache the previous mouse pointer to be able to reset it at the end. The try ... finally construction, which comes from Delphi and is unfortunately often used in Embarcadero examples, is superfluous and often stupid in C++ programs.

Let's start with the VCL and take a look at this class. For the used screen variable and the type TCursor we need the include file <Vcl.Forms.hpp>, in which both were declared. In this example, the constructor and destructor are implemented implicitly, so that a Only Header class is created, which can be used very easily later on, since no dynamic or static libraries are needed for its use. 


#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

In the constructor, the previous mouse cursor is (temporarily) stored in the private variable "old_cursor". Only then is the mouse pointer changed to the hourglass. In the destructor the private variable is then used to restore the previous state. Now we only have to include the header file and define a variable of this type if we need an hourglass mouse pointer, and when the block ends, the screen pointer is reset automatically. Regardless of this, we leave this block on schedule or by an exception.


If we now include this in an application using the Fire Monkey Framework, we will get a lot of errors. The header file alone makes the whole thing fail, but the screen variable we used doesn't exist either, and so the methods for reading and setting the mouse pointer don't exist either. Now we could change all these places and create a new file. But our goal is a common interface and therefore only one file. For this purpose, there is still the way of conditional translation with the preprocessor in C++. With C++17 there is a new and more elegant way with if constexpr. Unfortunately the standard C++17 is not yet supported with the current C++Builder. 


But we still have to go the way we have done so far. Unfortunately, there is no compiler switch that determines which framework is selected in the project file. Therefore we have defined switches in our projects ourselves. For VCL projects we define the switch BUILD_WITH_VCL, for FMX projects we define the switch BUILD_WITH_FMX following the pattern. We use this switch in the project properties.

We can now use this flag to extend the framework dependent parts by means of conditional translation.


#ifndef MyFormH
#define MyFormH

#if !defined BUILD_WITH_VCL && !defined BUILD_WITH_FMX
   #error A framework variant must be selected for this application
#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

Now the source code is a bit more extensive. Especially the FMX variant seems unnecessarily complicated. Here the CursorService is accessed via a Delphi interface to a COM class, because the screen variable no longer exists in this form. But it is still no magic. If this file is now translated in a project with the VCL the previous variant is used, in case of FMX the new one. If neither variant is selected, an error is generated during the translation. So one treatment method looks the same in both cases.


#include <myform.h>

void __fastcall TfrmMain::Button1Click(TObject *Sender) {
   TMyWait wait;
   // to something here ...
   }

Now some may be put off by the conditional translation. But keep in mind that you only do this once, but then you can use it in many projects over and over again without the team members having to delve into the depths of VCL and FMX. Only one team member, or a small team in a larger organization, needs to learn the syntax and pitfalls of the Embarcadero framework. And if a decision is made to switch to a different compiler and thus a different framework, again only that member / team, or a purchased working student, needs to make the adjustments. All others can continue working on the actual business tasks, the source code otherwise remains unchanged. It is also easier for teams to work with different development environments in parallel in one company.


Thus, an approach like the one started with this blog post protects their investments in the future. This is certainly a good argument for the CEO or board of directors of your company, and proof that active marketing is just one thing, marketing with the goal of basing C++ developments. A cleanly written C++ program can still be translated on any platform without major changes, if the architect knows his craft. C++ is a general-purpose, high-performance and standardized programming language, offers a wide range of abstractions, and is available on any platform. It is not owned by any company. And there's nothing, really nothing, in the way of using Embarcadero C++Builder. Especially when the gap in the standard is closed in the fall and C++17 is supported.

Kommentar verfassen

* These fields are required

Kommentare

Keine Kommentare

About the Author

 


Über den Autor

Volker Hillmann

CEO, Developer, Germany, Berlin
Volker Hillmann was born in 1965 and holds a degree in mathematics with a focus on databases and data security. He has been programming in C since 1988. After first touches on a Unix machine with Turbo C 1.5 on PCs. That's how he got to know C++ in 1991 and since then he is programming in different areas with C++. After some experience in the insurance and banking industry, he founded adecc Systemhaus GmbH in 2000, of which he is still the CEO. He is also MVP at embarcadero Germany.

More posts by this author