Porting from Qt 4 to Qt 5

Porting from Qt 4 to Qt 5 is intentionally easy. There has been a conscious effort throughout the development of Qt 5 so far to maintain source compatibility compared to Qt 4.

Unlike the port from Qt 3 to Qt 4, central classes have not experienced large API cleanups, and there are few new frameworks replacing old ones (as for example containers (QPtrList and QValueList replaced by QList, itemviews replacing Q3ListView and graphicsview replacing the Canvas APIs), and changes that compile but affect runtime behaviour, such as QWidget::show becoming non-virtual, and painting being restricted to the paintEvent.

Still, the porting effort is non-zero, so this post summarizes some of the steps required when porting parts of KDE to Qt 5.

KDE PIM was one of the last parts to port fully to Qt 4 and kdelibs 4. Porting to Qt 5 should be much faster.

Pre-porting

As part of a porting strategy, it makes sense to be able to compile code with both the old and new Qt. This ensures that the amount of time (and commits) where your code does not compile with either base libraries is minimized, allows work based on Qt 4 to continue with the library, ensures that unit tests continue to work for most of the duration of porting, and regressions resulting from porting your code are easily distinguished from bugs introduced in Qt 5.

Qt3Support porting

Porting software to Qt 5 can start with modernizing a Qt 4 based codebase.

One significant change in Qt 5 from a point of view of porting an old codebase is the removal of the Qt3Support module, and the removal of APIs marked as Qt3Support. In most cases, the Qt3Support code is a method which has been renamed to something more appropriate in Qt 4. The old method then forwards to the new one, such as QWidget::setShown forwarding to QWidget::setVisible. Parts of KDE still use the old methods, and the same will be true for other old 3rd party codebases too.

Porting away from the Qt3Support APIs in Qt 4 is one of the necessary, unavoidable steps in a Qt 4 to Qt 5 port, though it will be possible in theory to build the Qt3Support module with Qt 5 at some point.

Fixing up includes

One of the major internal infrastructural changes in Qt 5 compared to Qt 4 is the splitting of widgets from the QtGui module into a new QtWidgets module. This obviously will require buildsystem changes at least, but also causes the need for downstreams to add includes for headers which were not needed before, as those includes were removed from headers which now remain in the QtGui module. A common manifestation of this is having to add #include "QDrag" where it was not needed before. This is because it used to be included as part of the gui/kernel/qevent.h header, but that is no longer the case in Qt 5.

Another includes-related issue in porting from Qt 4 to Qt 5 is dealing with includes for classes which have moved to the QtWidgets module. Whereas Qt 4 based code might use

#include <QtGui/QWidget>

This must be updated to either

#include <QtWidgets/QWidget>

Or more portably (Which works in Qt 4 and Qt 5):

#include <QWidget>

A small script can be used to make changes like this in bulk. Like the removal of the use of Qt3Support, the clean-up of includes can largely be done on a Qt 4 codebase before porting.

Fixing platform defines

It seems to have been common across Qt and KDE codebases to wrap platform specific code using the preprocessor macros Q_WS_* instead of Q_OS_*. For example,

#ifdef Q_WS_WIN // call windows API #endif

instead of

#ifdef Q_OS_WIN // call windows API #endif

In Qt 5, the Q_WS_* macros have been removed, so any code wrapped in them will never be compiled. Where appropriate (ie when code being wrapped is operating system specific and not window system specific), such code can and should be ported to the Q_OS_* macros.

Missing Q_OBJECT macros and metatype cleanup

Qt 4 can be quite forgiving regarding whether a QObject subclass requires the use of the Q_OBJECT macro, even though that can cause subtle and unexpected runtime bugs in the QMetaObject system. That is not changed per-se in Qt 5, but if you attempt to put a pointer to a type derived from QObject into a QVariant with using the Q_DECLARE_METATYPE macro, you will now get a compile error in Qt 5 (just like in Qt 4, but a different compile error this time). That is because QVariant now records the fact that the type it is storing is a pointer to a type derived from QObject, which allows for interesting features in QtDeclarative, language bindings, and other libraries making heavy use of the QMetaObject introspection APIs.

Another side effect of that is that the type argument to the Q_DECLARE_METATYPE macro must be fully defined, not forward declared. That is, this will no longer compile:

class MyType; Q_DECLARE_METATYPE(MyType);

The macro must be moved to a place where MyType has been fully defined (such as the header which defines it). In cases where MyType derives from QObject, the macro can be removed entirely.

Refactoring

One of the major changes in Qt 5 is greater focus on QML, a runtime interpreted language for creating user interfaces, and QtQuick, the APIs that accompany the language. Although QtWidgets are still available, tested and working in Qt 5, many performance and user interaction benefits can come from porting to QML.

As QML is runtime interpreted and does not have the same type-safety constraints that C++ has, it is largely intended to be used with your ‘models’ of data as represented by QObject subclasses and their properties and other types which can be contained in a QVariant.

If part of the goals of porting a codebase to Qt 5 is to make increasing use of QML, then it can make sense to refactor the existing code so that logic and ‘model representation’ – that is, representations of the state of the application and its contents – are separated from the widgets used in the application. This kind of refactoring can be done on a Qt 4 based codebase. A working or experimental QML port can even be made in a Qt 4 codebase to verify the refactoring in concept. This is a nice effect of QML becoming available during the Qt 4 releases – in effect, it can be seen as a Qt5Support library.

Porting away from QWS

The QWS system is not part of Qt 5 and its APIs have been removed. Code using those APIs will need to be ported to the new QPA system, which is a central part of Qt 5. QPA was actually introduced in Qt 4.8 (though the Qt 5 API is somewhat different).

It is possible to port to QPA in a Qt 4 code, which will need some further porting in Qt 5, but will have largely the same design at a high level. There is no documentation for the QPA APIs in Qt 4.8, but there are some reference implementations to compare to.

Porting

The porting steps described so far are all source compatible with Qt 4, meaning they can be done as part of regular maintenance on a Qt 4 based project which has an eye on Qt 5. Some APIs have source-incompatible changes in Qt 5, many of which are listed in the changes file. In many cases, these issues are not relevant to “normal” code, because they are rarely used, or change edge cases in minor ways.

Nevertheless, these changes will need to be part of a porting strategy. As they cause a necessary difference between your Qt 4 and Qt 5 based code, they will either cause you to drop support for building against Qt 4, or to litter the code with preprocessor #ifdef s for either version.

Plugin loading

Another significant porting burden, in plugin-heavy systems at least is that the user code required for plugin loading has changed. The moc tool is now responsible for generating plugin metadata, so rather than a preprocessor macro in a C++ file (Q_EXPORT_PLUGIN2), as in Qt 4, in Qt 5 a new macro must be used in the header file, where moc can see it.

The process is described by Lars, and is relatively straightforward. Where it becomes difficult though is in cases where the Q_EXPORT_PLUGIN2 macro is wrapped with another macro, as done in KDE in K_EXPORT_PLUGIN.

Such a wrapping of the new macro would not be possible with Qt 5 because moc would not parse any wrapper ( moc does not do full preprocessing).

Skipping Unit tests

One of the few other changes source incompatible changes is in the QTestLib module – a change in the QSKIP macro. In Qt 4 the macro takes two arguments, and in Qt 5 only one.

This poses an obvious portability problem. The solution chosen in KDE is to create a wrapper macro that always takes two arguments and silently discards one in Qt 5 mode. This is something that we should remove in the future when building against Qt 4 is no longer required in KDE.

Another solution was merged last week, making use of C99 and C++11. If you enable the -std=c++11 switch when building your software, source compatibility will be restored.

QMetaMethod::signature

In software which makes extensive use of the QMetaObject introspection system, it is relatively common to use the QMetaMethod::signature API. In Qt 4, this returns a const char *. In Qt 5 however, the return value is constructed dynamically, so that had to be changed (It doesn’t make sense to return a pointer to a temporary). Now the equivalent method has both a different return type (QByteArray) and a different name (QMetaMethod::methodSignature). A naive port to just the new method name could create runtime bugs:

// Old Qt 4 based code. The const char * returned is // assigned to a local variable and used const char *name = mm.signature(); otherApi(name); // New Naively ported Qt 5 based code const char *name = mm.methodSignature(); // Uh-oh, name is already a dangling pointer. The QByteArray // returned from methodSignature has already deleted the data. otherApi(name); // This is now a crash waiting to happen.

The runtime bug can be avoided by defining QT_NO_CAST_FROM_BYTEARRAY while building your code. This is something that can and should be enabled on a Qt 4 based codebase. The method rename and return-type change can be done in an independent porting step.

Changed virtual methods

A small number of virtual methods have changed signatures in Qt 5. These won’t cause build errors during porting (except in the case of changed pure-virtuals), but will cause unexpected errors at runtime when the ‘overridden’ method is not executed. One such example is the QAbstractItemView::dataChanged signal, which gained a parameter.

There are several solutions to this. The new C++11 standard has syntax for indicating that a particular method on a class is an override of a virtual method on one of the base-classes. Using such a syntax could be a useful pre-porting step so that build errors occur during the actual porting step. Build errors should always be preferred to runtime errors because they are easier to find (indeed, they can’t be avoided).

Another option is to enable compiler warnings to find the issue. GCC can issue warnings if a method in a derived class hides a virtual method in the base class (-Woverloaded-virtual). This can be enabled in your buildsystem as a pre-porting step so that the porting step can be more explicit. Additionally, as the Pragmatic Programmer teaches, it makes sense to always use the strictest available compiler warning level, so you should add this to your buildsystem already anyway.

Post-porting

As all software needs to be maintained, not just written (or ported, in this case), there are extra steps that can be taken after completing a Qt 4 to Qt 5 port.

It will make sense eventually to not build against both Qt 4 and Qt 5 in the same branch. Having a fully ported to Qt 5 codebase with all unit tests passing marks the point where it will start to make sense to use different branches for Qt 4 based and Qt 5 based versions. One consequence of that is to be able to port away from API that is present in Qt 4 but is deprecated in Qt 5.

Porting away from deprecated methods

Deprecated methods are methods which still exist in Qt 5, but which are obsolete or replaced with a more suitable alternative. In the case of Qt 5.0, this is largely legacy API which is kept around for Qt 4 compatibility, and to ease porting, similar to the case with Qt 3 originating methods in Qt 4.

It is beneficial to port away from deprecated methods as soon as practical, because they typically cause warnings at compile time, which can be just noise compared to other compiler warnings, and because newer API can be faster or use concepts which fit better with the rest of Qt.

Porting to QML

Porting an existing UI to QML is an optional step in a Qt 5 port which can be started either pre-port or post port to Qt 5.

The version of QML which was available in Qt 4 is now available in Qt 5 under the name QtQuick1. It is only provided there as a porting step, and it will not be receive performance improvements etc. Code using it should be ported to QML2, (and the QtQuick API in Qt 5) to benefit from maintenance and performance improvements. Porting to QML2 is largely a case of using different class names in the relevant C++ API, and updating the way custom items are painted. Because QML2 uses a scene-graph, rather than the imperative QPainter API (This is where the performance benefits come from), custom items need to be painted using a different updated API.

Conclusion

This is a non-exhaustive, but representative list of steps involved in porting from Qt 4 to Qt 5. A much more complete list of porting relevant changes will be part of the Qt 5 release notes.

It is also worth noting that this blog post focused (rather boringly) on steps required to make an application build with Qt 5, but says nothing about runtime porting bugs, which is where the real effort of porting is spent. Some KDE applications currently show some subtle bugs that will need to be tracked down:

The changes listed in boundary conditions listed in the changes file indicate where differences in Qt 5 could cause bugs to manifest in ported code.