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

一、从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_QOBJECTQScopedPointer<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 userQThreadData *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_WINRTstatic void setMainThread();
#endifstatic 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::exitQMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread.loadAcquire()))->mutex);if (d->threadData->quitNow) // 已经设置为falsereturn -1;if (d->inExec) {  // 已经设置为trueqWarning("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;   /// 线程等级提升+1d->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 warningd->inExec = false;--d->threadData->loopLevel;}};LoopReference ref(d, locker);// remove posted quit events when entering a new event loopQCoreApplication *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-70185if (d->threadData->loopLevel > 1)emscripten_sleep(1);
#endif/**************************************************************************/while (!d->exit.loadAcquire())processEvents(flags | WaitForMoreEvents | EventLoopExec);  /// 加入到事件循环stack中/**************************************************************************/ref.exceptionCaught = false;return d->returnCode.loadRelaxed();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/616934.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯单片机组备赛——蜂鸣器和继电器的基本控制

文章目录 一、蜂鸣器和继电器电路介绍二、题目与答案2.1 题目2.2 答案2.3 重点函数解析 一、蜂鸣器和继电器电路介绍 可以发现两个电路一端都接着VCC&#xff0c;所以我们只要给另一端接上低电平就可以让蜂鸣器和继电器进行工作。与操作LED类似&#xff0c;只不过换了一个74HC5…

爬虫之使用代理

爬虫—使用代理 1. 为什么使用代理 1.1 让服务器以为不是同一个客户端在请求 1.2 防止我们的真实地址被泄漏&#xff0c;防止被追究 2. 理解使用代理的过程 3. 理解正向代理和反向代理的区别 通过上图可以看出&#xff1a; 正向代理&#xff1a;对于浏览器知道服务器的真实…

vue前端开发自学demo,父子组件之间传递数据demo2

vue前端开发自学demo,父子组件之间传递数据demo2!实际上&#xff0c;组件之间传递数据的&#xff0c;数据类型&#xff0c;是可以多种多样的&#xff0c;下面为大家展示几个常见的数据类型&#xff0c;比如数字类型&#xff0c;数组类型&#xff0c;对象类型。 代码如下所示&a…

Linux Debian12系统gnome桌面环境默认提供截屏截图工具gnome-screenshot

一、简介&#xff1a; 在Debian12中系统gnome桌面环境默认提供一个截图捕获工具screenshot,可以自定义区域截图、屏幕截图、窗口截图和录制视频&#xff0c;截图默认保存在“~/图片/截图”路径下。 可以在应用程序中搜索screenshot,如下图&#xff1a; 也可以在桌面右上角找到…

中国聚α烯烃(PAO)行业市场调研与预测报告(2024版)

内容简介&#xff1a; 合成润滑油是指使用化学方法合成的基础油加入不同的添加剂调和而成的高性能润滑油。合成润滑油在各项性能上都远远优于矿物润滑油&#xff0c;为苛刻工况下工作的设备提供更加可靠的润滑。 常见的合成润滑油主要有聚烯烃类、合成酯类、聚醚类和烷基化芳…

算法进阶——按之字形顺序打印二叉树

题目 给定一个二叉树&#xff0c;返回该二叉树的之字形层序遍历&#xff0c;&#xff08;第一层从左向右&#xff0c;下一层从右向左&#xff0c;一直这样交替&#xff09;。 数据范围&#xff1a;0≤n≤1500,树上每个节点的val满足∣val∣<1500 要求&#xff1a;空间复杂…

物理机搭建hive

一、修改Hadoop配置 修改core-site.xml 配置yarn-site.xml 分发文件&#xff0c;然后重启集群 二、 Hive解压安装 上传文件 添加hive环境便量&#xff0c;source生效 启动高可用集群&#xff0c;启动hive 三、配置mysql元数据库 检查当前系统是否安装过Mysql&#xf…

AJAX入门到实战,学习前端框架前必会的(ajax+node.js+webpack+git)(七)

08.什么是模块化&#xff1f; CommonJS 标准 09.ECMAScript 标准 - 默认导出和导入 10.ECMAScript 标准 - 命名导出和导入 11.包的概念 实操&#xff1a; server.js utils/lib/index.js utils/package.json 12.npm - 软件包管理器 13.npm - 安装所有依赖 从别处&#xff08;网…

node 第二十一天 webpack 初见

为什么需要学习&#xff08;了解&#xff09;webpack webpack是前端工程化的基石&#xff0c;webpack又是基于node进行文件打包bundle&#xff0c;所以作为前端起手学习node服务端开发&#xff0c;同时学习webpack是很有必要的。 随着vite的出现&#xff0c;vue这一脉可能也许不…

