Qt toolchain és Hello world alkalmazás

A Qt egy C++ és (elsősorban) GCC fölé épülő, cross-platform környezet. Néhány előnye, amit érdemes lehet kihasználni:

Letölteni innen lehet: https://www.qt.io/

A Qt környezet alapvetően a GCC fordítócsaládra épít (Windows alatt ezt a MinGW formájában érhetjük el), de szükség esetén kiválóan működik a Visual Studio fordítójával is.

A Qt+MinGW toolchain részei

C++ esetén a Qt toolchain az alábbiak szerint működik:

Egy Qt projekt létrehozása

Most pedig nulláról indulva hozzunk létre egy konzolos hello world alkalmazást és nézzük meg, hogyan is épül fel.

A Qt Creatort elindítva és a New Projectre kattintva válasszuk az Application - Qt Console Application lehetőséget. A projekt neve “QtHelloWorld” lesz.

Ha több toolchain is fent van a gépünkön (pl. Visual Studio), akkor választhatunk, hogy melyekre akarjuk előkészíteni a projektet. Lehet többre is, de most a MinGW elegendő lesz:

Next-next-finish engineering és készen is vagyunk:

F5-re le is fordul és el is indul a program, bár még nem tesz semmit.

(Qt Creatorban a bal alsó sarokban tudjuk kiválasztani, hogy Debug vagy Release módban fordítjuk a programot. A Ctrl-R debugger nélkül futtatja a programot (sima zöld “play gomb”), az F5 pedig debuggerrel (zöld play gomb bogárral). Egy debug módban lefordított programot simán lehet debuggolás nélkül is futtatni.)

Írjuk át a programot az alábbira:

#include <QDebug>

int main(int argc, char *argv[])
{
    Q_UNUSED(argc);
    Q_UNUSED(argv);
    qDebug() << "Hello, world!";
    return 0;
}

Mivel a program akkor szép, ha nem generál csomó warningot, úgy illik, hogy a nem használt változókat a Q_UNUSED makróval jelöljük. (A háttérben ez a makró használja a paraméterét, de semmi értelmeset nem csinál vele. De megnyugtatja a fordítót, hogy tényleg tudunk róla, hogy ezt nem használjuk.)

A kíváncsiak nyomhatnak a Q_UNUSED-on egy F2-t is (vagy jobb klikk, Follow symbol under cursor) és megnézhetik, pontosan mi van a makróban.

(Megjegyzés: amit kitöröltünk, egy QCoreApplication objektumot hozott létre és indított el. Ez az objektum például akkor kell, ha grafikus felületet akarunk készíteni, mert ő indítja el a Qt-s ablakok eseménykezelőit. De most ilyesmire nincsen szükségünk.)

A qDebug() szintén egy makró, melynek visszatérési értéke egy debug célokra szolgáló stream. Olyasmi, mint az std::cout sima C++ alatt. A lényeg, hogy tudunk vele a konzolra írni.

Ezzel végeztünk is magával a programunkkal. Nézzük meg a projekt fájlt, a QtHelloWorld.pro-t:

#-------------------------------------------------
#
# Project created by QtCreator 2015-07-13T16:04:52
#
#-------------------------------------------------

QT       += core
QT       -= gui

TARGET = QtHelloWorld
CONFIG   += console
CONFIG   -= app_bundle

TEMPLATE = app

SOURCES += main.cpp

