- End to End GUI Development with Qt5
- Nicholas Sherriff Guillaume Lazar Robin Penea Marco Piccolino
- 1114字
- 2021-06-10 19:27:13
Designing a maintainable project
The first step in designing a maintainable project is to properly split it in clearly defined modules. A common approach is to separate the engine from the user interface. This separation forces you to reduce coupling between the different parts of your code and make it more modular.
This is exactly the approach we will take with the gallery application. The project will be divided into three sub-projects:
The sub-projects are as follows:
- gallery-core: This is a library containing the core of the application logic: the data classes (or business classes), persistent storage (in SQL), and the model that makes the storage available to the UI through a single entry point.
- gallery-desktop: This is a Qt widgets application that will depend on the gallery-core library to retrieve data and display it to the user. This project will be covered in Chapter 12, Conquering the Desktop UI.
- gallery-mobile: This is a QML application targeted at mobile platforms (Android and iOS). It will also rely on gallery-core. This project will be covered in Chapter 13, Dominating the Mobile UI.
As you can see, each layer has a single responsibility. This principle is applied to both the project structure and the code organization. Throughout these three projects, we will endeavor to live up to the motto of the chapter: "Divide your project and rule your code".
To separate your Qt project this way, we will create a different kind of project, a Subdirs project:
- Click on File | New File or Project.
- In the Projects types, select Other Project | Subdirs Project | Choose.
- Name it ch03-gallery-core and then click on Choose.
- Select your latest Qt Desktop Kit, and then click on Next | Finish & Add Subproject.
Here, Qt Creator created the parent project, ch03-gallery-core, which will host our three sub-projects (gallery-core, gallery-desktop, and gallery-mobile). The parent project has neither code nor a compilation unit in itself, it is simply a convenient way to group multiple .pro projects and express the dependencies between them.
The next step is to create the first subdir project, which Qt Creator proposed immediately when you clicked on Finish & Add Subproject. We will start with gallery-core:
- Select Library in the Projects tab.
- Select C++ Library.
- Choose the Shared Library type, and name it gallery-core, and click on Next.
- Select the modules, QtCore, and QtSql, and then click on Next.
- Type Album in the Class name field, and click on Next. Qt Creator will generate the basic skeleton of a library with this class as an example.
- Check that the project is properly added as a sub-project of ch03-gallery-core.pro and click on Finish.
Before delving into gallery-core code, let's study what Qt Creator just made for us. Open the parent .pro file, ch03-gallery-core.pro:
TEMPLATE = subdirs SUBDIRS += gallery-core
Until now, we used the TEMPLATE = app syntax in our .pro files. The subdirs project template indicates to Qt that it should search for sub-projects to compile. When we added the gallery-core project to ch03-gallery-core.pro, Qt Creator added it to the SUBDIRS variable. As you can see, SUBDIRS is a list, so you can add as many sub-projects as you want.
When compiling ch03-gallery-core.pro, Qt will scan each SUBDIRS value to compile them. We can now switch to gallery-core.pro:
QT += sql QT -= gui TARGET = gallery-core TEMPLATE = lib DEFINES += GALLERYCORE_LIBRARY SOURCES += Album.cpp HEADERS += Album.h gallery-core_global.h unix { target.path = /usr/lib INSTALLS += target }
Let's see how this works:
- The QT has appended the sql module and removed the gui module. By default, QtGui is always included and has to be removed explicitly.
- The TEMPLATE value is different, again. We use lib to tell qmake to generate a Makefile that will output a shared library named gallery-core (as specified by the TARGET variable).
- The DEFINES += GALLERY_CORE_LIBRARY syntax is a compilation flag that lets the compiler know when it should import or export library symbols. We will come back soon to this notion.
- The HEADERS contains our first class Album.h, but also another generated header: gallery-core_global.h. This file is syntactic sugar provided by Qt to ease the pain of a cross-platform library.
- The unix { ... } scope specifies the installation destination of the library. This platform scope is generated because we created the project on Linux. By default it will try to install the library in the system library path (/usr/lib).
Please remove the unix scope altogether, we don't need to make the library available system-wide.
To have a better understanding of the cross-platform shared object issue, you can open gallery-core_global.h:
#include <QtCore/qglobal.h> #if defined(GALLERYCORE_LIBRARY) # define GALLERYCORESHARED_EXPORT Q_DECL_EXPORT #else # define GALLERYCORESHARED_EXPORT Q_DECL_IMPORT #endif
We encounter again the GALLERYCORE_LIBRARY defined in gallery-core.pro file. Qt Creator generated a useful piece of code for us: the cross-platform way to handle symbol visibility in a shared library.
When your application links to a shared library, symbol functions, variables, or classes must be marked in a special way to be visible by the application using the shared library. The default visibility of a symbol depends on the platform. Some platforms will hide symbols by default, other platforms will make them public. Of course, each platform and compiler has its own macros to express this public/private notion.
To obviate the whole #ifdef windows #else boilerplate code, Qt provides a Q_DECL_EXPORT (if we are compiling the library) and Q_DECL_IMPORT (if we are compiling your application using the shared library). Thus, throughout the symbols you want to mark as public, you just have to use the GALLERYCORESHARED_EXPORT macro.
An example is available in the Album.h file:
#ifndef ALBUM_H #define ALBUM_H #include "gallery-core_global.h" class GALLERYCORESHARED_EXPORT Album { public: Album(); }; #endif // ALBUM_H
You include the proper gallery-core_global.h file to have access to the macro and you use it just after the class keyword. It does not pollute your code too much and is still cross-platform.
- Increased compilation time: each time you modify the library, the application will have to be recompiled as well.
- Tighter coupling, multiple applications cannot link to your library. Each one of them must embed it.