通过上两篇博客https://blog.csdn.net/Master_Cui/article/details/109093845https://blog.csdn.net/Master_Cui/article/details/109109972

知道了QT如果处理事件以及事件发送、事件队列和事件循环相关的类,但是这些东西是如何串联在一起的呢?下面通过源码分析下事件机制的时序

1.首先从一般的QT的main函数开始

int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;w.show();return a.exec();//进入事件循环
}int QApplication::exec()
{return QGuiApplication::exec();
}int QGuiApplication::exec()
{
#ifndef QT_NO_ACCESSIBILITYQAccessible::setRootObject(qApp);//设置根对象,一般不会调用,因为在创建Widget是,会调用父类的构造函数,会设置对象树的根对象
#endifreturn QCoreApplication::exec();
}int QCoreApplication::exec()
{if (!QCoreApplicationPrivate::checkInstance("exec"))//判断QCoreApplication的实例是否存在,不存在,返回错误码-1,退出return -1;//一般情况下不会返回-1,因为在QCoreApplicationPrivate中的init函数会初始化QCoreApplication::selfQThreadData *threadData = self->d_func()->threadData;//获取QCoreApplication的线程数据if (threadData != QThreadData::current()) {//如果QCoreApplication所在线程和当前主线程的数据不一致,退出qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());return -1;}if (!threadData->eventLoops.isEmpty()) {//如果线程数据中的eventLoops不为空,说明主线程中已经有事件循环对象在运行,退出qWarning("QCoreApplication::exec: The event loop is already running");return -1;//eventLoops是一个容器,存储的是QEventLoop的指针 QStack<QEventLoop *> eventLoops;}threadData->quitNow = false;QEventLoop eventLoop;//创建了一个QEventLoop对象self->d_func()->in_exec = true;self->d_func()->aboutToQuitEmitted = false;int returnCode = eventLoop.exec();//关键代码,调用事件循环对象的execthreadData->quitNow = false;if (self)self->d_func()->execCleanup();//当主事件循环退出时,会调用该函数,该函数内部发射了信号void QCoreApplication::aboutToQuit(),做了一些清理动作return returnCode;
}

可见,上述代码最终调用了QCoreApplication::exec()进入应用程序的主事件循环,QCoreApplication::exec()的实现如下,而在QCoreApplication::exec()中最终又创建了一个QEventLoop对象并调用了其中的exec进入主事件循环。当QEventLoop对象的exec退出时(主事件循环退出),会调用execCleanup进行一些清理工作,关于QEventLoop和QCoreApplication见博客https://blog.csdn.net/Master_Cui/article/details/109093845https://blog.csdn.net/Master_Cui/article/details/109109972

QEventLoop::exec的实现如下

int QEventLoop::exec(ProcessEventsFlags flags)
{Q_D(QEventLoop);//创建一个指向QEventLoopPrivate的指针d//we need to protect from race condition with QThread::exitQMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread.loadAcquire()))->mutex);//防止多线程竞争if (d->threadData->quitNow)return -1;if (d->inExec) {qWarning("QEventLoop::exec: instance %p has already called exec()", this);return -1;}struct LoopReference {//定义一个循环事件数据相关的结构体QEventLoopPrivate *d;QMutexLocker &locker;bool exceptionCaught;LoopReference(QEventLoopPrivate *d, QMutexLocker &locker) : d(d), locker(locker), exceptionCaught(true){d->inExec = true;d->exit.storeRelease(false);++d->threadData->loopLevel;d->threadData->eventLoops.push(d->q_func());//通过d->q_func()获取当前事件循环的指针并添加到当前线程的数据中locker.unlock();}~LoopReference(){if (exceptionCaught) {qWarning("Qt has caught an exception thrown from an event handler. Throwing\n""exceptions from an event handler is not supported in Qt.\n""You must not let any exception whatsoever propagate through Qt code.\n""If that is not possible, in Qt 5 you must at least reimplement\n""QCoreApplication::notify() and catch all exceptions there.\n");}locker.relock();QEventLoop *eventLoop = d->threadData->eventLoops.pop();Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");Q_UNUSED(eventLoop); // --release warningd->inExec = false;--d->threadData->loopLevel;}};LoopReference ref(d, locker);//创建一个LoopReference的对象,用Q_D(QEventLoop)创建的d指针初始化该对象,并将事件循环对象添加到当前的线程数据QCoreApplication *app = QCoreApplication::instance();if (app && app->thread() == thread())QCoreApplication::removePostedEvents(app, QEvent::Quit);// remove posted quit events when entering a new event loop#ifdef Q_OS_WASM// Partial support for nested event loops: Make the runtime throw a JavaSrcript// exception, which returns control to the browser while preserving the C++ stack.// Event processing then continues as normal. The sleep call below never returns.// QTBUG-70185if (d->threadData->loopLevel > 1)emscripten_sleep(1);
#endifwhile (!d->exit.loadAcquire())//判断事件循环是否已经退出processEvents(flags | WaitForMoreEvents | EventLoopExec);//核心代码,调用QEventLoop::processEvents()处理事件,ref.exceptionCaught = false;return d->returnCode.loadRelaxed();
}bool QEventLoop::processEvents(ProcessEventsFlags flags)
{Q_D(QEventLoop);if (!d->threadData->hasEventDispatcher())return false;return d->threadData->eventDispatcher.loadRelaxed()->processEvents(flags);//eventDispatcher是个指针,调用QAbstractEventDispatcher的processEvents
}

