Porting a Qt4 application to Qt5

Somewhere here recently there was a post about innovations in Qt5. Everything seems to look great, but how are things really with respect to existing applications? In this article, I will consider an example of porting one of my projects to Qt5 while maintaining source compatibility with Qt4.

So, as you would expect, my GUI application is not going to. And it is not going to because I use standard Qt widgets. After a short trial, it turns out that the widgets are now in a separate module and should be explicitly included. Let's make it compatible with Qt4 by adding the following code to our project qmake file (* .pro | * .pri):
greaterThan(QT_MAJOR_VERSION, 4) {
  QT += widgets
  DEFINES += HAVE_QT5
}

Also from now on, thanks to the added define, you can insert #ifdef HAVE_QT5 anywhere in your code and then write Qt5-specific code. The same effect with ifdef could be achieved by creating a precompiled header with the contents:
#define HAVE_QT5 (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))


And again, my project is not going to. This time, the reason is the missing Q_WS_ * defines. With Q_WS_WIN and Q_WS_MAC everything seems to be very clear, just change them to Q_OS_WIN and Q_OS_MAC, respectively. But what to do with Q_WS_X11? Quite often, Q_WS_X11 is used as just a sign of Unix-like systems with the exception of Mac OS (X), but it also happens that this specific code hides X11-specific code. But in my case, this is basically the same thing, so you can just replace it with some other define (we just cannot redefine Q_WS_X11, since some parts of Qt still use this definition) and declare it in the right place, in the project file for example:
unix:!mac:DEFINES += HAVE_X11


Move on. My project is quite old and it uses a lot of obsolete classes or class methods or anything that has already been thrown away or prohibited in Qt5. The assembly does not go again. Some of what is deprecated can be found in the Qt5 documentation . As a rule, the same documentation says what should be used in return, although there are exceptions that I will discuss below. If the outdated classes and their methods are not found in the Qt5 documentation, then these are probably the remains of Qt3 and can be found in the Qt4 documentation. From what I came across it is worth noting: QIconSet, QMenuItem (qt3. Change to QIcon and QAction, respectively), QAbstractItemModel :: reset (use beginResetModel / endResetModel), QKeySequence no longer casts to int (we loop through the elements of the sequence), part QUrl migrated to the new QUrlQuery class, Qt :: escape was deleted (its inline wrapper over Qt :: escape and QString :: toHtmlEscaped), qInstallMsgHandler (qInstallMessageHandler) ...

Next QPA. Everything would be fine, but with the advent of this thing from Qt a very useful class QX11Info disappeared, and nothing sane was said in the documentation on this subject. He disappeared because he was rigidly tied to Xlib. Instead, QPlatformNativeInterface appeared in Qt5, which, however, was soon banned and is now only available as a private class / header. In my project, as a quick solution still tied to Xlib, I just wrote a wrapper over QX11Info, which in the case of Qt5 has its own implementation:
x11info.h
#ifndef X11INFO_H
#define X11INFO_H
typedef struct _XDisplay Display;
class X11Info
{
	static Display *_display;
public:
	static Display* display();
	static unsigned long appRootWindow(int screen = -1);
};
#endif // X11INFO_H

x11info.cpp
#include "x11info.h"
#ifdef HAVE_QT5
# include 
# include 
#else
# include 
#endif
Display* X11Info::display()
{
#ifdef HAVE_QT5
	if (!_display) {
		_display = XOpenDisplay(NULL);
	}
	return _display;
#else
	return QX11Info::display();
#endif
}
unsigned long X11Info::appRootWindow(int screen)
{
#ifdef HAVE_QT5
	return screen == -1?
				XDefaultRootWindow(display()) :
				XRootWindowOfScreen(XScreenOfDisplay(display(), screen));
#else
	return QX11Info::appRootWindow(screen);
#endif
}
Display* X11Info::_display = 0;

I can not vouch for the logical fidelity of the implementation, since the code has not yet been tested until the end of this article.
I also have native event handlers such as QApplication :: x11EventFilter for example. In Qt5, of course, they had to be rewritten. To do this, we need a QAbstractNativeEventFilter and some knowledge of xcb programming (Xlib will not work here because QPA does not know anything about it). In principle, the transition to xcb is not too complicated due to the similarity of the API of these two libraries, but it will not hurt to stock up on manuals. In my case, the implementation was quite trivial: in its application class, next to x11EventFilter, added another method called xcbEventFilter and several ifdefs to compile only the desired method. Next, I created a class inherited from QAbstractNativeEventFilter and from it I simply redirected the processing of all xcb events to our method:
#ifdef HAVE_X11
# ifdef HAVE_QT5
class XcbEventFiter : public QAbstractNativeEventFilter
{
	MyApplication *app;
public:
	XcbEventFiter(MyApplication *app) : app(app) {}
	virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *) Q_DECL_OVERRIDE
	{
		if (eventType == "xcb_generic_event_t") {
			return app->xcbEventFilter(message);
		}
		return false;
	}
};
# endif
// ....
#endif

If you want to initialize the application, you can replace the application's singleton custom in nativeEventFilter.

Further plugins. In Qt5, they are loaded in a different, incompatible way and should be declared differently too. Such an unpleasant breakdown was made in order to optimize (now it is not necessary to load the whole plugin as a full shared library to make sure that it is a Qt plugin in general and to get any meta information from it, such as, for example, the version of the program with which this plugin compatible.more info here ), but nevertheless it needs to be repaired anyway.
To begin with, all export macros of our plugins are conditional like this:
#ifndef HAVE_QT5
Q_EXPORT_PLUGIN2(myplugin, MyPlugin);
#endif

Also in the plugin class, we conditionally add a new Qt5-specific macro Q_PLUGIN_METADATA somewhere near Q_INTERFACES but after Q_OBJECT:
#ifdef HAVE_QT5
	Q_PLUGIN_METADATA(IID "tld.domain.Project.MyPluginInterface" FILE "myplugin.json")
#endif

The FILE part “myplugin.json” is necessary only if we really need metadata in the plugin, and as for the “tld.domain.Project.MyPluginInterface” interface, this is the same interface as in Q_DECLARE_INTERFACE. In my case, the metadata will contain: the plugin version, the minimum version of the program itself, and the download priority. Also, do not forget to add magic with the HAVE_QT5 declaration to the plugin project files, well, or, as a quick option without magic, use #if QT_VERSION> = 0x050000 .
In the case of static plugins, you have to change the call Q_IMPORT_PLUGIN macros. As a parameter, they now accept the class name of the plugin, and not what was the first parameter in Q_EXPORT_PLUGIN2.

So, launch! And, as expected, segfault. My code expects QMetaType :: Void == 0, but in Qt5 it is not. Great, fix, run and run segfault again. This time, the problem is that some types are declared in one place, and Q_DECLARE_METATYPE for them is in another. Because of what, the latter, even when explicitly enabled with a type, does not work correctly. I did not understand what exactly the catch was, I simply transferred Q_DECLARE_METATYPE for types to their header files. And again the launch - it works!

The program has started, but there is still a lot of work. The transition to xcb should still be full, i.e. my X11Info class needs to be rewritten using xcb. Also, it is necessary to check for operability everything that has been patched, however, as well as not patched. But, I hope, the most difficult is over!

I hope my experience will be useful to you. Below are some links that helped me solve Qt5 puzzles:

www.kdab.com/porting-from-qt-4-to-qt-5
xcb.freedesktop.org/tutorial
qt-project.org/doc/qt-5.0 /qtwidgets/tools-plugandpaint.html
google.com

UPDATE: Useful crutch for resolving forbidden in Qt5

Also popular now: