Qt源码解析4-事件系统-事件循环原理源码分析

Qt源码解析 索引

走读源码,从走读的最简单的测试程序为切入点。开始分析事件的exec执行函数。

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    ...
    return a.exec();
}

.... 表示 省略的代码段

1、QApplication::exec

进入主事件循环并等待,直到调用exit(),然后返回设置为exit()的值(如果通过quit()调用exit(),则值 为0)。

需要调用此函数来启动事件处理。主要的事件循环从窗口系统接收事件并将其分派给应用程序部件。

通常,在调用exec()之前不能进行任何用户交互。作为一个特殊情况下,在调用Exec()之前,如QMessageBox的模态部件可以使用,因为模态小部件调用Exec()来启动本地事件循环。

使您的应用程序执行空闲处理,即执行特殊的函数,当没有挂起事件时,使用一个超时为0的QTimer。使用processEvents()可以实现更高级的空闲处理方案。

我们建议您将清理代码连接到QCoreApplication::aboutToQuit()信号,而不是把它放在你的应用程序的main()函数。这是因为,在某些平台上QApplication::exec()调用可能不会返回。例如,Windows操作系统 平台,当用户注销时,系统终止进程在Qt关闭所有顶层窗口。因此,并在在QApplication::exec()调用之后,没有方法能保证应用程序将有时间退出其事件循环,并且执行在main()函数之后的代码。

1.1、QApplication

QApplication专门为 QGuiApplication提供了一些基于QWidget的应用程序所需的功能。它处理小部件特定的初始化、完成。

对于任何使用 Qt 的 GUI 应用程序,只有一个QApplication对象,无论应用程序在任何给定时间是否有 0、1、2 或更多窗口。 对于非基于QWidget的 Qt 应用程序,请改用QGuiApplication,因为它不依赖于QtWidgets库。

一些 GUI 应用程序提供了一种特殊的批处理模式,即。为执行任务提供命令行参数,无需人工干预。 在这种非 GUI 模式下,实例化一个普通的QCoreApplication通常就足够了,以避免不必要地初始化图形用户界面所需的资源。

以下示例显示了如何动态创建适当类型的应用程序实例:

QCoreApplication* createApplication(int &argc, char *argv[])
{
    for (int i = 1; i < argc; ++i) {
        if (!qstrcmp(argv[i], "-no-gui"))
            return new QCoreApplication(argc, argv);
    }
    return new QApplication(argc, argv);
}
​
int main(int argc, char* argv[])
{
    QScopedPointer<QCoreApplication> app(createApplication(argc, argv));
​
    if (qobject_cast<QApplication *>(app.data())) {
       // start GUI version...
    } else {
       // start non-GUI version...
    }
​
    return app->exec();
}

可以通过instance () 函数访问 QApplication 对象,该函数返回与全局qApp指针等效的指针。

QApplication 的主要职责范围是:

  • 它使用用户的桌面设置初始化应用程序,例如palette(), font() ,doubleClickInterval()。它会跟踪这些属性,以防用户全局更改桌面,例如通过某种控制面板。

  • 它执行事件处理,这意味着它从底层窗口系统接收事件并将它们分派给相关的小部件。通过使用sendEvent () 和postEvent (),您可以将自己的事件发送到小部件。

  • 它解析常见的命令行参数并相应地设置其内部状态。有关更多详细信息,请参阅下面的 constructor documentation文档。

  • 它定义了应用程序的外观和感觉,它被封装在一个QStyle对象中。这可以在运行时使用setStyle () 进行更改。

  • 它通过translate ()提供对用户可见的字符串的本地化。

  • 它提供了一些神奇的对象,如desktop()clipboard()

  • 它知道应用程序的窗口。可以使用widgetAt ()询问哪个widget在某个位置,获取topLevelWidgets ()closeAllWindows ()的列表等。

  • 它管理应用程序的鼠标光标处理,见setOverrideCursor ()

由于 QApplication对象进行了如此多的初始化,因此必须在创建与用户界面相关的任何其他对象之前创建它。QApplication 还处理常见的命令行参数。因此,在应用程序本身进行任何解释或argv修改之前创建它通常是一个好方法。

1.2 QGuiApplication

QGuiApplication 包含主事件循环,来自窗口系统和其他来源的所有事件都在其中被处理和调度。它还处理应用程序的初始化和终结,并提供会话管理。此外,QGuiApplication 处理大部分系统范围和应用程序范围的设置。

1.3 QCoreApplication

非 GUI 应用程序使用此类来提供它们的事件循环。对于使用 Qt 的非 GUI 应用程序,应该只有一个 QCoreApplication 对象。对于 GUI 应用程序,请参阅QGuiApplication。对于使用 Qt Widgets 模块的应用程序,请参阅QApplication。