上面代码中Q_D是一个宏,定义如下

#define Q_D(Class) Class##Private * const d = d_func()

d_func()的作用就是将QEventLoop的指针转化为QEventLoopPrivate的指针,见博客https://blog.csdn.net/Master_Cui/article/details/109112367

还有这样一句代码

 d->threadData->eventLoops.push(d->q_func());

d是指向QEventLoopPrivate的指针,QEventLoopPrivate的定义如下(在qeventloop_p.h中定义)

class QEventLoopPrivate : public QObjectPrivate
{Q_DECLARE_PUBLIC(QEventLoop)
public:inline QEventLoopPrivate(): inExec(false){returnCode.storeRelaxed(-1);exit.storeRelaxed(true);}//...
};

Q_DECLARE_PUBLIC(QEventLoop)展开就是

#define Q_DECLARE_PUBLIC(Class)                                    \inline Class* q_func() { return static_cast<Class *>(q_ptr); } \inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \friend class Class;

q_func的作用就是将q_ptr转化为对应类的指针,而q_ptr定义在纯虚基类QObjectData中,所以d->q_func()就是获取QEventLoop的指针

上面的代码表明,当调用QEventLoop::exec时,会先定义一个结构体,将QEventLoop的添加操作封装在结构体的构造函数中,然后创建该结构体的对象,之后调用QEventLoop::processEvents进行事件处理,而在QEventLoop::processEvents中,会调用QAbstractEventDispatcher的processEvents

而QAbstractEventDispatcher是一个纯虚基类,具体见https://blog.csdn.net/Master_Cui/article/details/109109972,QAbstractEventDispatcher的子类有很多,在创建事件分发器时,会根据平台信息,创建出不同平台的事件分发器,根据平台特性,进行具体的实现。在Ubuntu18.04下,通过qtcreater调试获得函数的调用顺序如下

所以,最终指向的是子类QEventDispatcherGlib,所以接着会调用QEventDispatcherGlib的processEvents函数

QEventDispatcherGlib::processEvents的实现如下

bool QEventDispatcherGlib::processEvents(QEventLoop::ProcessEventsFlags flags)
{Q_D(QEventDispatcherGlib);//创建QEventDispatcherGlibPrivate的指针const bool canWait = (flags & QEventLoop::WaitForMoreEvents);if (canWait)emit aboutToBlock();elseemit awake();// tell postEventSourcePrepare() and timerSource about any new flagsQEventLoop::ProcessEventsFlags savedFlags = d->timerSource->processEventsFlags;d->timerSource->processEventsFlags = flags;if (!(flags & QEventLoop::EventLoopExec)) {// force timers to be sent at normal priorityd->timerSource->runWithIdlePriority = false;}bool result = g_main_context_iteration(d->mainContext, canWait);while (!result && canWait)result = g_main_context_iteration(d->mainContext, canWait);//核心代码,迭代main_contextd->timerSource->processEventsFlags = savedFlags;if (canWait)emit awake();return result;
}

g_main_context_iteration是glic中主事件循环机制的函数,所以,在Ubuntu18.04,QT的事件机制实际上是采用glic中的事件循环机制来完成的

参考

Qt5.14源码

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

