C++-Framework: Was sich mit Qt 5 ändert | heise online

C++-Framework: Was sich mit Qt 5 ändert

Die jüngere Geschichte des Cross-Plattform-Frameworks Qt liest sich wie eine Mischung aus schlechtem Thriller und Lehrbuch über Managementfehler. Die komplette Übernahme des Produkts durch Digia und die Veröffentlichung von Qt 5 kurz vor Weihnachten beendeten dann das Trauerspiel. Die umfangreichen Änderungen entfalten auf Entwickler eine geradezu magische Anziehungskraft.

vorlesen Druckansicht 4 Kommentare lesen
Lesezeit: 17 Min.
Von
  • Tam Hanna
Inhaltsverzeichnis

Die jüngere Geschichte des Cross-Plattform-Frameworks Qt liest sich wie eine Mischung aus schlechtem Thriller und Lehrbuch über Managementfehler. Die komplette Übernahme des Produkts durch das Beratungsunternehmen Digia und die Veröffentlichung von Qt 5 kurz vor Weihnachten beendeten das Trauerspiel – die umfangreichen Änderungen entfalten auf Entwickler eine geradezu magische Anziehungskraft.

Qt 4 erschien schon 2005. Damals waren Handcomputer teure Spielzeuge für VIPs und Technikfreaks – die Entwickler von Applikationen für diese Geräte waren hoch spezialisierte Informatiker, die sich nur damit beschäftigten. Geräte wie Palms Centro und das iPhone verschoben den Fokus. Plötzlich waren Smartphones angesagt, die Werbeindustrie entdeckte die Produkte als Trägerplattform. Dadurch veränderten sich die Ansprüche an Entwickler: "Early Adopters" forderten Funktionen, die "neu angeworbenen" Anwender waren mit schickem Design und primitiver Bedienung zufrieden. Ein einheitlicher Look und die effiziente Ausnutzung des Bildschirmplatzes sind unwichtig – stattdessen legt die neue Nutzerschicht Wert auf Effekte, aufwendige Hintergrundbilder und schickes Design.

Die Hardwareindustrie reagierte auf diese Änderungen. Heutige Handys enthalten Grafikprozessoren, die von der Leistung her den vor fünf bis zehn Jahren in Workstations eingesetzten Chips entsprechen. Auch auf Seiten der Hersteller fand ein Umdenkprozess statt. War das Fertigen von Handhelds früher die Aufgabe einiger darauf spezialisierter Anbieter, animierte die gesteigerte Breite des Markts neue Teilnehmer. Da diese auf den Sperreffekt einer eigenen Plattform setzen wollten, entstanden im Laufe der Zeit zunehmend mehr Betriebssysteme.

Qt war für diese "neue Welt" denkbar schlecht gerüstet. QWidgets integrierten sich eng in die GUI-Stacks der zugrunde liegenden Plattformen, was das Erstellen einer neuen Portierung ungemein erschwerte. Außerdem waren die Widgets den neuen Anforderungen nicht gewachsen: Sie wollten Daten möglichst effizient darstellen. Das Verschwenden von Bildschirmplatz war nach der damaligen Design-Philosophie ein Unding.

Doch damit nicht genug – den neuen Eigentümer Nokia bedachte die Community aufgrund seiner sonstigen Schwerpunkte (Symbian und MeeGo) mit Misstrauen. Deshalb waren Veränderungen angebracht. In einem am 19. Mai 2011 veröffentlichten Dokument versprach der Qt-Chefentwickler umfangreiche Änderungen für die nächste Version des Frameworks. Er legte die folgenden Ziele fest:

  1. neuer Grafik-Stack
  2. leichtere Portierbarkeit
  3. einfacher Code einreichen können

Trolltech lebte ausschließlich vom Verkauf kommerzieller Lizenzen des Frameworks. Darum passte das Unternehmen Qt an viele kommerzielle Unix-Systeme an – wer für sein Betriebssystem Lizenzgebühren bezahlt, zahlt wahrscheinlich gerne für die Entwicklungsumgebung.

Da die GUI-Stacks der verschiedenen Plattformen teilweise inkompatibel waren, führte man in Qt for Embedded Linux das QWS (Qt Windowing System) ein. Da damalige Embedded-Prozessoren keine fortgeschrittenen Grafikprozessoren enthielten, war die Unterstützung von OpenGL nicht vorgesehen (und ließ sich auch nicht ohne weiteres nachrüsten).