Ennek a fájlnak két fő feladata van: a Qt környezet konfigurálása (pl. akarunk-e GUI támogatást a programunkba, alkalmazást (exe) vagy osztálykönyvtárat (lib) akarunk-e fordítani, vagy ha több projektünk is van, amiket együtt kell lefordítani (pl. egy alkalmazás, aminek egy részét lib-be fordítjuk), azt is itt kell megadni.

A QT kezdetű sorok bekapcsolják a Qt core funkciókat és kikapcsolják a GUI funkciókat. A célalkalmazás (vagyis a neve) a QtHelloWorld, ami egy sima konzol alkalmazás. Az alkalmazás sablon alapján készül a konfiguráció, nincs benne header fájl és forrás fájl is csak egy: a main.cpp.

A projekt a fájlrendszerben

Ha megnézzük azt a könyvtárat, ahol a projektünk van, észrevesszük, hogy ott van még egy fájl QtHelloWorld.pro.user néven. Ez a projekt felhasználói szintű beállításait tartalmazza, ezért tipikusan például verziókövetni nem is szoktuk, mert mindenkinél más. (Ha letöröljük, a projekt megnyitásakor újra meg kell adnunk, hogy milyen toolchaint akarunk használni, mert az például ebben van.)

Ha belenézünk, többnyire a beállításaink vannak benne kulcs-érték párok formájában. Pl. “EditorConfiguration.AutoIndent” és “ProjectExplorer.BuildConfiguration.BuildDirectory”.

Ami egyből feltűnhet, hogy a fordítás eredménye, az exe fájl nincs itt. Erre pont a fent említett BuildDirectory beállítás adja meg a választ: egy könyvtárral kijjebb van egy build-QtHelloWorld-Desktop_Qt_5_4_0_MinGW_32bit-Debug nevű könyvtár. Ezt hívják shadow buildnek: a fordítás kimenete máshova kerül, hogy a forráskód könyvtárát ne “szemetelje” össze. A neve utal a fordítási beállításokra (MinGW és Debug mód), benne pedig az alábbiak vannak:

(Ha esetleg a munkánk során arra gyanakszunk, hogy valami nem fordult újra, pedig kellene neki, akkor egyrészt a Qt creatorban a projekten jobb gombbal kattintva a Clean ebben a build könyvtárban takarít, vagy akár magát a könyvtárat is letörölhetjük, az akkor biztosan mindent újrageneráltat.)

A fordítási folyamat lépésről lépésre

Most nézzük meg, pontosan mik történnek, amikor fordul a programunk. Ehhez letöröltem a build könyvtárat és a projektre kattintva kértem egy Build-et. Lent a Compile Output ablakban az alábbiak jelentek meg.

16:33:48: Running steps for project QtHelloWorld...

Most a QtHelloWorld projekthez szükséges lépéseket futtatjuk.

16:33:48: Starting: "C:\Qt\5.4\mingw491_32\bin\qmake.exe" E:\Oktatas\AlkFejl\QtHelloWorld\QtHelloWorld.pro -r -spec win32-g++ "CONFIG+=debug"

Indul a qmake. Megkérjük, hogy a QtHelloWorld.pro fájl alapján készítsen Makefile-okat win32 alatti G++ (GCC C++ fordítója) fordításhoz. Debug módban szeretnénk fordítani.

16:33:49: The process "C:\Qt\5.4\mingw491_32\bin\qmake.exe" exited normally.

Szuper, exited normally, a qmake lefutott.

16:33:49: Starting: "C:\Qt\Tools\mingw491_32\bin\mingw32-make.exe" 
C:/Qt/Tools/mingw491_32/bin/mingw32-make -f Makefile.Debug

Indul a MinGW make-je, méghozzá a Makefile.Debug fájl alapján fog dolgozni.

mingw32-make[1]: Entering directory 'E:/Oktatas/AlkFejl/build-QtHelloWorld-Desktop_Qt_5_4_0_MinGW_32bit-Debug'

A sor elején látszik, hogy most már a make üzeneteit látjuk. Először is belép a munkakönyvtárába.

g++ -c -pipe -fno-keep-inline-dllexport -g -frtti -Wall -Wextra -fexceptions -mthreads -DUNICODE -DQT_CORE_LIB -I..\QtHelloWorld -I"C:\Qt\5.4\mingw491_32\include" -I"C:\Qt\5.4\mingw491_32\include\QtCore" -I"debug" -I"." -I"C:\Qt\5.4\mingw491_32\mkspecs\win32-g++" -o debug\main.o ..\QtHelloWorld\main.cpp

A make elindította a g++ fordítót. Itt elég sok parancssori kapcsoló van, melyeknek most csak egy része érdekes számunkra:

Ezzel a compiler el is végezte minden feladatát, mivel több cpp fájlunk nincsen. Ha lenne, mindre egyesével meghívódna a fordító.

g++ -Wl,-subsystem,console -mthreads -o debug\QtHelloWorld.exe debug/main.o  -LC:/Qt/5.4/mingw491_32/lib -lQt5Cored 

Mivel a make ezzel minden cpp fájlt lefordított, meghívja a linkert (ami jelen esetben szintén a g++ egyik funkciója, de működésében attól még a fordítási folyamat egy alapvetően másik lépése). Itt a fontosabb paraméterek:

Itt érdemes megemlíteni, hogy a libraryket is lehet release és debug módban is fordítani, és ha bináris formában kapjuk meg őket, akkor tipikusan mindkettőt mellékelik. A debug módban fordított library neve általában azonos a release módúval, kivéve a név végén lévő “d”-t, mint a fenti példában is a Qt5Cored.

mingw32-make[1]: Leaving directory 'E:/Oktatas/AlkFejl/build-QtHelloWorld-Desktop_Qt_5_4_0_MinGW_32bit-Debug'

A make végzett ebben a könyvtárban, visszalép egyet. (A make egész könyvtár szerkezeteket be tud járni úgy, hogy minden könyvtárban lehet neki feladatot adni Makefile formájában. Például több program is lehet egymás mellett, mindnek lehet saját Makefile-ja, és a gyökérben lehet egy olyan, ami minden könyvtárra ráküldi a make-et, a részleteket meg majd ott megtalálja.)

16:33:50: The process "C:\Qt\Tools\mingw491_32\bin\mingw32-make.exe" exited normally.
16:33:50: Elapsed time: 00:01.

Sikeresen végeztünk. Ráadásul egész gyorsan.

Ami a fentiekből fontos: minden hiba és warning, ami az “Issues” ablakban megjelenik (vagy általában bármely fejlesztő környezet hibajelző ablakában), az innen származik, a fordító a konzol ablakba írja ki őket. Vagyis például ha a fordító nem talál egy include fájlt, akkor a konzol ablakban megkeresve, hogy mikor kaptuk ezt a hibát, meg lehet azt is nézni, hogy milyen paraméterekkel futott le a fordító. Onnan pedig kiderül, hogy pontosan melyik könyvtárakban kereste a header fájlokat. A header és lib fájlok meg nem találásával kapcsolatos hibákat tipikusan innen lehet megoldani.

A fordítás során kaphatunk a fordítótól hibákat (pl. ismeretlen változó, szintaktikai hiba stb.), vagy a linkertől (“undefined reference”). Ez utóbbi már nem a forráskód egy konkrét sorára hivatkozik, hanem arra utal, hogy egy vagy több object fájl hivatkozik valamire, amit viszont semelyik object vagy library fájlban nem talál a linker, így a hívási csonkokat nem tudja hova bekötni. (Ilyen esetben azt kell ellenőrizni, hogy az a cpp fájl, amiben a nem talált függvény van, tényleg lefordult-e, és a linkernek fel van-e sorolva bemenetként (vagy libraryként a -l kapcsolóval), a linker pedig nem reklamált-e, hogy nem találja.

Fájlok: projekt, forráskód, header, fordítási termékek és társaik

A toolchain és fordítás után még egyszer menjünk végig az egyes fájlokon, amikkel egy projektben találkozhatunk:

Ahhoz képest, hogy egy hello world programot írtunk, elég sok mindenről szó esett. De a fordítási folyamatnak az egyes lépéseit fontos egyszer végignézni, mert rengeteg hibát akkor lehet könnyen megoldani, ha tisztában vagyunk vele, mi is történik a háttérben. (Egy fekete dobozról elég nehéz megmondani, hogy miért nem mondja azt, hogy “Hello world”…)

Szerzők, verziók: Csorba Kristóf