4.4.2016

foto Petr Bravenec

Petr Bravenec
Twitter: @BravenecPetr
+420 777 566 384
petr.bravenec@hobrasoft.cz

Popisovat Qt by mě asi nenapadlo, kdybych si nechystal tento článek jako podklad pro přednášku na akci Tkalci na webu ve Valašském Meziříčí.

Jak jsem se ke Qt dostal

Už před více než dvaceti lety jsem hledal způsob, jak vytvářet aplikace pro okenní prostředí X11 v Unixu a Linuxu. První knihovnou, kterou jsem používal, byla knihova X Athena Widget. Moje nejstarší aplikace z té doby (rok 1996) je stále k nalezení na internetu a funguje (podrobnosti v samostatném článku).

V té době ještě asi neexistovala prostředí Gnome a KDE, o knihovnách GTK a Qt nikdo neslyšel. Nebo možná slyšel, ale určitě ne já, protože na internet jsem se dostal velmi zřídka.

Programovat s knihovnou X Athena Widget je dost nepohodlné, navíc graficky je ta knihovna dost škaredá. Takže jakmile se objevila prostředí Gnome a KDE, začal jsem pokukovat po GTK nebo Qt. V té době jsem Qt hodil přes palubu, protože víc než na Linuxu jsem pracoval na Unixu a tam jsem si mohl o překladači C++ nechat leda tak zdát a navíc se mi dvakrát nezamlouvala různá rozšíření specifická pro Qt a tehdejší licence knihovny Qt.

Další aplikace jsem tvořil v GTK. Pokud vím, žádná moje aplikace v GTK už se nepoužívá.

Jakmile se uvolnila licenční politika Qt, začala pro mě být tato knihovna zajímavější. Lákala mě - pro mne tehdy teoretická - možnost tvorby aplikací pro Windows i pro Linux současně. Asi první věcí, co jsem v Qt vyrobil, je aplikace na měření solárních článků. Tahle aplikace se používá dodneška.

V současnosti děláme v Qt jak desktopové aplikace, tak serverové aplikace:

  • Deko the CRM - CRM systém vystavěný nad databází CouchDB (windows, linux)
  • GeoSign - software pro elektronickou komunikaci geodetů s úřady (windows, linux)
  • Mdcv04ip - server pro řízení komunikačních systémů v nemocnicích
  • Fotomon - celý balík různých programů - desktopových i serverových - pro monitorování fotovoltaických elektráren
  • hobrasoft httpd server - událostmi řízený http server pro Qt aplikace

Vlastnosti Qt

O některých vlastnostech knihovny Qt se dočtete už při prvním seznamování - multiplatformnost, vícejazyčnost. Prakticky ihned při prvních pokusech s tvorbou aplikace programátor odhalí další věci - signály a sloty. Ale až když začne programátor pracovat s knihovnou Qt intenzivněji, dostane šanci objevit další vlastnosti - řízení událostmi, asynchronní zpracování, vlákna.

Multiplatformnost

Pro mě velmi důležitou vlastností knihovny Qt je schopnost fungovat v různých prostředích. Sám primárně programuji v Linuxu, ale většina mých zákazníků používá Windows. Aplikaci, kterou vytvořím a odladím v Linuxu, můžu obvykle zcela bez úprav přeložit i pro Windows. Celý postup mám vymakaný až do takové podoby, že v Linuxu překládám a vytvářím i aplikaci určenou pro Windows a to včetně instalačního balíku. Windows používám pouze pro vytváření screenshotů do dokumentace, aby mi uživatelé Windows 10 nezáviděli dekorace kolem oken.

Pokud chci, aby byla aplikace přeložitelná bez starostí i pro Windows, je nutné používat pouze funkce Qt. To obvykle stačí, aby aplikace fungovala bez jakýchkoliv úprav jak v Linuxu, tak ve Windows. Pokud pracuji se soubory, je vhodné pamatovat na některých místech aplikace na použití konverzních funkcí, aby uživatel nedostával názvy souborů v GUI s nevhodně skloněnými lomítky. Pokud sáhnu mimo knihovnu Qt, nemusí být výsledek vždy podle očekávání - naposledy jsem se setkal s různým chováním funkce rand() na různých platformách.

Knihovna Qt se hodně používá na různých zařízeních s ARM procesory. Běžně vytvářím serverové aplikace pro Beaglebone a na procesorech ARM jsem pro český trh programoval firmware pro tři různé čtečky elektronických knih.