QCoreApplication 包含主事件循环,来自操作系统(例如,定时器和网络事件)和其他来源的所有事件都在其中被处理和分派。它还处理应用程序的初始化和完成,以及系统范围和应用程序范围的设置。

1.3.1事件循环和事件处理

事件循环从调用exec () 开始。长时间运行的操作可以调用processEvents () 以保持应用程序响应。 一般来说,我们建议您尽早在main()函数中创建 QCoreApplication、 QGuiApplication、QApplication对象。 exec () 直到事件循环退出才会返回;例如,当调用quit () 时。

还提供了几个静态便利功能。QCoreApplication 对象可从instance () 获得。事件可以使用 sendEvent() 发送或使用postEvent ( ) 发布到事件队列。 挂起的事件可以使用removePostedEvents () 删除或使用sendPostedEvents ()调度。

该类提供了一个quit () 槽和一个aboutToQuit () 信号。

1.3.2应用程序和库路径

一个应用程序有一个applicationDirPath () 和一个applicationFilePath ()。库路径(请参阅QLibrary)可以使用libraryPaths () 检索并由setLibraryPaths ()、addLibraryPath () 和removeLibraryPath () 操作。

1.3.3国际化和翻译

可以使用installTranslator () 和removeTranslator () 添加或删除翻译文件。 应用程序字符串可以使用translate () 进行翻译。QObject::tr () 和 QObject::trUtf8() 函数是根据translate ( ) 实现的。

2、源码分析

最终都会调用QCoreApplication::exec()

QTDIR/src/corelib/kernel/qcoreapplication.cpp

int QCoreApplication::exec()
{
    //Step0:判断状态
    if (!QCoreApplicationPrivate::checkInstance("exec"))//单例是否初始化
        return -1;
    //d_func()具体使用,参见“源码解析1-D指针.md”,通过Q_DECLARE_PRIVATE(QCoreApplication)定义
    QThreadData *threadData = self->d_func()->threadData;//是都在main函数中初始化
    if (threadData != QThreadData::current()) {
        qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
        return -1;
    }
    if (!threadData->eventLoops.isEmpty()) {//事件循环是否已经启动
        qWarning("QCoreApplication::exec: The event loop is already running");
        return -1;
    }
    //Step1:创建事件循环
    threadData->quitNow = false;
    QEventLoop eventLoop;
    self->d_func()->in_exec = true;
    self->d_func()->aboutToQuitEmitted = false;
    int returnCode = eventLoop.exec();
    threadData->quitNow = false;
    //Step2:程序退出清理
    if (self)
        self->d_func()->execCleanup();
​
    return returnCode;
}

//Step1:创建事件循环代码QTDIR\qtbase\src\corelib\kernel\qeventloop.cpp

int QEventLoop::exec(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    //Step0:参数判断
    //we need to protect from race condition with QThread::exit
    QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread))->mutex);
    if (d->threadData->quitNow)
        return -1;
​
    if (d->inExec) {
        qWarning("QEventLoop::exec: instance %p has already called exec()", this);
        return -1;
    }
    //Step1:添加事件循环
    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());//添加事件对象到事件循环堆栈
            locker.unlock();
        }
​
        ~LoopReference()
        {
            if (exceptionCaught) {
                ....
            }
            locker.relock();
            QEventLoop *eventLoop = d->threadData->eventLoops.pop();
            Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
            Q_UNUSED(eventLoop); // --release warning
            d->inExec = false;
            --d->threadData->loopLevel;
        }
    };
    LoopReference ref(d, locker);
    /*Step3:当创建新的事件循环,表示进入新的循环事件,需要将“退出事件”移除,否则事件循环退出,新添加事件没有意义*/
    // remove posted quit events when entering a new event loop
    QCoreApplication *app = QCoreApplication::instance();
    if (app && app->thread() == thread())
        QCoreApplication::removePostedEvents(app, QEvent::Quit);
​
#ifdef Q_OS_WASM
    ....
#endif
    /*Step4:事件循环主体,processEvents是QAbstractEventDispatcher::processEvents()的一个简单包装,  */
    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);
​
    ref.exceptionCaught = false;
    return d->returnCode.load();
}
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    if (!d->threadData->hasEventDispatcher())
        return false;
    /*load()是QAtomicPointer类型的函数,原子操作类。QAbstractEventDispatcher是一个纯虚函数,window平台具体实现为QEventDispatcherWin32*/
    return d->threadData->eventDispatcher.load()->processEvents(flags);
}

此处用到的一个重要的变量threadData,QThreadData源码位置QTDIR\qtbase\src\corelib\thread\qthread_p.h

class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
    ....
    QThreadData *threadData; // id of the thread that owns the object
    ....
}

QAbstractEventDispatcher事件和具体操作系统有关,先不分析。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

道格拉斯范朋克

播种花生牛奶自留田

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值