Qt程序提供了事件循环机制,那么它是怎么工作的,工作原理又是什么呢?让我们通过qt源码揭开它神秘的面纱。
在开始之前,我们先思考下面几个问题:
1.事件循环是何时,以及如何启动的?
2.对于Windows,Qt是如何捕获事件(如:鼠标事件,键盘按键事件)?如何对捕获到的事件进行处理的?
3.如何将Windows消息转换为QEvent?
4.用户自定义的事件又是如何被处理的?
一、Windows消息机制简介
在Windows操作系统,所有的WIN32程序都建立在消息循环的基础之上,windows的消息机制可以分为以下几大步:
- 注册窗口过程处理函数
- 创建窗体
- show窗体
- 获取消息
- 翻译消息
- 分发消息至窗口处理函数
二、事件循环初始化
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
首先实例化QApplication对象,在构造QApplication时初始化threadData,如下所示:
父对象如果存在则使用父对象的threadData,表明和父对象使用同一个工作线程处理事件,反之通过QThreadData::current()拿到当前线程threadData。对于主事件循环来说,则通过QThreadData::current()拿到当前主线程,通过主线程处理事件。
class QThreadData
{
public:
QThreadData(int initialRefCount = 1);
~QThreadData();
static Q_AUTOTEST_EXPORT QThreadData *current(bool createIfNecessary = true);
/**
* 省略 .......
*/
public:
int loopLevel;
int scopeLevel;
QStack<QEventLoop *> eventLoops;
QPostEventList postEventList; // 待处理的事件列表
QAtomicPointer<QThread> thread; // 工作线程
QAtomicPointer<void> threadId; // 工作线程Id
QAtomicPointer<QAbstractEventDispatcher> eventDispatcher; // 事件分发者,在不同平台下创建不同的事件分发者
QVector<void *> tls;
FlaggedDebugSignatures flaggedSignatures;
};
以windows为例,eventDispatcher的初始化:
事件分发者(eventDispatcher),初始化调用堆栈如上
Step 1
// src\corelib\kernel\qcoreapplication.cpp
QCoreApplication::QCoreApplication(int &argc, char **argv
#ifndef Q_QDOC
, int _internal
#endif
)
#ifdef QT_NO_QOBJECT
: d_ptr(new QCoreApplicationPrivate(argc, argv, _internal))
#else
: QObject(*new QCoreApplicationPrivate(argc, argv, _internal))
#endif
{
d_func()->q_ptr = this;
d_func()->init(); // 调用QCoreApplicationPrivate的init函数
#ifndef QT_NO_QOBJECT
QCoreApplicationPrivate::eventDispatcher->startingUp();
#endif
}
程序在启动时,通过main函数实例化QApplication对象。在QCoreApplication构造函数中调用QCoreApplicationPrivate的init函数初始参数
Step 2
// src\corelib\kernel\qcoreapplication.cpp
void QCoreApplicationPrivate::init()
{
/**
* 省略 .......
*/
auto thisThreadData = threadData.loadRelaxed();
eventDispatcher = thisThreadData->eventDispatcher.loadRelaxed();
// otherwise we create one
if (!eventDispatcher)
createEventDispatcher(); // 调用createEventDispatcher创建事件分发者
Q_ASSERT(eventDispatcher);
if (!eventDispatcher->parent()) {
eventDispatcher->moveToThread(thisThreadData->thread.loadAcquire());
eventDispatcher->setParent(q);
}
thisThreadData->eventDispatcher = eventDispatcher; // 事件分发者赋值给threadData的eventDispatcher。当threadData.postEventList列表中有新加入的事件时,需要借助事件分发者向外投递一个消息,处理此事件
eventDispatcherReady();
/**
* 省略 .......
*/
}
在QCoreApplicationPrivate的init函数中,如果事件分发者没有创建则通过createEventDispatcher虚方发创建不同平台的事件分发者。
Step 3
// src\widgets\kernel\qapplication.cpp
void QApplicationPrivate::createEventDispatcher()
{
QGuiApplicationPrivate::createEventDispatcher();
}
// src\gui\kernel\qguiapplication.cpp
void QGuiApplicationPrivate::createEventDispatcher()
{
Q_ASSERT(!eventDispatcher);
if (platform_integration == nullptr)
createPlatformIntegration(); // 创建平台
// The platform integration should not mess with the event dispatcher
Q_ASSERT(!eventDispatcher);
eventDispatcher = platform_integration->createEventDispatcher(); // 通过对应平台的事件分发者
}
上段代码需要注意的是createEventDispatcher是一个虚方法,并且在每个类中都有实现。在此处则调用是QApplicationPrivate的该方法。在此处创建QWindowsIntegration以及QWindowsGuiEventDispatcher
Step 4
// src\gui\kernel\qguiapplication.cpp
void QGuiApplicationPrivate::createPlatformIntegration()
{
QHighDpiScaling::initHighDpiScaling();
// Load the platform integration
QString platformPluginPath = QString::fromLocal8Bit(qgetenv("QT_QPA_PLATFORM_PLUGIN_PATH"));
/**
* 代码省略 .......
* 这部分代码通过从环境变量或者从命令行参数中解析平台名称,插件路径,插件名称
*/
init_platform(QLatin1String(platformName), platformPluginPath, platformThemeName, argc, argv); // 创建平台插件
if (!icon.isEmpty())
forcedWindowIcon = QDir::isAbsolutePath(icon) ? QIcon(icon) : QIcon::fromTheme(icon);
}
static void init_platform(const QString &pluginNamesWithArguments, const QString &platformPluginPath, const QString &platformThemeName, int &argc, char **argv)
{
QStringList plugins = pluginNamesWithArguments.split(QLatin1Char(';'));
QStringList platformArguments;
QStringList availablePlugins = QPlatformIntegrationFactory::keys(platformPluginPath);
for (const auto &pluginArgument : plugins) {
// Split into platform name and arguments
QStringList arguments = pluginArgument.split(QLatin1Char(':'));
const QString name = arguments.takeFirst().toLower();
QString argumentsKey = name;
argumentsKey[0] = argumentsKey.at(0).toUpper();
arguments.append(QLibraryInfo::platformPluginArguments(argumentsKey));
// Create the platform integration.
QGuiApplicationPrivate::platform_integration = QPlatformIntegrationFactory::create(name, arguments, argc, argv, platformPluginPath); // 通过插件工具,创建平台插件
if (Q_UNLIKELY(!QGuiApplicationPrivate::platform_integration)) {
if (availablePlugins.contains(name)) {
qCInfo(lcQpaPluginLoading).nospace().noquote()
<< "Could not load the Qt platform plugin \"" << name << "\" in \""
<< QDir::toNativeSeparators(platformPluginPath) << "\" even though it was found.";
} else {
qCWarning(lcQpaPluginLoading).nospace().noquote()
<< "Could not find the Qt platform plugin \"" << name << "\" in \""
<< QDir::toNativeSeparators(platformPluginPath) << "\"";
}
} else {
QGuiApplicationPrivate::platform_name = new QString(name);
platformArguments = arguments;
break;
}
}
/**
* 代码省略 .......
*/
}
从环境变量或者从命令行参数中解析平台名称,插件路径,插件名称。将插件路径下的所有插件放入到列表中,遍历插件列表加载对应平台的插件。如在windows上则加载的是qwindows.dll
插件位于安装目录下如:C:\Qt\5.15.2\msvc2019_64\plugins\platforms
windows下qt程序包插件位于执行目录下的platforms目录下:
在linux平台下,通过QT_QPA_PLATFORM_PLUGIN_PATH环境变量加载平台插件(因为我是在交叉编译环境上运行,所以是qeglfs):
Step 5
// src\gui\kernel\qplatformintegrationfactory.cpp
QPlatformIntegration *QPlatformIntegrationFactory::create(const QString &platform, const QStringList ¶mList, int &argc, char **argv, const QString &platformPluginPath)
{
#if QT_CONFIG(library)
// Try loading the plugin from platformPluginPath first:
if (!platformPluginPath.isEmpty()) {
QCoreApplication::addLibraryPath(platformPluginPath);