Jednoduchou, malou aplikaci jsem v Qt vytvořil i pro Android.

Velmi příjemné je, že prakticky vždy jsem aplikaci vytvořil ve svém běžném desktopovém počítači s Linuxem a teprve po základním zprovoznění jsem aplikaci přenesl na cílovou platformu, kde už se pouze doladily detaily.

Podpora národních jazyků a zvyklostí

Aplikace Qt lze snadno překladát do jiných jazyků. Na programátora jsou přitom kladené jen velmi snadno splnitelné požadavky - každý řetězec určený k překladu je potřaba uzavřít do volání funkce tr(). Řetězce jsou součástí přímo zdrojového kódu (na rozdíl třeba od Androidu) a vypreparovat ven do xml souboru se dají jednoduchou utilitou. Pro překladatele pak existuje v Qt aplikace pro tvorbu překladů. Ta se dá samostatně nainstalovat na počítač překadatele. Překladateli pak stačí poslat xml soubor s texty pro překlad pro doplnění nových výrazů. Překladatelé si kvůli přehledu často otevírají několik jazykových mutací najednou (angličtinu a češtinu), i když vytvářejí třeba překlad do ruštiny.

Signály a sloty

Signály a sloty bych zařadil mezi nejužitečnější mechanismus, kterým Qt rozšiřuje možnosti jazyka C++. V žádné jiné platformě, kterou jsem kdy používal, jsem nenarazil na nic ani vzdáleně podobného.

Každý objekt, který v Qt existuje, může emitovat nějaký signál. Například časovač, třída QTimer, po vypršení nastavené doby vyvolá signál timeout(). Časovač se nestará, kam se signál posílá, jestli se předává do stejného vlákna či do jiného vlákna, kolika jiným objektům se signál předává, nebo jestli se vůbec signál zpracovává.

Jiným příkladem může být třeba tlačítko, třída QPushButton. Při stištění tlačítka (klavesnicí, myší) se emituje signál clicked() a tlačítku je srdečně jedno, kam je signál napojený, na kolik různých objektů, nebo jestli je signál vůbec někam napojený.

Každý objekt, který v Qt existuje, může mít nějaké sloty. Slot je celkem normální metoda třídy, která může být napojená na nějaký signál. Propojení signálů a slotů je dynamické, děje se až za běhu programu. Ani zde se objekt nijak nestará, jestli má na své sloty napojené nějaké signály. O propojení se může starat nějaký jiný objekt. Jako názornou ukázku bych uvedl třeba formulář, který se automaticky ukládá každou minutu nebo stištěním tlačítka:

    connect(timer, SIGNAL(timeout()), formular, SLOT(save()));
    connect(button, SIGNAL(clicked()), formular, SLOT(save()));

Výborné je, že signály fungují bezpečně i mezi vlákny. Qt v takovém případě předává data přes frontu událostí a díky tomu se nemusejí používat jiné prostředky pro mezivláknovou komunikaci (zamykání, synchronizace).

Při propojení signálů a slotů se provádí i typová kontrola. Protože se propojení realizuje až za běhu programu, překladač není obvykle schopný rozeznat problémy v době překladu, ale aplikace bez potíží upozorní na nesoulad mezi propojovanými signály a sloty při vytváření propojení za běhu aplikace.

V jiných jazycích se samozřejmě používají konstrukce, které funkčně odpovídají signálům a slotům z Qt. V jazyce C se běžně používají callbacky (GTK). O callback se ale musí starat obě strany: objekt, který emituje událost, musí mít evidenci volaných callbacků a objekt, který přijímá událost, musí vědět, jak požádat emitora o zasílání událostí. Komunikovat takto mezi vlákny je zhola nemožné. Jazyky JavaScript a C# řešívají tento úkol podobně: funkcí předanou v parametru. Tento přístup na nějaké striktní oddělení zdroje události a zpracovatele události zcela rezignuje. V případě JavaScriptu nelze o vícevláknovém zpracování hovořit. V případě C# je nutné rozlišovat, kam se signál posílá, pomocí mechanismu invoke - stejný program pak vypadá jinak v jednovláknovém prostředí a jinak ve vícevláknovém prostředí.

Řízení událostmi