HarmonyOS应用开发学习笔记 UIAbility组件与UI的数据同步 EventHub、globalThis

1、 HarmoryOS Ability页面的生命周期 2、 Component自定义组件 3、HarmonyOS 应用开发学习笔记 ets组件生命周期 4、HarmonyOS 应用开发学习笔记 ets组件样式定义 Styles装饰器&#xff1a;定义组件重用样式 Extend装饰器&#xff1a;定义扩展组件样式 5、HarmonyOS 应用开发…

TS:.d.ts 文件 和 declare 的作用

1 declare 做外部声明1.1 声明外部类型1.2 声明外部模块1.2.1 解决引入资源模块报错1.2.2 跳过对第三方库的类型检查 1.3 声明外部变量1.4 声明外部命名空间&#xff08;作用域&#xff09; 2 .d.ts 文件做外部声明3 declare global {} 在模块中做外部声明 先说一下我对 .d.ts文…

计算机缺失msvcp140.dll的修复教程,教你快速解决dll问题

“针对计算机系统中出现的msvcp140.dll文件丢失问题&#xff0c;小编将详细阐述一系列有效的解决方法。首先&#xff0c;msvcp140.dll是Microsoft Visual C Redistributable Package中的一个关键动态链接库文件&#xff0c;对于许多应用程序的正常运行至关重要。当系统提示该文…

RS触发器

转自&#xff1a;【基础】RS触发器_两个或非门构成rs触发器-CSDN博客 RS触发器为什么能 “保持上一状态” 触发器就是在常规的门电路的基础上加入了反馈&#xff0c;这样触发器就实现了存储数据的功能。这也是上面章节 “RS触发器实验” 的 RS触发器特征表 中第3条 “保持上一…

线性分解模型(LDM)的扩展方法——分析稀疏数据里的微生物组存在或缺失关联

谷禾健康 生态学家在分析微生物组和感兴趣的协变量(如临床结果或环境因素)之间的关联时&#xff0c;经常以两种方式查看物种分类计数数据。 一种是将计数视为定量的(即作为相对丰度数据进行分析)&#xff1b;另一种是将计数数据离散化&#xff0c;只表明一个分类单元在样本中是…

终极Linux命令宝典:从入门到精通,一网打尽!

文章目录 Linux命令详解1、Linux初级命令1.1、ls&#xff08;List&#xff09;1.1.1、ls-常用参数&#xff1a; 1.2、pwd&#xff08;Print Working Directory&#xff09;1.3、touch&#xff08;change file timestamps&#xff09;1.3.1、touch-常用参数 1.4、cat&tac (C…

Java多线程:创建多线程的三种方式

在Java中&#xff0c;有三种方式创建多线程&#xff0c;继承类Thread&#xff0c;继承接口Runnable&#xff0c;继承接口Callable。其中Thread和Runnable需要重写方法run&#xff0c;方法run没有返回值&#xff1b;Callable需要重写方法call&#xff0c;方法call可以返回值。 …

SAP OData(三)Query Option

Query option是指客户端在获取EntitySet的URL中后缀的一些指令&#xff0c;在第一篇第四小节我们已经见识了一部分Query指令。在下面表中列出了最重要的QueryOption。注意指令在URL中必须小写。 Operation Query Option Filtering and projecting $filter and $select Sort…

thinkphp6实现简单定时任务

thinkphp6实现定时任务 创建定时任务文件定义指令编写Test.php代码运行测试 创建定时任务文件 Test类名根据自己的需要修改 php think make:command Test testcommand文件夹在app目录下没有需要自己创建 运行上面的命令后会在command下 多一个Test.php文件 定义指令 在conf…

【Elasticsearch】Elasticsearch集群搭建详细手册

一、集群搭建 1.1.资源准备 服务器 192.168.X.26192.168.X.25192.168.X.24 安装包 elasticsearch-6.8.10.tar.gz 防火墙 所有服务器均开通9200,9300端口 1.2.安装组件 第一步&#xff1a;创建es安装目录 #在opt目录下创建soft目录 cd /opt mkdir soft第二步&#xff1a;上…

Java内存模型之原子性

文章目录 1.什么是原子性2.Java中的原子操作有哪些3.long和double的原子性4.原子操作 原子操作 ! 原子操作 1.什么是原子性 一系列的操作&#xff0c;要么全部执行成功&#xff0c;要么全部不执行&#xff0c;不会出现执行一半的情况&#xff0c;是不可分割的。 注意&#x…