Im Laufe der Zeit kamen einige Entwickler auf die Idee, die Abhängigkeit vom Host-Betriebssystem auch am Desktop und im Mobilbereich durch das Einschieben einer Abstraktionsschicht (Qt Platform Abstraction)zu beheben. Diese würde als Fenstermanager agieren und die Steuerelemente darstellen – das Host-Betriebssystem ist nur mehr für das Einsammeln von Eingaben und das Ausgeben der berechneten Bitmaps verantwortlich.

Bodgan Vatra nutzte die Technik, um in geringer Zeit einen Port für Android bereitzustellen ("Necessitas"). Nach dem Beweis der Praxistauglichkeit entdeckte das Qt-Team, dass die Abstraktion auch zu einer wesentlichen Vereinfachung der Codebasis führt. Der Endeffekt dieser Überlegungen ist, dass das sogenannte Lighthouse in allen Portierungen von Qt 5 zum Einsatz kommt. Der frühere plattformspezifische Code fällt damit weg.

Aufgrund der angesprochenen zunehmend breiteren Verfügbarkeit von OpenGL beschloss das Qt-Team außerdem, OpenGL 2.0 (ES oder Desktop) vorauszusetzen. Der Sinn hinter dieser Einschränkung ist es, die Entwickler vom Zwang zur Berücksichtigung von Nicht-OpenGL-fähiger Hardware zu befreien. Wenn OpenGL als Basis gilt, wird der Standard auch voll ausgereizt.

Entwickler bemerken diese Änderung, da die bekannten Q_WS-Makros nicht mehr belebt werden. Das führt bei der Kompilierung des Programms meist nicht zu Problemen, legt es allerdings zur Laufzeit (im Rahmen der hoffentlich erfolgenden Akzeptanztests) flach:

#ifdef Q_WS_WIN
// Windows-API aufrufen
#endif

Der Grund dafür liegt darin, dass die plattformspezifischen Makros in Qt 5 mit der Zeichenkombination Q_OS beginnen. Wer in seinem Programm auf Q_WS setzt, bekommt keinen Compiler-Fehler. Da die Makros aber undefiniert sind, wird der plattformspezifische Code nicht ausgeführt. Korrekt sähe das Snippet also so aus:

#ifdef Q_OS_WIN
// Windows-API aufrufen
#endif

Für den durchschnittlichen Entwickler hat die Einführung von QPA keine große Signifikanz. Ein anderes neues Feature der Plattform fällt hingegen jedem auf: Das an eine Mischung aus JavaScript und JSON (JavaScript Object Notation) erinnernde QML entzweit die Nutzerschaft seit seiner Einführung. Zeitgemäße Benutzerschnittstellen bestehen primär aus grafischen Elementen und Animationen. Die Qt Markup Language kommt dem Trend entgegen – die in JSON geskripteten GUIs lassen sich durch Export aus PhotoShop erstellen.

Auch ist die Syntax wesentlich einfacher als die komplexe Kombination aus XML und C++-Code in Widget-Anwendungen. Designer haben eine realistische Chance, damit zurechtzukommen – das ist ein nicht unerheblicher Vorteil, der die Entwicklung von Programmen wesentlich beschleunigt.

Quasi nebenbei macht QML die Skriptsprache JavaScript zu einem Teil des Qt-Frameworks. Kleinere programmiertechnische Probleme lassen sich dank des integrierten JavaScript-Interpreters direkt in der .qml-Datei lösen. Es ist also nicht nötig, für jeden Farbwechsel in die in C++ gehaltene Logik zu "springen".

Die Mehrheit der bislang besprochenen Features ist allerdings schon in Qt 4.8 enthalten. Qt 5 bringt zusätzlich zum schon bekannten Quick 1.0 eine neue, Qt Quick 2.0 genannte Bibliothek mit. Diese bringt neben einer geänderten Interop-API für C++ folgende neue Features mit:

  • Canvas-Steuerelement mit am HTML5-Canvas angelehnter API
  • Unterstützung für Shader-Effekte
  • Partikeleffekte
  • Sprites
  • Offline-Storage-API zum Speichern von Daten in JavaScript
  • Window-Objekt zum Zugriff auf das "Host-Formular"

Übrigens: QML enthält keine fertigen Steuerelemente. Stattdessen greifen Entwickler auf Komponenten zurück, die sie entweder selbst erstellen oder von anderen Softwarehäusern beziehen. Digia plant, im Laufe der Zeit einige Komponenten mitzuliefern – diese sind aber noch nicht fertig.