Se signály a sloty úzce souvisí událostní charakter celé knihovny. Typický program v Qt je celý řízený událostmi a velká část knihovních funkcí a objektů funguje zcela asynchronně. V různých systémových utilitách, které bývají napsané v C, bývají naprosto běžné postupy "Přečti 10 bajtů ze socketu". V Qt takto nepřečtete nic. Pro Qt je typické, že socket vám dá vědět "něco přišlo ze sítě" signálem a teprve potom se můžete pokusit o přečtení očekávaných deseti bajtů. Nutno přiznat, že Socket v Qt třeba disponuje metodou waitForReadyRead(), která funguje dle očekávání - počká, dokud není v socketu něco ke čtení, ale spousta dalších tříd (dále zmiňovaný QNetworkAccessManager) nic podobného neumí a fungují zcela asynchronně.

Správa paměti

Poměrně bolestivou oblastí, která odrazuje spoustu lidí od jazyků C a C++ je správa paměti. Tradiční přístup vede k častému používání operátorů new a delete a nutnost na každý new zavolat delete vede k chybám a únikům paměti. Mou noční můrou je třeba knihovna OpenSSL - režie spojená s vytvářením a uvolňováním objektů OpenSSL tvoří dost podstatnou část zdrojového kódu a triviální chyby zde vedou k neuvolňování paměti, v horším případě k havárii aplikace a v těch nejhorších případech k neočekávanému chování aplikace.

Qt k tomuto tématu přistupuje trochu jinak. Každý objekt v Qt dědí z třídy QObject a při jeho vytváření se kromě vyjímečných situací novému objektu vždy předává ukazatel na jeho rodiče. Nevím, jak to Qt dělá a je mi to celkem jedno, ale při smazání některého objektu se zlikvidují automaticky i všichni jeho potomci (je zoufalé narazit na kus kódu, u kterého si jeho autor usmyslel, že to zvládne lépe ručně voláním new a delete). Nemám ve zvyku se o paměť příliš starat a ono to většinou nějak vychází. Pokud se v aplikaci soustředím na vyhledání a opravení úniků paměti, obvykle na moc vysoký počet úniků nenarazím. Většina úniků vzniká z jiných příčin, než je někde zapomenutý delete.

Qt nepoužívá garbage collector. Pokud programátor cítí potřebu používat sofistikovanější metody pro správu paměti, může použít třídy QSharedPointer (čítač referencí) nebo QObjectCleanupHandler (garbage collector). S třídou QSharedPointer jsem se kdysi snažil experimentovat, ale dospěl jsem k názoru, že s trochou pečlivosti a kázně je pro mě tato třída naprosto zbytečná. Pokud programátor potřebuje použít třídu QSharedPointer, s velkou pravděpodobností jde o chybu v návrhu aplikace.

How does Qt delete objects ? And what is the best way to store QObjects?

Modularita

Knihovna Qt je příjemně modulární. Celá knihovna se všemi moduly může být velmi rozsáhlá. Díky modularitě je však možné očesat celou instalaci Qt doslova na kost a na výsledném stroji může být nainstalovaných jen pár MB skutečně potřebných částí.

Občas někoho překvapím, když označím knihovnu Qt jako výborný nástroj pro tvorbu embedded aplikací na výkonově velmi omezených platformách (čtečky elektronických knížek, beaglebone, *pi). Většina lidí má totiž knihovnu Qt spojenou s prostředím KDE, což je těžkotonážní grafické prostředí pro Linux.

Dokumentace

Asi nebudu daleko od pravdy, když označím Qt za nejlépe zdokumentovaný toolkit vůbec. Dokumentace je velmi podrobná, s množstvím příkladů a s výborným provázáním jednotlivých částí. Nedostižným snem mých klientů je, abych jim dodával takto zdokumentované aplikace - mým snem zase je, aby moji klienti měli dost prostředků na zaplacení takové práce.

Knihovna Qt je navíc hodně rozšířená - je vysoká pravděpodobnost, že na kterýkoliv dotaz položený Googlu ohledně Qt najdete odpověď na Stack Overflow.

Moduly Qt

