Итак, как и следовало ожидать мое GUI приложение не собирается. И не собирается оно потому, что я использую стандартные Qt виджеты. После недолгих разбирательств выясняется, что виджеты сейчас в отдельном модуле и должны быть явно включены. Сделаем это совместимым с Qt4 способом — добавим следующий код в наш файл qmake проекта (*.pro|*.pri):
greaterThan(QT_MAJOR_VERSION, 4) { QT += widgets DEFINES += HAVE_QT5 }
Также с этого момента благодаря добавленноу дефайну можно в любом месте своего кода вставить #ifdef HAVE_QT5 и далее написать код специфичный для Qt5. Того же эффекта с ifdef можно было добиться создав precompiled header с содержимым:
#define HAVE_QT5 (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
И опять мой проект не собирается. На этот раз причина в отсутствующих дефайнах Q_WS_*. С Q_WS_WIN и Q_WS_MAC вроде бы всё предельно ясно, просто меняем их на Q_OS_WIN и Q_OS_MAC соответственно. Но что же делать с Q_WS_X11? Довольно часто Q_WS_X11 используется как просто признак юникс-подобных систем за исключением Mac OS (X), но и бывает так, что под этим дефайном скрывается код специфичный для X11. Но в моем случае это в общем-то одно и тоже, так что можно просто заменить на какой-либо другой дефайн (просто переопределить Q_WS_X11 мы не можем, так как некоторые части Qt всё ещё используют это определение) и объявить его в положенном месте, в файле проекта например:
greaterThan(QT_MAJOR_VERSION, 4):unix:!mac:DEFINES += HAVE_X11
Идем дальше. Проект у меня довольно старый и в нем используется множество устаревших классов или методов классов или чего угодно, что уже выброшено или запрещено в Qt5. Сборка опять не идет. Часть из того, что устарело, можно найти в документации по Qt5. Как правило в той же документации сказано, что надо использовать взамен, хотя есть и исключения, которые я рассмотрю ниже. Если же в документации по Qt5 не найдено устарвших классов и их методов, то вероятно это остатки Qt3 и их можно найти в документации на Qt4. Из того на что я наткнулся стоит отметить: QIconSet, QMenuItem (qt3. меняем на QIcon и QAction соответственно), QAbstractItemModel::reset (используем beginResetModel/endResetModel), QKeySequence больше не кастуется в int (делаем цикл по элементам последовательности), часть QUrl перекочевала в новый класс QUrlQuery, Qt::escape удален (свой inline враппер над Qt::escape и QString::toHtmlEscaped), qInstallMsgHandler(qInstallMessageHandler)…
Далее QPA. Всё бы ничего, но с появлением этой штуки из Qt исчез весьма полезный класс QX11Info, и в документации по этому поводу не сказано ничего вменяемого. Исчез он потому, что был жестко завязан на Xlib. Вместо него в Qt5 появился QPlatformNativeInterface, который, впрочем, вскоре был запрещен и сейчас иммеется только в виде приватного класса/заголовка. В своем проекте, как быстрое решение всё ещё завязанное на Xlib, я просто написал враппер над QX11Info, который в случае Qt5 имеет свою собственную реализаию:
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 <X11/Xlib.h> # include <QtGlobal> #else # include <QX11Info> #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;
Не ручаюсь за логическую верность реализации, так как до конца написания статьи код ещё не был протестирован.
Также у меня имеются нативные обработчики событий, такие как QApplication::x11EventFilter например. В Qt5 конечно же их пришлось переписать. Для этого нам понадобится QAbstractNativeEventFilter и немного знаний программирования xcb (Xlib здесь не сработает так как QPA о нем ничего не знает). В Принципе, переход на xcb не слишком сложен ввиду схожести API этих двух библиотек, но мануалами запастись не помешает. В моем случае реализация была довольно тривиальна: в своем классе приложения рядом с x11EventFilter добавил ещё один метод под названием xcbEventFilter и несколько ifdefов для компиляции только нужного метода. Далее создал класс унаследованный от QAbstractNativeEventFilter и из него просто обработку всех xcb событий перенаправил на наш метод:
#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
Если душе угодно инициализацию приложением можно заменить кастом сингелтона приложения в nativeEventFilter.
Далее плагины. В Qt5 они загружаются другим, несовместимым способом и объявлены должны быть тоже по-другому. Сделана такая неприятная поломка в целях оптимизации (сейчас необязательно грузить весь плагин как полноценную разделяемую библиотеку, что бы убедиться что это вообще Qt плагин и что бы получить из него какую-либо мета информацию, такую как, например, версия программы, с которой данный плагин совместим. подробнее здесь), но чинить тем не менее её всё равно надо.
Для начала, все макросы экспорта наших плагинов делаем условными примерно так:
#ifndef HAVE_QT5 Q_EXPORT_PLUGIN2(myplugin, MyPlugin); #endif
Также в классе плагина условно добавляем новый специфичный для Qt5 макрос Q_PLUGIN_METADATA где-нибудь рядом с Q_INTERFACES но после Q_OBJECT:
#ifdef HAVE_QT5 Q_PLUGIN_METADATA(IID "tld.domain.Project.MyPluginInterface" FILE "myplugin.json") #endif
Часть FILE «myplugin.json» необходима только если нам на самом деле нужны метаданные в плагине, а что касается интерфейса «tld.domain.Project.MyPluginInterface», то это тот же интерфейс, что и в Q_DECLARE_INTERFACE. В моем случае в метаданных будут храниться: версия плагина, минимальная версия самой программы и приоритет загрузки. Также надо не забыть добавить магию с объявлением HAVE_QT5 в файлы проектов плагинов, ну или, как быстрый вариант без магии, использовать #if QT_VERSION >= 0x050000.
В случае же статических плагинов, придется поменять вызов Q_IMPORT_PLUGIN макросов. В качестве параметра они сейчас принимают имя класса плагина, а не то, что было первым параметром в Q_EXPORT_PLUGIN2.
Итак, запуск! И, как следовало ождать, segfault. Мой код ожидает, что QMetaType::Void == 0, но в Qt5 это не так. Отлично, исправляем, запускаем и снова segfault. На этот раз проблема в том, что некоторые типы объявлены в одном месте, а Q_DECLARE_METATYPE для них — в другом. Из-за чего последний, даже при явно включенном хидере с типом, работает неправильно. Я не стал разбираться в чем именно загвоздка, просто перенес Q_DECLARE_METATYPE для типов в их заголовочные файлы. И снова запуск — работает!
Программа запустилась, но работы ещё много. Переход на xcb таки должен быть полноценным, т.е. мой класс X11Info должен быть переписать с использованием xcb. Также надо проверить на работоспособность всё, что было пропатчено, впрочем, как и не пропатченное. Но, я надеюсь, самое сложное уже позади!
Надеюсь мой опыт будет вам полезен. Ниже приведу несколько ссылок, которые мне помогли в решение Qt5 головоломок:
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
ссылка на оригинал статьи http://habrahabr.ru/post/164721/
Добавить комментарий