Obwohl QML in Qt 5 wesentliche Performancesteigerungen und neue Funktionen erhalten hat, bleiben die bekannten Widgets nach wie vor voll unterstützt. Da die Qt-Quick-Komponenten für den Desktop erst in einigen Monaten Teil der offiziellen Distribution des Frameworks werden, sind Widgets – zumindest am Desktop – nach wie vor "first class citizens" von Qt.

Trotzdem ist Digia bemüht, den Entwicklern den Unterschied zwischen QML und Widget zu verdeutlichen. Deshalb wandern die Widgets aus dem QtGui-Modul in ein eigenes Modul namens Widgets, das in der .pro-Datei mit folgendem Statement einzubinden ist:

QT += widgets

Die Übersiedlung hat den unangenehmen Nebeneffekt, dass sich einige Header-Pfade ändern. Daher ist es unter Umständen notwendig, die Includes zu aktualisieren. In der Quellcode-Distribution findet sich unter qtbase/bin/ ein Skript mit dem Namen fixqt4headers.pl, das automatisierte Abhilfe verspricht.

Qt war von Anfang an ein proprietäres Produkt. Da es in Open-Source-Kreisen rasch weite Verbreitung fand, entstand bald Sorge über die Sicherheit des darauf basierenden Codes. Da Trolltech als Unternehmen vom Verkauf von Qt lebte, war nicht davon auszugehen, dass das Framework durch "Missmanagement" vom Markt verschwinden würde.

Das Risiko ging zur damaligen Zeit primär von lizenzrechtlichen Querelen aus. Die Free Qt Foundation löste dieses Problem durch einen Vertrag mit Trolltech: Wenn die Firma die Entwicklung der quelloffenen Version von Qt einstellt (lies: kein Update für sechs Monate), fällt die letzte veröffentlichte Version unter die BSD-Lizenz.

Nach der Übernahme durch Nokia änderte sich die Lage. Für die Finnen war der Verkauf von Qt-Lizenzen kein attraktives Geschäft – die Plattform war primär zur Vereinheitlichung der außer Kontrolle geratenen Betriebssystemvielfalt vorgesehen. Um das Interesse der Community nicht völlig zu verlieren, wurde das Open-Governance-Modell eingeführt. Es sieht vor, dass die Benutzerschaft als Ganzes die Entscheidungen bei Qt trägt – analog zur Symbian Foundation wird das Mitspracherecht durch das Volumen der Beiträge festgelegt (Meritocracy).

In der Vergangenheit trat das Qt-Framework als monolithischer Block in Erscheinung. Zur Verkleinerung der Binärdatei gab es zwar einzelne Module, beim Portieren galt es aber als Sache des guten Tons, immer das gesamte Framework zu bearbeiten. Vatras Arbeiten an Necessitas zeigten, dass diese Vorgehensweise nicht optimal ist. In Qt 5 unterteilt Digia sein Produkt deshalb in drei Kategorien namens Essentials, Add-Ons und Tools.

Als Qt Essentials bezeichnet das Qt-Team jene Module, die auf allen unterstützten Plattformen verfügbar sind und während der gesamten Lebenszeit von Qt 5 quell- und binärkompatibel gehalten werden. Spezifisch handelt es sich dabei um elf Module, die die folgende Tabelle zusammenfasst.

Modulname Kurzbeschreibung
Core Basisklassen für den Rest des Frameworks
GUI OpenGL und Basisklassen für grafische Darstellung
Multimedia Kamera, Audio, Video
Netzwerk Diverse Klassen zum Erstellen portabler Netzwerkanwendungen
QML Logik von QML
Quick Grafische Repräsentation für QML
SQL Datenbankintegration
Test Unit-Testing
WebKit HTML-Renderer
WebKit Widgets HTML-Renderer von Qt 4
Widgets Grafische Elemente von Qt 4

Die Add-Ons sind zusätzliche Module, die weitere Funktionen zum Framework hinzufügen. Viele von ihnen sind auf allen unterstützten Plattformen verfügbar. Der Unterschied zu den Essentials besteht darin, dass jedes Add-On-Modul seine eigenen Bedingungen zur Plattformunterstützung und Kompatibilität festlegen darf. Zu guter Letzt gibt es die Gruppe der Tools. Dabei handelt es sich um Werkzeuge für Entwickler, die auf allen Entwicklungsplattformen verfügbar sind.

Vermutlich am interessantesten unter den Neuerungen von Qt 5 ist – neben der weiter unten besprochenen Verbesserung im Signal-Slot-System – die Integration eines Parsers für das im JavaScript-Bereich verbreitete JSON-Format. Die im Core-Modul angesiedelte QJsonDocument-Klasse erlaubt das Einlesen von Arrays oder "Objekten" (besser: assoziative Arrays) aus JSON-Dateien. Die aufwendige Konversion in native Arrays oder QVariantMap-Instanzen erfolgt durch das Framework.

