学习 Qt 源码,避不开断点调试和 pdb 文件。有三种思路:
使用 Qt 4.8.7,安装包中源码和 pdb 文件都有,版本太旧;
使用 Qt5.12.x,有离线安装包,但
- 缺失 pdb 调试文件,单独下载 pdb 文件后与源码版本不一致,断点位置偏移;
- 缺少
moc_qobject.cpp等构建过程中生成的中间文件,影响理解;
使用 Qt5.15.x 等任意版本,从源码构建,难度大,收益也大。
摸索之后,选择了从源码构建 qtbase 基础模块,用于学习核心特性足够。稍后出一键构建脚本。
先避开图形界面程序 Qt Application,通过 Qt Console Application 学习,只使用 Core 模块。
学习目标:
- 元对象系统,搞定
moc_qobject.cpp就差不多学会了 - 事件循环,核心实现都在
QThreadData类中 - 信号槽,= 元对象系统中的函数调用能力
- Qt 跨模块(dll/exe) 内存分配
思考:元对象系统在完成信号槽之外,还实现了哪些特性?
分离接口和实现
QObject 的 d_ptr 指针,指向 QObjectPrivate ; QWidget 的 d_ptr 指针,指向 QWidgetPrivate 。
Qt 内部实现上使用了宏,本质上是 pImpl 手法,分离接口和具体实现。
指针类型是抽象类 QObjectData ,它是所有 QXxxPrivate 类型的基类。
QScopedPointer<QObjectData> d_ptr
元对象系统就定义在 QObjectData 中:元对象针对类型,而非某个具体的对象实例。
1 | // 摘自 qobject.h 版本 Qt 5.12.12 |
元对象类型
要理解 QObject ,先看 QMetaObject
The
QMetaObjectclass contains meta-information about Qt objects.The
QMetaMethodclass provides meta-data about a member function.
struct QMetaObject 有很多方法,实际只有一个(语法上)公有的成员变量 d 但在语义上却是 private data 。为什么如此处理?
1 | // 摘自 `qobjectdefs.h` 头文件 |
文件 moc_qobject.cpp 去哪里查看呢? 构建过程生成的中间文件。
1 | // 摘自 moc_qobject.cpp Qt5.15.18 ,类型的静态成员变量 |
之后无符号整型指针 d.data (即 qt_meta_data_QObject[]) 又是按照 QMetaObjectPrivate 解析的,后者的定义在哪?
找到这个约定我们就能理解 moc_qobject.cpp 中 const uint qt_meta_data_CObject[] 一堆的数字是什么意思了。
1 | // 5.12.12\Src\qtbase\src\corelib\kernel\qmetaobject_p.h |
关于 methodCount() 在帮助手册中的描述前后有差异(推断后一段存在笔误):
method()andmethodCount()provide information about a class’s meta-methods (signals, slots and other invokable member functions).Returns the number of methods in this class, including the number of methods provided by each base class. These include signals and slots as well as normal member functions.
帮助手册针对 Q_INVOKABLE 的解释,很明显 invokableMethod() 和 normalMethod() 是不同的!前者表示:
Apply this macro to declarations of member functions to allow them to be invoked via the meta-object system.
在尝试追踪 connect 如何工作之前,我们先看一下怎么使用 QMetaObject 执行 QObject 中的函数?
通过元对象执行函数
元对象系统并不能调用常规的成员函数。
见 QMetaObject::invokeMethod() 静态函数,以及 enum Qt::ConnectionType 枚举定义。
it determines whether a particular signal is delivered to a slot immediately or queued for delivery at a later time.
默认参数 Qt::AutoConnection 根据信号槽的接收者是否和 发送者 emit 在相同线程:
- 如果在同一线程,就直接调用(等同于回调函数)
- 如果不在同一线程,等进入接收者所在线程的事件循环时再执行槽函数(等同于发送异步信号)。
为了更清晰地理解后者,我把有关的描述列在下面:
Qt::QueuedConnectionThe slot is invoked when control returns to the event loop of the receiver’s thread. The slot is executed in the receiver’s thread.
摘自 QMetaObject::invokeMethod()
If type is
Qt::QueuedConnection, aQEventwill be sent and thememberis invoked as soon as the application enters the main event loop.
所以,如何理解每个线程都有独立的事件循环?或者放弃多线程只用单一线程。
源文件内部类
我们可以在 .cpp 文件中定义只用于当前文件的 C++ 类,但 QObject 子类不支持这种做法。
因为链接时无法看到 moc 扩展的定义,报错 LNK2001: unresolved external symbol
推测可能需要某种技巧才能满足。
Define a QObject derived class inside an anonymous namespace?