Und ggf. gibt es hier noch einen weiteren Textabschnitt, der im mobilen Bereich aber nicht zu sehen ist.
Wir alle kennen das Problem, dass die Delphi- Stringtypen (UnicodeString oder kurz String, AnsiString) nicht unbedingt zu C++ passen, und wir immer wieder die Methode c_str() bemühen müssen. Das ist nicht nur nervig, es kann auch zu Fehlern führen. Außerdem wollen wir oft den Inhalt einen Strings in einem anderen Datentyp haben, oder dieses umgekehrt in einen Delphi- String umwandeln, zum Beispiel um diesen in einem Edit- Feld eines Formulars anzuzeigen.
Dazu kommt, dass dank der nicht unbedingt nachvollziehbaren Entscheidung von Embarcadero unter iOS und Android weiter auf ein 16 bit Zeichentyp zu setzen, anstatt den systemspezifischen wchar_t Typ zu nutzen, der bei den unixoiden Betriebssystem ja ein 32 bit Datentyp ist. Damit kann es passieren, dass wir es in einem C++- Programm unter Android plötzlich mit 3 nicht unbedingt kompatiblen Datentypen zu haben. Dabei sollte es im Businessteil ihrer Anwendung nur die Entscheidung zwischen nationalen Zeichensatz (8 bit, char, string) geben, oder dem internationalen (wchar_t, wstring). Außerdem sind die Indizes für die Delphi Stringtypen auf den Desktop- Betriebssystemen 1 basierend, auf den mobilen Betriebssystemen aber an C und C++ orientiert 0 basierend. Auch dieses kann zu Problemen und notwendigen Anpassungen führen. Alles das wäre unter C++ einfach schlichtweg unmöglich, da hier ein Industriestandard die Investitionen des Entwicklers schützt.
Ich möchte hier eine Headerdatei für die weitere Verwendung in diesem Blog erzeugen, und dieses Problem mit Hilfe von C++- templates und der Spezialisierung dieser für spezielle Datentypen.
Vielleicht dieses vorneweg. Ich war einmal, so beginnen nicht nur Märchen. Oft fangen wir als Unternehmensberater auch mit solchen Sätzen an. Ich war einmal bei einem Kunden zu einer Inhouse- Schulung. Hier arbeiten mehrere Entwickler seit einigen Jahren an einem Programm aus dem Energiesektor. Während der Schulung wurde auch das Thema behandelt, dass für die Embarcadero- Stringtypen die Konstruktoren für die Standard C++ Datentypen fehlen, beziehungsweise die Konvertierungsoperatoren für die umgekehrte Richtung nicht umgesetzt sind. Leider sind gerade die Operatoren nicht über argument dependent name lookup (ADL) nicht ergänzbar. In diesem Fall könnte der leistungsfähige C++- Compiler dieses Problem für uns lösen, und das ohne einen Fehler. Nun ist dieses für einen Delphi- Programmierer sicher auch völlig uninteressant, er dürfte nicht mal verstehen, warum ich das überhaupt kritisiere. Und damit ist vielleicht auch zu erklären, warum es fehlt. Nun ließ einer der Entwickler eine grep- Suche über den aktuellen Quelltextstand laufen, und im Laufe der Jahre hatten sich sagenhaft 2,5 Millionen Aufrufe der Methode c_str() in den Quelltexten angehäuft. Sollten jetzt Änderungen notwendig werden, zum Beispiel bei einer Migration auf ein unixoides Betriebssystem, kann sich jeder das Ausmaß der notwendigen Arbeiten vorstellen.
Ich möchte dieses Problem für die zukünftigen Beispiele in diesem Blog mit den templates __ToVCL und __FromVCL lösen. Auch hier nutze ich die vordefinierten Bedingungen für VCL bzw. FMX, da nur bei der Nutzung eines dieser Frameworks die Nutzung von Delphi- Typen sinnvoll erscheint. Danach folgen die beiden Headerdateien für Delphi- Stringtypen <system.hpp> und C++- Stringtypen <string>.
Nun braucht man für die meisten Datentypen keine internationalen Zeichen, also nutze ich für die Implementierung des templates auch nur eine nationale Variante und nutze die in der Delphi- Klassenbibltiothek automatische Konvertierung von UnicodeString zu AnsiString. Für die notwendige Typumwandlung werde sich das template boost::lexical_cast verwenden, dass eine Zeichenkette in einen beliebigen C++- Typ umwandeln kann. Dieses funktioniert auch für von ihnen selbst definierte Datentypen, wenn sie die Stream- Operatoren implementiert haben.
#ifndef MyDelphiHelperH
#define MyDelphiHelperH
#if !defined BUILD_WITH_VCL && !defined BUILD_WITH_FMX
#error Für diese Anwendung muss eine Framework- Variante ausgewählt werden
#endif
#include <system.hpp>
#include <string>
#include <sstream>
#include <boost/lexical_cast.hpp>
template <typename ty>
inline String __ToVCL(ty const& val) {
std::ostringstream os;
os << val;
return os.str().c_str();
}
template <typename ty>
inline ty __FromVCL(String const& text) {
return boost::lexical_cast<ty>(AnsiString(text).c_str());
}
#endif
Da ich es an der Oberfläche der Delphi- Frameworks immer mit UnicodeStrings zu tun habe, habe ich dieses nur für diesen Typ umgesetzt. Eine Erweiterung auf den Delphi- Typ Ansistring sollte aber für jeden sehr einfach sein. Mir geht es hier nicht um eine vollständige und funktionierende Bibliothek, sondern um das Erklären von Techniken, Problemen und Lösungsansätzen.
Nun kann der Aufwand, die boost Bibliothek auf einem mobilen System wie Android mitzuführen, höher sein, als der Nutzen. Oder ein Compiler bietet mir nicht die Unterstützung von boost an. Vielleicht gib es hier eine einfache Möglichkeit, auch boost muss ja irgendwie einen Ansatz gefunden haben. Deshalb ändere ich das obige Beispiel wie folgt ab, und nutze wieder die bedingte Übersetzung, sollte es sich bei der Zielumgebung um Android handeln.
#ifndef MyDelphiHelperH
#define MyDelphiHelperH
#if !defined BUILD_WITH_VCL && !defined BUILD_WITH_FMX
#error Für diese Anwendung muss eine Framework- Variante ausgewählt werden
#endif
#include <system.hpp>
#include <string>
#include <sstream>
#ifndef __ANDROID__
#include <boost/lexical_cast.hpp>
#endif
template <typename ty>
inline String __ToVCL(ty const& val) {
std::ostringstream os;
os << val;
return os.str().c_str();
}
template <typename ty>
inline ty __FromVCL(String const& text) {
#ifndef __ANDROID__
return boost::lexical_cast<ty>(AnsiString(text).c_str());
#else
std::istringstream ins(AnsiString(text).c_str());
ty val;
ins >> val;
return val;
#endif
}
#endif
Nun ist diese Umwandlung nicht effektiv, kann auch zu Fehlern führen, wenn Stringtypen selber verwendet werden. Für diese Typen möchte ich Spezialisierungen implementieren. Normalerweise erzeugt der Compiler für jeden Kombination von Typparameter eine Kopie das templates und ersetzt die Typparameter durch die konkreten Typen. Dadurch entsteht am Ende sehr robuster und direkt gelinkter Code der schneller ist, als Entscheidungen zur Laufzeit. Templates können aber auch spezialisiert werden (für ausgewählte Typparameter eine abweichende Implementierung festlegen), was ich hier ausnutzen möchte.
Außerdem ist bei den Delphi- Stringtypen eine Inkonsistenz vorhanden, da die Indizes hier auf mobilen Betriebssystemen dem Standardverhalten folgt und mit 0 beginnt. Außerdem muss hier die Umwandlung von dem 16 bit Zeichensatz auf den Systemtyp wchar_t erfolgen, der bei allen unixoiden 32 bit nutzt.
#ifndef MyDelphiHelperH
#define MyDelphiHelperH
#if !defined BUILD_WITH_VCL && !defined BUILD_WITH_FMX
#error Für diese Anwendung muss eine Framework- Variante ausgewählt werden
#endif
#include <system.hpp>
#include <string>
#include <sstream>
#ifndef __ANDROID__
#include <boost/lexical_cast.hpp>
#endif
template <typename ty>
inline String __ToVCL(ty const& val) {
std::ostringstream os;
os << val;
return os.str().c_str();
}
template <>
inline String __ToVCL<std::string>(std::string const& val) {
return val.c_str();
}
template <>
inline String __ToVCL<std::wstring>(std::wstring const& val) {
return val.c_str();
}
template <typename ty>
inline ty __FromVCL(String const& text) {
#ifndef __ANDROID__
return boost::lexical_cast<ty>(AnsiString(text).c_str());
#else
std::istringstream ins(AnsiString(text).c_str());
ty val;
ins >> val;
return val;
#endif
}
template <>
inline std::wstring __FromVCL<std::wstring>(String const& text) {
#ifdef __ANDROID__
// String indexes are 1-based in desktop platforms and 0-based in mobile platforms.
std::wstring strTemp2 = L"";
for(int i = 0; i < text.Length(); ++i) strTemp2.push_back(static_cast(text[i]));
return strTemp2. c_str();
#else
return text.c_str();
#endif
}
template <>
inline std::string __FromVCL<std::string>(String const& text) {
return AnsiString(text).c_str();
}
#endif
Nun kann es im Zusammenhang mit nullterminierten Zeichenfolgen Probleme geben, da ja diese als const char* gespeichert sind, abweichend zu dem internationalen Zeichenfeldern mit wchar_t. Deshalb werde ich eine Methode mit dem selben Namen __ToVCL implementieren, die aber kein template ist. Für die spätere Anwendung spielt dieses aber keine Rolle. Und mich befreit dieser Ansatz die Varianten auseinanderzuhalten, so dass ich mich auf inhaltliche Probleme konzentrieren kann.
inline String __ToVCL(const char* val) {
return val;
}
Damit habe ich die Unterschiede zwischen den mobilen Betriebssystemen und eventuelle Konvertierungen von nationalen zu internationalen Zeichensätze an einer zentralen Stelle implementiert. Sollte es Anpassungen geben, wird es keine Auswirkungen auf die Teile meiner Businessanwendung geben. Ich habe die Entscheidungen wieder in meiner Hand. Dadurch ergibt sich die Möglichkeit einer Testanwendung, die für FMX und die VCL gleich ist, und auch mit dem mobilen Betriebssystem Android als Zielplattform funktioniert.
#include "MainFormVCL.h"
#include "MyDelphiHelper.h"
#include <exception>
using namespace std;
void __fastcall TForm1::Button1Click(TObject *Sender) {
try {
int iWert = __FromVCL<int>(Edit1->Text);
iWert *= 5;
Edit2->Text = __ToVCL(iWert);
}
catch(exception& ex) {
ShowMessage(__ToVCL(ex.what()));
}
return;
}
Nun höre ich schon die Kritik, dass ich hier ja auch zahlreiche Aufrufe von Funktionen habe, außerdem erzeugt dieser Quellcode unnötige Kopien und kann daher nicht effektiv sein. Dieses trifft auf moderne C++- Compiler aber nicht mehr zu. Ich werde das in einem späteren Blogpost erklären und ihnen auch am konkreten Beispiel zeigen. Und die Funktionsaufrufe geben mir als verantwortungsbewussten Programmierer gerade die Möglichkeit, Entscheidungen zu treffen, Inkonsistenzen aufzulösen und auf zukünftige Veränderungen zu reagieren. Und dieses nicht verteilt über den kompletten Quellcode meiner Anwendung, sondern an einer zentralen Stelle. Sollte ich diese doch nacharbeiten müssen, ist es wesentlich einfacher, im Quellcode nach __FromVCL oder ::ToVCL zu suchen.
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