前言
Qt是一个跨平台的C++框架,各位小伙伴在使用Qt的时候有没有想过Qt是怎么实现跨平台的呢?Qt为了实现跨平台做了哪些工作。现在我们就来探讨一下Qt跨平台的基础,QPA插件。文章有点长,请各位小伙伴耐心看完。
QPA简介
QPA的全称是Qt Platform Abstraction,即Qt平台抽象层。在Qt5中替代QWS(Qt Window System)。QPA插件是通过对各种QPlatform* 类进行子类化来实现的。有几个根类,例如用于窗口系统集成的QPlatformIntegration和QPlatformWindow,以及用于更深入的平台主题和集成的QPlatformTheme。QStyle不是QPA的一部分。QPA类没有源代码或二进制兼容性保证,这意味着平台插件只能保证与它开发的Qt版本一起使用。
为什么要使用QPA
Qt一直宣称是跨平台的,但是Qt的底层却不是跨平台,在Qt底层需要调用各个系统的api完成。以QWidget为例,在Qt5中,除了qwidget.cpp还有qwidget_win.cpp、qwidget_x11.cpp、qwidget_wince.cpp等一系列各平台相关的文件。在kernel.pri中使用win32 unix:x11等包含不同的cpp文件,在编译Qt的时候,不同的平台的代码就参与编译。下面是Qt4中源码kernel.pri的一段。
win32 {
DEFINES += QT_NO_DIRECTDRAW
HEADERS += \
kernel/qwinnativepangesturerecognizer_win_p.h
SOURCES += \
kernel/qapplication_win.cpp \
kernel/qclipboard_win.cpp \
kernel/qcursor_win.cpp \
kernel/qdesktopwidget_win.cpp \
kernel/qdnd_win.cpp \
kernel/qmime_win.cpp \
kernel/qsound_win.cpp \
kernel/qwidget_win.cpp \
kernel/qole_win.cpp \
kernel/qkeymapper_win.cpp \
kernel/qwinnativepangesturerecognizer_win.cpp
!contains(DEFINES, QT_NO_DIRECTDRAW):LIBS += ddraw.lib
}
symbian {
exists($${EPOCROOT}epoc32/include/platform/mw/akntranseffect.h): DEFINES += QT_SYMBIAN_HAVE_AKNTRANSEFFECT_H
SOURCES += \
kernel/qapplication_s60.cpp \
kernel/qeventdispatcher_s60.cpp \
kernel/qwidget_s60.cpp \
kernel/qcursor_s60.cpp \
kernel/qdesktopwidget_s60.cpp \
kernel/qkeymapper_s60.cpp\
kernel/qclipboard_s60.cpp\
kernel/qdnd_s60.cpp \
kernel/qsound_s60.cpp
HEADERS += \
kernel/qt_s60_p.h \
kernel/qeventdispatcher_s60_p.h
LIBS += -lbafl -lestor
INCLUDEPATH += $$MW_LAYER_SYSTEMINCLUDE
INCLUDEPATH += ../3rdparty/s60
contains(QT_CONFIG, s60) {
SOURCES += kernel/qsoftkeymanager_s60.cpp
HEADERS += kernel/qsoftkeymanager_s60_p.h
}
}
unix:x11 {
INCLUDEPATH += ../3rdparty/xorg
HEADERS += \
kernel/qx11embed_x11.h \
kernel/qx11info_x11.h \
kernel/qkde_p.h
SOURCES += \
kernel/qapplication_x11.cpp \
kernel/qclipboard_x11.cpp \
kernel/qcursor_x11.cpp \
kernel/qdnd_x11.cpp \
kernel/qdesktopwidget_x11.cpp \
kernel/qmotifdnd_x11.cpp \
kernel/qsound_x11.cpp \
kernel/qwidget_x11.cpp \
kernel/qwidgetcreate_x11.cpp \
kernel/qx11embed_x11.cpp \
kernel/qx11info_x11.cpp \
kernel/qkeymapper_x11.cpp \
kernel/qkde.cpp
contains(QT_CONFIG, glib) {
SOURCES += \
kernel/qguieventdispatcher_glib.cpp
HEADERS += \
kernel/qguieventdispatcher_glib_p.h
QMAKE_CXXFLAGS += $$QT_CFLAGS_GLIB
LIBS_PRIVATE +=$$QT_LIBS_GLIB
}
SOURCES += \
kernel/qeventdispatcher_x11.cpp
HEADERS += \
kernel/qeventdispatcher_x11_p.h
}
虽然这样也可以实现跨平台,但是这一切都使得将Qt移植到一个新的窗口系统变的不太容易。例如我们要移植Qt到新的系统MyOS上,就需要建一系列的xxxx_myos.cpp文件。为了解决上面的问题,Qt5引入了QPA来替代,这样我们在移植Qt到MyOS上的时候就只需要新建一个MyOS的QPA插件。QPA插件在$$QTDIR/plugins/platforms下。在windows上我们可以看到qwindows.dll,在linux下有libxcb.so,他们就是QPA插件。
QPA插件的使用
我们不会直接使用QPA插件,QPA插件时Qt自己调用的,建一个简单的列子来说明
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
我们都知道QApplication是继承QGuiApplication,注意QApplication a(argc, argv),该方法直接调用QApplicationPrivate::init(),(不知道QApplicationPrivate的伙伴们可以查阅Qt-D指针和Q指针及使用)QApplicationPrivate::init()中调用
QGuiApplicationPrivate::init();在QGuiApplicationPrivate::init里面会创建QPlatformIntegration实例。QPlatformIntegration就是Qt上层与各个平台底层的桥梁,通过他或者他提供的一些接口返回的实例对象来实现平台功能。查看Qt的源码可以知道Qt先获取的QPA插件的路径platformPluginPath,该值一般为空,QPA插件的名称platformName,该值是在编译Qt的时候确定的,
QT_QPA_DEFAULT_PLATFORM_NAME在qtgui-config.h中定义,比如在windows上在qtgui-config.h会有#define QT_QPA_DEFAULT_PLATFORM_NAME "windows"
void QGuiApplicationPrivate::createPlatformIntegration()
{
QHighDpiScaling::initHighDpiScaling();
// Load the platform integration
QS