Die Klasse QStandardPaths liefert auf Anfrage eine Liste relevanter Pfade zurück. So ist es in Qt 5 möglich, den Pfad des Font-Ordners der Host-Maschine ohne nativen Code zu ermitteln – die StandardLocation-Enumeration listet alle abfragbaren Verzeichnisse auf.

Wer häufig reguläre Ausdrücke parst, profitiert von der neuen QRegularExpressions-Klasse. Sie implementiert die aus Perl bekannte PCRE-Syntax (Perl Compatible Regular Expressions), was das Verwenden der diversen RegEx-Bibliotheken unnötig macht.

Die Änderungen im Netzwerk-Stack fallen gravierend aus. Erstens sind die QFtp- und QHttp-Klassen nun nur mehr als optionale Add-ons erhältlich; stattdessen sollen die Entwickler auf den schon mit Qt 4 eingeführten QNetworkAccessManager umsteigen.

Als "Ersatz" für die aufgrund der Entfernung des Qt3Support-Moduls nicht mehr verfügbare Q3Dns-API gibt es die neue Klasse QDnsLookup. Sie handhabt das Absetzen von DNS-Anfragen, das Hantieren mit Sockets entfällt an der Stelle komplett.

Zu guter Letzt verbessert Qt 5 die Interaktion mit per IPv6 ansprechbaren Endpunkten. Die Socket-Klassen verbinden sich ab sofort, sofern nicht explizit zum Verwenden eines bestimmten Protokolls angewiesen, sowohl per IPv4 als auch per IPv6 mit den als Ziel übergebenen Gegenstellen.

Ein Qt Multimedia genanntes Modul vereinigt die bekannte QtMultimedia-Bibliothek und das nur auf Mobilplattformen verbreitete Qt MultimediaKit. Es ist eine transparente Abstraktionsschicht zwischen Anwendung und Betriebssystem, die keine eigenen Codecs mitbringt. Daraus folgt, dass die Anzahl der unterstützten Medienformate nach wie vor von Plattform zu Plattform verschieden ist – eine Falle für Entwickler, die damit nicht rechnen.

Das Signal-Slot-System sticht unter den Neuerungen heraus. Es erlaubt das Entkoppeln der einzelnen Klassen eines Systems. Statt die Objekte direkt miteinander zu verbinden, erfolgt die Kommunikation über ein vom Framework bereitgestelltes Event-System. Zum Aufbau von Verbindungen kommt eine connect() genannte Methode zum Einsatz. Diese arbeitet in Qt 4 mit Strings – als Beispiel dafür ein connect()-Aufruf aus einem kommerziellen Produkt des Autors:

QObject::connect(buttonBox, SIGNAL(accepted()), HighscoreForm, 
SLOT(accept()));

Die Makros SIGNAL und SLOT wandeln die an sie übergebenen Signaturen während des Durchlaufs des Präprozessors in Strings um. Der Compiler sieht also einen Methodenaufruf nach folgendem Schema:

QObject::connect(Objektpointer, String, Objektpointer, String);

Daraus folgt, dass es nicht möglich ist, die Gültigkeit der Verbindung zwischen Signal und Slot während der Kompilierung zu überprüfen. Stattdessen findet der Check erst zur Laufzeit (also beim Aufruf von connect()) statt. Wenn der Aufruf erst am Computer des Endanwenders auftritt, hat das Programm einen Fehler.

Qt 5 bringt neue Overloads der Methode connect() mit. Sie akzeptieren statt der Strings Funktions-Pointer und ermöglichen so das Überprüfen der Korrektheit der Verbindung schon während der Kompilierung des Programms:

QObject::connect(sender, &Sender::valueChanged, receiver, 
&Receiver::updateValue);

Zu guter Letzt können C++11-fähige Compiler Signale mit Lambda-Funktionen verdrahten. Das ermöglicht die von HTML bekannten "Inline Functions". Ob das auf die Übersichtlichkeit des Codes positiv wirkt, muss jeder Entwickler für sich entscheiden.

Nach all den theoretischen Überlegungen wendet sich der Artikel kurz der praktischen Frage zu, was zu tun ist, wenn man eine in Qt 4 gehaltene Anwendung betreibt. Aus Sicht des Autors ist die – wahrscheinlich überraschende – erste Antwort erst einmal "nichts". Ein Qt-4-Programm funktioniert auch nach der Auslieferung von Qt 5 ohne jegliche Probleme weiter.