9.QT事件机制源码时序分析(上)相关推荐

  1. 11.QT事件机制源码时序分析(下)

    接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109182406,本文继续解析QCoreApplication::sendEvent和Q ...

  2. 10.QT事件机制源码时序分析(中)

    接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109162220,上篇文章已经说过,在Ubuntu18.04,QT的事件机制实际上是采用 ...

  3. Qt 事件机制源码分析 QApplication exec 源码分析 多图超级详细

    前言: 不熟悉qt 源码结构的 可以先看这一篇 点我点我点我 写qt 的都知道 以下代码, 这段代码究竟的运行机制是怎么样的 咱们一步一步的看 QApplication a(argc, argv);Q ...

  4. React事件机制 - 源码概览(下)

    上篇文档 React事件机制 - 源码概览(上)说到了事件执行阶段的构造合成事件部分,本文接着继续往下分析 批处理合成事件 入口是 runEventsInBatch // runEventsInBat ...

  5. 【保姆级】react17 事件机制源码解析

    写在前面 react17是一个过渡版本,最大的改动莫过于针对_事件机制的重构_.虽然现在react18版本已经发布了,但对事件机制这部分几乎是没有改动的,因此这里依然可以对17版本的这部分源码作一次解 ...

  6. 软件自动更新解决方案及QT实现(源码已上传)

    软件自动更新解决放案及QT实现...1 1 文件的版本控制-XML.2 2 更新程序的实现...2 2.1 界面设置...2 2.2 程序功能...3 2.2.1 下载网络数据...3 2.2.2 X ...

  7. Qt 事件机制,底层实现原理

    [1]事件 事件是可以被控件识别的操作.如按下确定按钮.选择某个单选按钮或复选框. 每种控件有自己可识别的事件,如窗体的加载.单击.双击等事件,编辑框(文本框)的文本改变事件等等. 事件就是用户对窗口 ...

  8. Android View系列(二):事件分发机制源码解析

    概述 在介绍点击事件规则之前,我们需要知道我们分析的是MotionEvent,即点击事件,所谓的事件分发就是对MotionEvent事件的分发过程,即当一个MotionEvent生成以后,系统需要把这 ...

  9. android SDK-25事件分发机制--源码正确解析

    android SDK-25事件分发机制–源码正确解析 Android 事件分发分为View和ViewGroup的事件分发,ViewGroup比View过一个拦截判断,viewgroup可以拦截事件, ...

最新文章

  1. Windows 系统下使用grep 命令
  2. 税友报税软件让修改服务器地址,税友报税软件让修改服务器地址
  3. AI的双刃剑:拍照搜题与协助作弊
  4. Qt中的QTimer
  5. 用一张白纸推导出 RAFT 算法
  6. python连接oracle详细教程_[宜配屋]听图阁
  7. Linux虚拟机安装应用程序提示Graphical installers are not supported by the vm
  8. python天下第一什么梗_「PHP 是最好的语言」这个梗是怎么来的?
  9. java io读书笔记(8)FileInputStream/FileOutputStream的应用
  10. JDK ThreadLocal解析
  11. 严蔚敏数据结构C语言版教材精讲考研真题串讲视频
  12. ie8 升级页面html,IE9及以下浏览器升级提示
  13. Linux搭建Redis集群(搭建集群必看)
  14. cadlisp框选打印_在CAD中实现批量打印
  15. 异常解决 java.lang.UnsupportedOperationException: Required method destroyItem was not overridden
  16. 就业形势严峻,应届生应该如何做好职业规划?
  17. NLP 实战(11): CSDN Daily,兼谈技术写作的问题
  18. log4j2配置定期清理日志文件
  19. springboot中如何使用RedisTemplate存储实体对象
  20. 敏捷模式下的研发产能度量

热门文章

  1. Infortrend推出超大容量存储解决方案
  2. 持续交付流水线的敏捷利器:环境配置管理与应用部署自动化
  3. 1、Reactive Extensions for .NET(译)
  4. KS008基于SSM的新闻发布系统
  5. 基于SSH实现医院在线挂号系统
  6. SpringSecurity入门01(含源码)
  7. 微信小程序接入,https服务器搭建和调试
  8. 实验吧--web--天下武功唯快不破
  9. 【转载】cookie
  10. Linux系统下的文件管理类常命令及使用方式