Ve většině projektů používám dosud knihovnu 4.8. Popsané zkušenosti s moduly proto vycházejí především z verze knihovny Qt 4.8.

  • QtCore - QObject, vlákna, externí procesy, soubor, kontejnery, textové streamy, časovače, MVC
  • QtGui - většina toho, se zobrazuje na obrazovce - pokud o Qt moc nevíte, budete si myslet, že Qt je právě tohle. Kupodivu jde o nejméně zajímavou část celé knihovny, naopak Qt se snaží oddělit GUI od výkonné části pomocí jazyka QML.
  • QtMultimedia - podpora pro video a audio
  • QtNetwork - QNetworkAccessManager, QTcpSocket, QUdpSocket, QSslSocket, QTcpServer
  • QtOpenGL - akcelerované video karty
  • QtScript - vestavěný ECMA interpreter (javascript)
  • QtSql - připojení k sql databázím
  • QtSvg - všechny ikony a grafiku lze tvořit v SVG
  • QtQml, QtQuick - oddělení části GUI a výkonné části aplikace.
  • QtWebKit - vestavěný webový prohlížeč
  • QtTest - toolkit pro testování

QtCore

QObject - prakticky všechno v Qt má jako základní třídu QObject. Tato třída obstarává spojení signálů a slotů a správu paměti.

QThread - v Qt se dají snadno vytvářet vícevláknové aplikace. Signály posílané mezi různými vlákny se automaticky předávají přes frontu, takže pro většinu použití ani není potřeba používat mutexy či jiné způsoby ochrany paměti nebo synchronizaci.

QVariant

Celý soubor různých datových typů a kontejnerů. Hodně užitečné a používané jsou QString, QByteArray, QDateTime.

Kontejnery

QList, QLinkedList, QVector, QStack, QQueue, QSet, QMap, QHash

Hodně užitečná část Qt. Jednak umožňuje používat velmi snadno různé způsoby organizace dat pole potřeby a jednak rozšiřuje C++ o jazykové konstrukce, které jinde nejsou obvyklé. Občas lze narazit na argumenty proti přetěžování základních operátorů v C++ od lidí, kteří C++ neznají: "Přetěžování operátorů je pitomost. Jak mám vědět, co dělá třeba znaménko plus, když to někdo přetíží?"

Podívejte se na tento kus kódu:

    QSet<int> mnozina, mnozina2, mnozina3;
    mnozina2 << 1 << 2 << 3 << 4;
    mnozina3 << 5 << 6 << 7 << 8;
    mnozina = mnozina1 + mnozina2;

Uhádnete, aniž byste věděli, co který operátor dělá, co je v které proměnné uložené?

Kontejnery přinášejí do C++ třeba asociativní pole, přičemž si můžu vybrat algoritmus (hash, btree), jakým je pole organizované:

    QHash<QString, QString> pole;
    pole["klic"] = "Hodnota";

QTimer - časovač

Objekt, který hodně používám pro konstrukce typu "každou minutu ulož obsah formuláře", nebo "pět vteřin po startu zavři okno se splash obrázkem". Umožňuje psát aplikaci velmi asynchronním způsobem.

MVC

Model/view/kontroler návrhové schéma. Objekty odvozené od třídy QAbstractItemModel. Každý takový objekt pak lze zobrazit v nějakém view z GUI části. View samozřejmě může být různé - pro naše aplikace je typická tabulka s daty a několik různých grafů prezentující různé pohledy na tatáž data. Data (model) se udržuje v paměti jen jednou, zobrazovat se mohou data na více místech. Existují i modely propojené přímo s databázovou tabulkou, zobrazit a spravovat data v tabulce tak lze přímo v GUI s minimální námahou programátora. Modely mohou pracovat v líném režimu - data se tahají do modelu a ve view zobrazují až v momentě, kdy je to potřeba.

QtNetwork

Třída QNetworkAccessManager je švýcarským nožíkem pro přístup k internetu pomocí různých protokolů. Pracuje asynchronně, zvládá několik různých požadavků najednou.

Třídy QTcpSocket, QUdpSocket, QSslSocket, QTcpServer jsou nízkoúrovňové funkce pro přístup k síti. Pro většinu činností je jednodušší použít QNetworkAccessManager.

QtScript

Vestavěný ECMA interpreter. Pokud potřebuju, aby byla aplikace "programovatelná", přibalím k ní JavaScript.

QtWebKit

Webový prohlížeč se dá používat aniž by byl vůbec zobrazený a pomocí selektorů podobně jako v jQuery se dají z webové stránky vybírat informace. Stejně tak se dá prohlížeč vložit jako widget do normální desktopové aplikace - máme tak třeba v aplikaci Deko the CRM ve formuláři pro zadávání firem a lidí vloženou Google mapu. Podobně máme vyřešené sestavy - statická html stránka, jquery a data tahaná z napojení přímo do vnitřností aplikace napsané v C++.

