Qt源码分析:Qt程序是怎么运行起来的?

2024-01-08 20:22:08

一、从exec()谈起

一个标准的Qt-gui程序,在启动时我们会coding如下几行简洁的代码:

#include "widget.h"

#include <QApplication>

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

在这里我们首先考虑第一个问题,如果主程序中没有调用 a.exec(),在编译运行时会发生什么?
~~ one thousand years later~~
对,也许你已经非常清楚了,你定义的widget窗口竟然没有显示,或者你以自己如同高速摄像机的神器双眼,炯炯侠般捕获到窗口突然的闪现然后消失。这时候你明白,a.exec() 是保证窗口程序不立即退出的重要保证。
你的猜想很对,同时我要告诉你的是,a.exec() 做的工作显然比这要多很多。

二、源码分析

    QObject
		|__QCoreApplication					   源码路径:qtbase\src\corelib\kernel\qcoreapplication.cpp
					|__QGuiApplication            源码路径:qtbase\src\gui\kernel\qguiapplication.cpp
								|_QApplication       源码路径: qtbase\src\widgets\kernel\qapplication.cpp

		        图2.1   QApplication 继承类图

我们采用源码调试的方式,倒推来看我们的关注点。

1、 QApplication a(argc, argv);

对象实例化,构造函数如下:

#ifdef Q_QDOC
QApplication::QApplication(int &argc, char **argv)
#else
QApplication::QApplication(int &argc, char **argv, int _internal)
#endif
    : QGuiApplication(*new QApplicationPrivate(argc, argv, _internal))
{
    Q_D(QApplication);
    d->init();	/// 资源初始化
}

2、 a.exec()

int QCoreApplication::exec()
{
	// [1]
    if (!QCoreApplicationPrivate::checkInstance("exec"))
        return -1;

	// [2]
    QThreadData *threadData = self->d_func()->threadData;
    if (threadData != QThreadData::current()) {
        qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
        return -1;
    }
	// [3]
    if (!threadData->eventLoops.isEmpty()) {
        qWarning("QCoreApplication::exec: The event loop is already running");
        return -1;
    }

	// [4]
    threadData->quitNow = false;
    QEventLoop eventLoop;
    self->d_func()->in_exec = true;
    self->d_func()->aboutToQuitEmitted = false;
    int returnCode = eventLoop.exec();
    threadData->quitNow = false;

	// [5]
    if (self)
        self->d_func()->execCleanup();

    return returnCode;
}

下面掰开了,揉碎了,逐行扒光了解读一番:


[1]

if (!QCoreApplicationPrivate::checkInstance("exec"))
        return -1;

判断QCoreApplication 对象是否已经实例化,否则打印输出错误信息并退出。

bool QCoreApplicationPrivate::checkInstance(const char *function)
{
    bool b = (QCoreApplication::self != nullptr);
    if (!b)
        qWarning("QApplication::%s: Please instantiate the QApplication object first", function);
    return b;
}

[2]


 QThreadData *threadData = self->d_func()->threadData;
    if (threadData != QThreadData::current()) {
        qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
        return -1;
    }

self->d_func() ,有点眼熟啊,这不就是Qt的孪生指针刺客 :q指针和d指针 中d指针么?对这一块内容和知识不太熟悉的朋友,回头去看看博主曾经写过的相关内容 《Qt : d指针和q指针?》

我们回过头看看QCoreApplication是如何申明的:

class Q_CORE_EXPORT QCoreApplication
#ifndef QT_NO_QOBJECT
    : public QObject
#endif
{
 Q_DECLARE_PRIVATE(QCoreApplication)
 protected:
    QCoreApplication(QCoreApplicationPrivate &p);
	// ...
#ifdef QT_NO_QOBJECT
    QScopedPointer<QCoreApplicationPrivate> d_ptr;
#endif

// ...
}
#define Q_CAST_IGNORE_ALIGN(body) QT_WARNING_PUSH QT_WARNING_DISABLE_GCC("-Wcast-align") body QT_WARNING_POP
#define Q_DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr));) } \
    inline const Class##Private* d_func() const \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr));) } \
    friend class Class##Private;

#define Q_DECLARE_PRIVATE_D(Dptr, Class) \
    inline Class##Private* d_func() \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<Class##Private *>(qGetPtrHelper(Dptr));) } \
    inline const Class##Private* d_func() const \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const Class##Private *>(qGetPtrHelper(Dptr));) } \
    friend class Class##Private;

ok,读到这儿,我们先收起更深的好奇之心,我们拿到了我们暂时需要知道的, self->d_func() 获取到了 QCoreApplicationPrivate 的成员变量指针,即 QScopedPointer<QCoreApplicationPrivate> d_ptr;

class Q_CORE_EXPORT QCoreApplicationPrivate
#ifndef QT_NO_QOBJECT
    : public QObjectPrivate
#endif
{
 // ...
};
class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
    Q_DECLARE_PUBLIC(QObject)
public:
	// ....
    ExtraData *extraData;    // extra data set by the user
    QThreadData *threadData; // id of the thread that owns the object
};

喔嚯.回过头来再看这一段.

  QThreadData *threadData = self->d_func()->threadData;
    if (threadData != QThreadData::current()) {
        qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
        return -1;
    }