Da Qt 5 die Anzahl der unterstützten Plattformen stark minimiert, ist es gar nicht nicht möglich, jede Applikation zu migrieren. Wenn ein Unternehmen auf Symbian oder Meego gesetzt hat, ist es von vornherein ausgeschlossen – die neue Version des Frameworks möchte mit diesen Betriebssystemen nichts mehr zu tun haben.

Sofern die Plattformauswahl mit der von Qt 5 übereinstimmt, ist noch die Frage nach der Sinnhaftigkeit zu beantworten. Wer Interesse an einer der für die Zukunft geplanten Plattformen hat (Stichwort iOS), sollte eine Migration dringend ins Auge fassen. Kommt das Programm hingegen mit den Möglichkeiten von Qt 4.8 aus, besteht kein akuter Bedarf für Upgrades.

Aufgrund der Erfahrungen des Autors (eines seiner Produkte wurde von Palm OS auf bada, HTML5 und Qt portiert) führt die von Diomidis Spinellis in seinem Klassiker "Code Reading" vorgeschlagene Vorgehensweise am schnellsten zu brauchbaren Ergebnissen. Dabei bringt man das zu portierende Programm ohne Anpassungen in eine Form, die der Compiler des neuen Systems verwerten kann. Nach jedem Compiler-Lauf beseitigt man einige Fehler – am Ende steht ein kompilierbares System, das einem sorgfältigen Test zu unterziehen ist und danach einsatzbereit ist. Der Vorteil der Methode liegt darin, dass der Compiler den Entwickler von Fehler zu Fehler führt. Man ist von der ersten Sekunde an produktiv und verliert wenig Zeit mit der in der Retrospektive meist unergiebigen Analyse von "Portierungs-Gotchas".

Bei Qt sind die ersten Schritte das Anpassen der .pro-Datei (Stichwort QtGui versus Widgets) und der diversen Header. Wer in seinem Programm noch immer auf das Qt3Support-Modul setzt, muss spätestens an der Stelle mit Refactoring beginnen – in Qt 5 ist es nicht mehr verfügbar. Ein weiterer Quell diverser Ärgernisse ist das Fehlen des Q_OBJECT-Makros in Headern. Aufgrund des neuen Typsystems verzeiht Qt 5 in dem Bereich weniger Fehler. Da der Compiler in diesem Fall Fehlermeldungen wirft, ist das Beheben nur eine Fleißaufgabe.

Sofern eine Applikation auch QML-Code enthält, stellt sich die Frage nach der Wahl des richtigen Moduls. Das Weiterverwenden von Qt Quick 1.0 ist problemlos möglich. Will man die neuen Features (und den besseren Renderer) der neuen Version verwenden, sind allerdings einige Anpassungen vorzunehmen.

In den meisten Fällen ist das Programm nach dem Abarbeiten dieser Schritte lauffähig. Sicherlich gibt es immer Sonderfälle, weshalb ein sorgfältiger Test des Programms in jedem Fall zielführend ist. Der Dienstleister KDAB hat auf seinem Firmenblog einen Bericht über die Portierung der PIM-Suite von KDE veröffentlicht. Er zeigt die Herausforderungen im Detail. Zusätzlich gibt es als Teil der offiziellen Dokumentation des Frameworks eine detaillierte Änderungsliste, die auf potenzielle Problemstellen hinweist.

Signifikante Überarbeitungen eines Frameworks laufen nie komplett schmerzfrei ab. Wer Qt-Anwendungen für Symbian entwickelte, erinnert sich mit Sicherheit an die schlaflosen Nächte nach jedem Update. Die fünfte Version des Frameworks verhält sich – in Summe gesehen – überraschend problemlos. Natürlich fehlen den neuen Modulen noch einige Features, und die eine oder andere Änderung in der Syntax von APIs und Includes ist mit Sicherheit ärgerlich.

Wer mit den neuen Features nichts zu tun haben möchte, darf Qt 5 im Großen und Ganzen wie die Vorgängerversion benutzen. Trotzdem profitieren auch QML-Verweigerer von der neuen Architektur. Wer seine Qt-Anwendung für Android ausliefert, verwendet automatisch Lighthouse beziehungsweise QPA.

Tam Hanna
befasst sich seit der Zeit des Palm IIIc mit Programmierung und Anwendung von Handheldcomputern. Er entwickelt Programme für diverse Plattformen, betreibt Onlinenews-Dienste zum Thema und steht für Fragen, Trainings und Vorträge gern zur Verfügung.
(ane)