Qml a QtQuick

Původní widgety v knihovně Qt jsou jednoznačně orientované na vývoj desktopových aplikací. Knihovna Qt má však ambice běžet na mnoha různých zařízeních, od (PC, mobily, tablety, čtečky) s různými operačními systémy (Linux, Windows, Android, iOS). Protože zobrazovací a vstupní možnosti mnoha jiných různých zařízení (mobily, tablety) se od původního desktopu výrazně liší, ukázalo se vhodné odělit výkonnou část a část GUI. Pro výkonnou část lze dále použít knihovnu Qt a C++, pro část GUI se prosazuje jazyk QML s toolkitem QtQuick.

Jazyk QML je postavený nad JavaScriptem, ale je snadno propojitelný s výkonnou částí aplikace v C++ pomocí signálů a slotů.

Technologie schované za různými verzemi QtQuick se liší: QtQuick 1 je založená na QPainter či QGraphicsView, QtQuick2 pak na OpenGL. Liší se i interpteter jazyka JavaScript schovaný za různými verzemi Qt.

Qt 5

Verze 5 posouvá možnosti knihovny Qt zase o kus dál. Přiznám se, že s Qt5 nemám příliš zkušeností, ale množství pokusů s různými novými možnostmi už jsem absolvoval. Knihovna Qt5 je rozdělená do většího množství modulů než Qt4. To je dané jednak tím, že knihovna Qt5 toho zkrátka umí více a jednak tím, že celky, které (by) byly v Qt4 zabalené do jednoho modulu, jsou v Qt5 rozbité do několika nezávislých částí.

  • Android - podpora věcí specifických pro Android. Zahrnul bych sem i další moduly používané především na Androidu: QtBluetooth, QtLocation, QtPositioning, QtSensors.
  • Podpora pro ActiveX a COM ve Windows.
  • QtWebEngine, QtWebKit - různé nástroje pro zobrazení webového obsahu v GUI Qt aplikace.
  • QtWebChannel, QtWebSockets - různé nástroje pro propojení Qt aplikace s webovým prohlížečem.
  • Qt3D - skupina modulů pro tvorbu akcelerovaných 2D a 3D aplikací
  • QtWebEngine - html renderovací jádro založené na Chromiu

Serverové aplikace

Knihovnu Qt s oblibou používám pro typicky serverové aplikace. Nejrozsáhlejším kusem kódu z této oblasti je komunikační server pro nemocnice. Tento server obstarává obsluhu různých signalizačních prvků a komunikaci telefonních prvků s protokolem SIP. S komunikačními prvky se server baví pomocí UDP, data ukládá do CouchDB a servisní funkce jsou přístupné přes HTTP server.

V Qt jsem zkoušel několik různých http serverů. Jenže buď jsem narazil na server nepoužitelně primitivní, nebo naopak příliš ambiciozní, nepoužitelný v jednovláknovém prostředí (vícevlaknové servery potřebují synchronizaci vláken a zamykání). Nakonec jsem si vytvořil vlastní událostně řízení http server.

Moje první aplikace tohoto typu sbírala data na fotovoltaických elektrárnách. Webovou část aplikace jsem tehdy před lety svěřil docela šikovnému studentovi. Chybou bylo, že já sám jsem tehdy nerozeznal úskalí, které to s sebou nese, takže ve výsledku jsem dostal nepřehlednou změť C++, HTML a JavaScriptu. Tvorba webových stránek v C++ je prostě nesmysl, stejně jako je to nesmysl v PHP či kterémkoli jiném jazyce kromě HTML.

Dneska dělám webové rozhraní pro serverové nebo embedded aplikace jinak:

  • veškerý obsah je pouze statický (html, obrázky)
  • jednoduchý šablonový systém (include pro hlavičky a patičky)
  • data se tahají přes jednotné API v JSON
  • obsah stránek se tvoří pomocí jQuery

Data v API se předávají ve formátu JSON. Pokud se používá knihovna Qt4, je potřeba přibalit externí knihovnu QJson, u knihovny Qt5 je podpora pro JSON vestavěná.

Hobrasoft s.r.o. | Kontakt