这是是干啥啊,获取当前对象所在线程的id,哪个对象?QCoreApplication 对象或者它的子类对象QApplication a; 并判断当前线程id与对象所在线程id是否相同,不同则会给出错误提示并退出。

所以,这里有个知识点我们必须记住:QCoreApplication 及其子类,仅且只能在主线程中实例化!!!


[3]

我们先熟悉下 QThreadData 类

class QThreadData
{
public:
    QThreadData(int initialRefCount = 1);
    ~QThreadData();

    static Q_AUTOTEST_EXPORT QThreadData *current(bool createIfNecessary = true);
#ifdef Q_OS_WINRT
    static void setMainThread();
#endif
    static void clearCurrentThreadData();
    static QThreadData *get2(QThread *thread)
    { Q_ASSERT_X(thread != nullptr, "QThread", "internal error"); return thread->d_func()->data; }


    void ref();
    void deref();
    inline bool hasEventDispatcher() const
    { return eventDispatcher.loadRelaxed() != nullptr; }
    QAbstractEventDispatcher *createEventDispatcher();
    QAbstractEventDispatcher *ensureEventDispatcher()
    {
        QAbstractEventDispatcher *ed = eventDispatcher.loadRelaxed();
        if (Q_LIKELY(ed))
            return ed;
        return createEventDispatcher();
    }

    bool canWaitLocked()
    {
        QMutexLocker locker(&postEventList.mutex);
        return canWait;
    }

    // This class provides per-thread (by way of being a QThreadData
    // member) storage for qFlagLocation()
    class FlaggedDebugSignatures
    {
        static const uint Count = 2;

        uint idx;
        const char* locations[Count];

    public:
        FlaggedDebugSignatures() : idx(0)
        { std::fill_n(locations, Count, static_cast<char*>(nullptr)); }

        void store(const char* method)
        { locations[idx++ % Count] = method; }

        bool contains(const char *method) const
        { return std::find(locations, locations + Count, method) != locations + Count; }
    };

private:
    QAtomicInt _ref;

public:
    int loopLevel;
    int scopeLevel;

    QStack<QEventLoop *> eventLoops;    事件循环的主角来了
    QPostEventList postEventList;
    QAtomicPointer<QThread> thread;
    QAtomicPointer<void> threadId;
    QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;
    QVector<void *> tls;
    FlaggedDebugSignatures flaggedSignatures;

    bool quitNow;
    bool canWait;
    bool isAdopted;
    bool requiresCoreApplication;
};

QStack<QEventLoop *> eventLoops;QThreadData 类中的一个成员变量,它是一个栈,用于存储 QEventLoop 对象的指针。

Qt 中,每个 QThread 都有一个与之关联的事件循环(QEventLoop)。事件循环是 GUI 程序的核心,它用于接收和处理各种事件,如用户输入、定时器事件、网络事件等。

然而,一个线程不仅可以有一个主事件循环,还可以有一个或多个嵌套的事件循环。例如,当你在一个线程中调用 QEventLoop::exec() 时,你就创建了一个新的事件循环,并将其推入到 eventLoops 栈中。当 QEventLoop::exit() 被调用时,当前的事件循环会结束,并从 eventLoops 栈中弹出。

eventLoops 栈的顶部始终是当前线程的当前事件循环。这意味着,当你在一个线程中调用 QCoreApplication::processEvents() 时,Qt 会处理 eventLoops 栈顶的事件循环中的事件。

总的来说,QStack<QEventLoop *> eventLoops 成员变量用于存储和管理一个线程中的所有事件循环。

if (!threadData->eventLoops.isEmpty()) {
        qWarning("QCoreApplication::exec: The event loop is already running");
        return -1;
    }

这段,如果栈容器中的事件数量为空,则说明QCoreApplication事件循环还没有运行,否则,说明已经入事件循环状态。如果没有进入,那么继续往下走。


	/// 将几个标识状态对应初始化 
	threadData->quitNow = false;
    
	QEventLoop eventLoop;
    self->d_func()->in_exec = true;
    self->d_func()->aboutToQuitEmitted = false;
    
    /// 执行主线程的事件循环
    int returnCode = eventLoop.exec();
    threadData->quitNow = false;

    if (self)
        self->d_func()->execCleanup();
int QEventLoop::exec(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    //we need to protect from race condition with QThread::exit
    QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread.loadAcquire()))->mutex);
    if (d->threadData->quitNow) // 已经设置为false
        return -1;

    if (d->inExec) {  // 已经设置为true
        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;   /// 线程等级提升+1
            d->threadData->eventLoops.push(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 warning
            d->inExec = false;
            --d->threadData->loopLevel;
        }
    };
    LoopReference ref(d, locker);

    // 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
    // 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-70185
    if (d->threadData->loopLevel > 1)
        emscripten_sleep(1);
#endif

	/**************************************************************************/
    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);  /// 加入到事件循环stack中
   /**************************************************************************/
    ref.exceptionCaught = false;
    return d->returnCode.loadRelaxed();
}

文章来源:https://blog.csdn.net/weixin_39568531/article/details/135452420
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。