本文旨在介绍Qt(5.6.1)源码如何实现(系统相关特性只针对windows展开),本文并不是Qt的教程;适合具有一定Qt基础的童鞋一起探讨,很多童鞋在翻看源码时经常因为代码篇幅太长而放弃,大多时候我们只需要关心类的属性即可,类中的包含的数据才是其能对外提供功能的关键,了解属性配合调试就能很快掌握Qt的脉络;本文大多Qt类只列出部分关键属性,并简单的介绍其含义,但是为了方便喜欢深究读者也会给出Qt源码的调用链,可以对照源码自行翻看;本文尽量以开发中常用的Qt编程方式作为入口,一步步揭示其背后的原理;希望大家在读完本文后对Qt的具体实现有大致了解;由于本人的水平十分有限,一定各种各样的描述和理解的错误,希望大家可以帮忙指正,十分感谢;由于Qt的东西很多,只能慢慢持续更新,转载请标明出处哈;
阅读源码有什么意义? 阅读源码是一种手段,人是目的;通过阅读源码可以帮助你减少和解决开发中遇到的BUG,知其所以然等;在Qt源码阅读过程中你就是再和作者们一起,重现并参与Qt本身的设计与开发,设计过程中的很多点,最终都变成了使用Qt开发过程中的规则;
Qt元对象系统提供了Qt的信号和槽机制,对象之间的互相通信,运行时的信息,动态属性系统,半自动的内存管理等,下面让我们由定义一个简单的Qt类开始,逐步揭开Qt的神秘面纱。
从定义一个简单的继承自QObject的类开始,这里用到了一些传统C++不曾见到的语法,signals,slots,emit,...,众所周知,Qt底层是使用原生的C++编译器进行编译的,为什么这些C++不曾出现的语法,没有导致编译报错呢?仔细探究可发现这些新的语法大都被定义成了空的宏(编译时不起任何作用),这些关键字作只是作为标志,Qt的元编译器会扫描这些标志来记录类的信息和特征,具体的信息和特征后面会仔细介绍;
//teacher.h
class Teacher : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
public:
explicit Teacher(QObject *parent = 0) : QObject(parent){
}
//! 测试方法
void test() {
}
QString getName() const
{
return name;
}
void setName(const QString &name)
{
this->name = name;
}
signals:
//!点名
//! name 学生名
void rollCall(const QString &name);
//! 布置作业
//! info 内容
//! endData 截止完成时间
void arrangementWork(const std::string &info, const QTime &endData);
//!Teacher::name改变是触发该信号
void nameChanged(const QString &name);
public slots:
//! 批改作业
//! name 学生名
//! data 作业内容
void homework(QString name, std::string data) {
qDebug() << name << " : " << QString::fromStdString(data);
}
private:
//! 老师名
QString name;
};
slot和emit都被定义成了空的宏,signal总是被定义成public的;我们平时在书写代码的时候发送一个信号[emit ClassOver()]意味着直接的函数调用,然而我们只编写了信号函数的声明并没有实现它,这些工作都由Qt元编译器来完成的,Qt元编译器会为每个信号函数声明实现定义。
Qt元编译器只是一个辅助生成cpp代码的工具,最终编译工作还是会交给原生C++编译器完成
//Qt源码
//信号与槽的关键字
#define slots Q_SLOTS
#define signals Q_SIGNALS
# define Q_SLOTS QT_ANNOTATE_ACCESS_SPECIFIER(qt_slot)
# define Q_SIGNALS public QT_ANNOTATE_ACCESS_SPECIFIER(qt_signal) //信号总数被定义为public
# define QT_ANNOTATE_ACCESS_SPECIFIER(x) //空宏
//发送信号关键字
# define emit //空宏
问题:emit相当于直接函数调用,但是声明一个信号时并不需要实现函数定义,发出信号时调用的是什么?
QObject类是所有Qt对象的基类,这个基类提供了那些功能,可以通过其属性了解到;这里不打算介绍所有的类属性,因为那样会劝退很多人,只会涉猎其中一些关键的属性,我们先看下QObject的类图继承关系(只列关键的属性),再来分析;
尽管这个类图已经删减了很多东西,但是结构关系依然复杂,假如我们不考虑二进制兼容(后面会详细介绍),面向对象,把所有的属性都聚合到QObejct中,如下所示:
class QObject
{
//--- QObjectData
QObject *parent; // 当前QObject对象的父对象
QObjectList children; // 当前QObject对象的子对象列表
//--- QThreadData
QStack<QEventLoop *> eventLoops; //
QPostEventList postEventList; //线程等待处理的事件列表
Qt::HANDLE threadId; //系统相关线程句柄
QAbstractEventDispatcher eventDispatcher;
//--- QObjectPrivate
ExtraData *extraData; //用户数据
QObjectConnectionListVector *connectionLists; //该对象作为 sender的所有connections
Connection *senders; //该对象作为 receiver的所有connections
Sender *currentSender; //当前信号发送者
//--- QObject
static const QMetaObject staticMetaObject; //QObject的元对象,QMetaObject会详细介绍
};
结合我们平时使用,QObject的功能也就逐渐清晰了,下面让我们有Qt编程中常用示例作为出发点,窥探QObject内部实现:
//示例
//a.h
class A : public QObject
{
Q_OBJECT
public:
explicit A(QObject *parent = 0) : QObject(parent) {}
~A(){
qDebug() << "A over";
}
};
//b.h
class B : public QObject
{
Q_OBJECT
public:
explicit B(QObject *parent = 0) : QObject(parent) {
a_ = new A(this);
}
private:
A *a_;
};
//main.cpp
int main(int argc, char *argv[])
{
{
B b;
}
}
示例会输出A over,new一个类A的对象而不需要手动delete,依然可以触发A的析构,原因是B::QObject::QObjectPrivate::children中保存new出A对象的指针,B析构会顺手清理children的对象列表;
构造/析构调用链:
participant B as B
participant A as A
participant QObject as O
participant QObjectPrivate as P
B -> A: 开始构造A::A()
A -> O: QObject::QObject()
O -> O: QObject::setParent()
O -> P: QObjectPrivate::setParent_helper()
P -> B: B::QObject::QObjectPrivate::children.append()
B -> B: 开始析构B::~B()
B -> O: QObject::~QObject()
O -> P: QObjectPrivate::deleteChildren()
P -> A: A::~A()
Qt开发过程中会经常用到,动态属性的功能,如下:
//a.h
class A : public QObject
{
Q_OBJECT
public:
explicit A(QObject *parent = nullptr) : QObject(parent) {}
};
//main.cpp
int main(int argc, char *argv[])
{
A a;
a.setObjectName("ClassA");
a.setProperty("key", QVariant::fromValue(QString("value")));
}
示例中setObjectName和setProperty设置变量都会存放在QObject::QObjectPrivate::extraData中
setProperty/setObjectName调用链
participant A as A
participant QObject as O
participant QObjectPrivate as P
A -> O: QObject::setObjectName()
O -> P: QObjectPrivate::extraData::objectName
A -> O: QObject::setProperty()
O -> P: QObjectPrivate::extraData::propertyValues
threadData是一个与线程相关的结构体,该结构是同一线程的所有Qt类对象所共享的;如何实现对同一线程所有QThreadData共享呢?QT使用了Windows的TLS[Thread Local Storage(线程本地存储)]机制,简单来说就是线程会向Windows申请一个ID,线程第一次创建ThreadData时会将ID和TheadData绑定,这样运行时通过当前线程的ID查询获得的就是当前线程的TheadData结构指针;
TheadData是一个内部结构,这里给出TheadData创建过程的调用链
participant QObject as O
participant QThreadData as T
participant Windows as W
O -> T: QThreadData::current()
T -> W: TlsAlloc()
W -> T: 线程本地存储ID
T -> W: TlsSetValue() 绑定threadData和ID
T -> O: QObject::QObjectPrivate::threadData
上面调用链分析了QThreadData第一次创建的情形,大多时候Qt类是通过partner->threadData直接赋值;
这里Thread Local Storage只是描述了Qt使用动态TLS的过程,关于TLS完整介绍请自行查阅MSDN;
先对connectionLists结构有个概念,vercet里面存放了一个双线链表,结构如下图所示,每个链表节点都是QMetaObject::Connection对象,先不关注,在信号与槽实现在做展开;
让我们看一个简单的例子,QObject::connect都对connectionLists/senders做了什么?
//teacher.h
class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = 0) : QObject(parent) {
}
signals:
void rollCall(QString name); //点名
};
//student.h
class Student : public QObject
{
Q_OBJECT
public:
explicit Student(QString name, QObject *parent = 0): QObject(parent), name_(name){
}
signals:
void report(QString msg);
public slots:
void onRollCall(QString name) { //点名答"到"
if(name_.compare(name) == 0)
qDebug() << name << " : \"here\"";
}
private:
QString name_;
};
//main.cpp
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Teacher teacher;
Student stuTom("Tom");
Student stuJerry("Jerry");
Student stuBruce("Bruce");
QObject::connect(&teacher, &Teacher::rollCall, &stuTom, &Student::onRollCall);
QObject::connect(&teacher, &Teacher::rollCall, &stuJerry, &Student::onRollCall);
QObject::connect(&teacher, &Teacher::rollCall, &stuBruce, &Student::onRollCall);
return a.exec();
}
这段代码运行以后teacher的connectionLists[3]中双向链每个节点的reciver分别指向了stuTom/stuJerry/stuBruce,而stuTom/stuJerry/stuBruce的中senders的链的表节中sender也指向了teacher,connectionLists[3]从3开始因为QObject中有3个信号函数;
问题:connectionLists的向量(vector)中存储的是信号函数标识,Teacher类中只了一个信号,向量为何下标是从3开始?
connect的调用链
participant QObject as O
participant QMetaObject as M
participant QObjectPrivate as P
O -> M: QMetaObject::connect()
M -> O: QObject::connectImpl()
O -> P: QMetaObjectPrivate::signalOffset()
P -> P: QObjectPrivate::connectImpl()
P -> P: QObjectPrivate::addConnection
在槽函数中调用sender()便是返回currentSender,信号和槽在同一线程时currentSender会在QMetaObject::activate被设置,不同线程则依赖QEvent::MetaCall类型事件设置(详见信号与槽);示例如下:
//student.h
class Student : public QObject
{
Q_OBJECT
public:
explicit Student( QObject *parent = 0): QObject(parent){
QTimer::singleShot(6*1000, [&]() {
emit report("I like study!");
});
}
signals:
void report(QString msg); //学习汇报
};
//teacher.h
class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = 0) : QObject(parent) {
}
public slots:
void onReport(QString msg) //接收汇报
{
auto *stu = qobject_cast<Student *>(sender());
qDebug() << msg << stu->objectName();
}
};
//main.cpp
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Teacher teacher;
Student stuTom("Tom");
Student stuJerry("Jerry");
Student stuBruce("Bruce");
stuTom.setObjectName("Tom");
stuJerry.setObjectName("Jerry");
stuBruce.setObjectName("Bruce");
QObject::connect(&stuTom, &Student::report, &teacher, &Teacher::onReport);
QObject::connect(&stuJerry, &Student::report, &teacher, &Teacher::onReport);
QObject::connect(&stuBruce, &Student::report, &teacher, &Teacher::onReport);
return a.exec();
}
信号与槽同一线程currentSender的设置过程
participant Student as S
participant QMetaObject as M
participant QConnectionSenderSwitcher as C
participant QObjectPrivate as P
S -> S: Student::report()
S -> M: QMetaObject::activate()
M -> C: QConnectionSenderSwitcher::switchSender
C -> P: QObjectPrivate::setCurrentSender()
问题:频繁发送信号时,currentSender设置可靠吗?信号与槽不在同一线程时如何保证currentSender值可靠性?
Qt中的Qt元对象系统负责信号和槽对象间的通信机制、运行时类型信息和Qt属性系统。为了方便阅读我直接将关键的数据放到了QMetaObject中,如下所示:
typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
struct QMetaObject
{
const QMetaObject *superdata; //父类对象的元信息
const QByteArrayData *stringdata; //存储类的详细信息
const uint *data; //记录Qt类的基本类型,存储数据结构和QMetaObjectPrivate内部定义保持一致
StaticMetacallFunction static_metacall;//动态方法调用
};
由于C++不支持反射等高级特性,Qt使用元编译器自行生成Qt类的详细信息并保存到QMetaObject中
Q_OBJECT只是一个简单的宏定义,如下所示:
//---
#define Q_OBJECT
public:
static const QMetaObject staticMetaObject; //静态的QMetaObject对象
virtual const QMetaObject *metaObject() const;
virtual void *qt_metacast(const char *);
virtual int qt_metacall(QMetaObject::Call, int, void **);
private:
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
每一个定义了Q_OBJECT宏的类,直接或者间接继承于QObject的类,且包含一个名为staticMetaObjec的静态QMetaObject对象,一个私有的静态函数qt_static_metacall
上面并没有对QMetaObject做详细介绍,这一节会结合Qt元编译器生成信息进一步完善QMetaObject内部的数据结构,还是让我们先定义一个简单的Teacher类
//teacher.h
class Teacher : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
public:
explicit Teacher(QObject *parent = 0) : QObject(parent){
}
//! 测试方法
void test() {
}
QString getName() const
{
return name;
}
void setName(const QString &name)
{
this->name = name;
}
signals:
//!点名
//! name 学生名
void rollCall(const QString &name);
//! 布置作业
//! info 内容
//! endData 截止完成时间
void arrangementWork(const std::string &info, const QTime &endData);
//!Teacher::name改变是触发该信号
void nameChanged(const QString &name);
public slots:
//! 批改作业
//! name 学生名
//! data 作业内容
void homework(QString name, std::string data) {
qDebug() << name << " : " << QString::fromStdString(data);
}
private:
//! 老师名
QString name;
};
通过上面学习,细心的你也许已经发现,Teacher这个类根本无法使用原生的C++编译器将其编译成二进制目标文件(更不要提链接成可执行文件了),这个类缺少了很多定义:
1- emit相当于public,声明了3个信号函数,未给出函数定义;
2- Q_OBJECT宏将函数和static成员直接插入到Teacher类中却并没有实现;
这些缺失的实现正是由Qt的元编译器帮我们补齐的,引入Q_OBJECT的类会在编译目录生成一个moc_##ClassName##.cpp的文件;这里还是以上面的Teacher为例,生成moc_teacher.cpp文件如下:
#include "../../untitled/teacher.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'teacher.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.6.3. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif
QT_BEGIN_MOC_NAMESPACE
struct qt_meta_stringdata_Teacher_t {
QByteArrayData data[11];
char stringdata0[90];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_Teacher_t, stringdata0) + ofs \
- idx * sizeof(QByteArrayData)) \
)
static const qt_meta_stringdata_Teacher_t qt_meta_stringdata_Teacher = {
{
QT_MOC_LITERAL(0, 0, 7), // "Teacher"
QT_MOC_LITERAL(1, 8, 8), // "rollCall"
QT_MOC_LITERAL(2, 17, 0), // ""
QT_MOC_LITERAL(3, 18, 4), // "name"
QT_MOC_LITERAL(4, 23, 15), // "arrangementWork"
QT_MOC_LITERAL(5, 39, 11), // "std::string"
QT_MOC_LITERAL(6, 51, 4), // "info"
QT_MOC_LITERAL(7, 56, 7), // "endData"
QT_MOC_LITERAL(8, 64, 11), // "nameChanged"
QT_MOC_LITERAL(9, 76, 8), // "homework"
QT_MOC_LITERAL(10, 85, 4) // "data"
},
"Teacher\0rollCall\0\0name\0arrangementWork\0"
"std::string\0info\0endData\0nameChanged\0"
"homework\0data"
};
#undef QT_MOC_LITERAL
static const uint qt_meta_data_Teacher[] = {
// content:
7, // revision
0, // classname
0, 0, // classinfo
4, 14, // methods
1, 50, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
3, // signalCount
// signals: name, argc, parameters, tag, flags
1, 1, 34, 2, 0x06 /* Public */,
4, 2, 37, 2, 0x06 /* Public */,
8, 1, 42, 2, 0x06 /* Public */,
// slots: name, argc, parameters, tag, flags
9, 2, 45, 2, 0x0a /* Public */,
// signals: parameters
QMetaType::Void, QMetaType::QString, 3,
QMetaType::Void, 0x80000000 | 5, QMetaType::QTime, 6, 7,
QMetaType::Void, QMetaType::QString, 3,
// slots: parameters
QMetaType::Void, QMetaType::QString, 0x80000000 | 5, 3, 10,
// properties: name, type, flags
3, QMetaType::QString, 0x00495103,
// properties: notify_signal_id
2,
0 // eod
};
void Teacher::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
Teacher *_t = static_cast<Teacher *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->rollCall((*reinterpret_cast< const QString(*)>(_a[1]))); break;
case 1: _t->arrangementWork((*reinterpret_cast< const std::string(*)>(_a[1])),(*reinterpret_cast< const QTime(*)>(_a[2]))); break;
case 2: _t->nameChanged((*reinterpret_cast< const QString(*)>(_a[1]))); break;
case 3: _t->homework((*reinterpret_cast< QString(*)>(_a[1])),(*reinterpret_cast< std::string(*)>(_a[2]))); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
void **func = reinterpret_cast<void **>(_a[1]);
{
typedef void (Teacher::*_t)(const QString & );
if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Teacher::rollCall)) {
*result = 0;
return;
}
}
{
typedef void (Teacher::*_t)(const std::string & , const QTime & );
if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Teacher::arrangementWork)) {
*result = 1;
return;
}
}
{
typedef void (Teacher::*_t)(const QString & );
if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Teacher::nameChanged)) {
*result = 2;
return;
}
}
}
#ifndef QT_NO_PROPERTIES
else if (_c == QMetaObject::ReadProperty) {
Teacher *_t = static_cast<Teacher *>(_o);
Q_UNUSED(_t)
void *_v = _a[0];
switch (_id) {
case 0: *reinterpret_cast< QString*>(_v) = _t->getName(); break;
default: break;
}
} else if (_c == QMetaObject::WriteProperty) {
Teacher *_t = static_cast<Teacher *>(_o);
Q_UNUSED(_t)
void *_v = _a[0];
switch (_id) {
case 0: _t->setName(*reinterpret_cast< QString*>(_v)); break;
default: break;
}
} else if (_c == QMetaObject::ResetProperty) {
}
#endif // QT_NO_PROPERTIES
}
const QMetaObject Teacher::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_Teacher.data,
qt_meta_data_Teacher, qt_static_metacall, Q_NULLPTR, Q_NULLPTR}
};
const QMetaObject *Teacher::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
void *Teacher::qt_metacast(const char *_clname)
{
if (!_clname) return Q_NULLPTR;
if (!strcmp(_clname, qt_meta_stringdata_Teacher.stringdata0))
return static_cast<void*>(const_cast< Teacher*>(this));
return QObject::qt_metacast(_clname);
}
int Teacher::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
if (_id < 4)
qt_static_metacall(this, _c, _id, _a);
_id -= 4;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 4)
*reinterpret_cast<int*>(_a[0]) = -1;
_id -= 4;
}
#ifndef QT_NO_PROPERTIES
else if (_c == QMetaObject::ReadProperty || _c == QMetaObject::WriteProperty
|| _c == QMetaObject::ResetProperty || _c == QMetaObject::RegisterPropertyMetaType) {
qt_static_metacall(this, _c, _id, _a);
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyDesignable) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyScriptable) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyStored) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyEditable) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyUser) {
_id -= 1;
}
#endif // QT_NO_PROPERTIES
return _id;
}
// SIGNAL 0
void Teacher::rollCall(const QString & _t1)
{
void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
// SIGNAL 1
void Teacher::arrangementWork(const std::string & _t1, const QTime & _t2)
{
void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)), const_cast<void*>(reinterpret_cast<const void*>(&_t2)) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
// SIGNAL 2
void Teacher::nameChanged(const QString & _t1)
{
void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 2, _a);
}
QT_END_MOC_NAMESPACE
这个代码看上去似乎挺复杂,但是只需要初略了解其中部分代码的含义即可
撇开符号表,原生C/C++编译后类,方法,属性的名称,都不会被保留;而Qt想要做运行时类信息获取,就必须自己记录这一信息,如何记录?以上面的moc_teacher.cpp为例,qt_meta_stringdata_Teacher就保存了类,方法,属性的名称,Qt在这里也做了一些优化,相同名称字符串只保留一份;
QT_MOC_LITERAL(0, 0, 7)中3个参数分别对应qt_meta_stringdata_Teacher.data数组下标,字符串对应qt_meta_stringdata_Teacher.stringdata0的起始位置,字符串长度
//qt_meta_stringdata_Teacher
struct qt_meta_stringdata_Teacher_t {
QByteArrayData data[11];
char stringdata0[90];
};
static const qt_meta_stringdata_Teacher_t qt_meta_stringdata_Teacher = {
{
QT_MOC_LITERAL(0, 0, 7), // "Teacher"
QT_MOC_LITERAL(1, 8, 8), // "rollCall"
QT_MOC_LITERAL(2, 17, 0), // ""
QT_MOC_LITERAL(3, 18, 4), // "name"
QT_MOC_LITERAL(4, 23, 15), // "arrangementWork"
QT_MOC_LITERAL(5, 39, 11), // "std::string"
QT_MOC_LITERAL(6, 51, 4), // "info"
QT_MOC_LITERAL(7, 56, 7), // "endData"
QT_MOC_LITERAL(8, 64, 11), // "nameChanged"
QT_MOC_LITERAL(9, 76, 8), // "homework"
QT_MOC_LITERAL(10, 85, 4) // "data"
},
"Teacher\0rollCall\0\0name\0arrangementWork\0"
"std::string\0info\0endData\0nameChanged\0"
"homework\0data"
};
可以看到Qt中信号与槽,元属性,以及信号或槽或元属性中使用了Qt未知的类型(std::string),等都会被详细记录下来;然而普通方法(Teacher::test)或属性并不会被记录,这就意味着运行时你无法动态获取它们;
运行时获取类属性,调用类方法,只记录了类中的字符信息远远不够,属性类型是什么?函数参数和返回值类型什么?...,这时moc_teacher.cpp的qt_meta_data_Teacher登场了,这个uint数组将Teacher类中所有可以动态获取的方法和属性清晰的呈现在我们面前;Qt将qt_meta_data_Teacher中所有对字符串的描述(方法,属性等)都换成了qt_meta_stringdata_Teacher.data数组的下标,同时还添加一个枚举类MethodFlags来标识方法类型,访问权限等;Qt的QMetaObjectPrivate结构体对应这个数组content部分,解析类的各种信息,由于QMetaObjectPrivate属性名十分清晰,qt_meta_data_Teacher的content部分就不做详细介绍了;
enum MethodFlags {
AccessPrivate = 0x00,
AccessProtected = 0x01,
AccessPublic = 0x02,
AccessMask = 0x03, //mask
MethodMethod = 0x00,
MethodSignal = 0x04,
MethodSlot = 0x08,
MethodConstructor = 0x0c,
MethodTypeMask = 0x0c,
MethodCompatibility = 0x10,
MethodCloned = 0x20,
MethodScriptable = 0x40,
MethodRevisioned = 0x80
};
struct QMetaObjectPrivate
{
//content
int revision;
int className;
int classInfoCount, classInfoData;
int methodCount, methodData;
int propertyCount, propertyData;
int enumeratorCount, enumeratorData;
int constructorCount, constructorData; //since revision 2
int flags; //since revision 3
int signalCount; //since revision 4
}
static const uint qt_meta_data_Teacher[] = {
// content:
7, // revision
0, // classname
0, 0, // classinfo
4, 14, // methods //14标识方法起始位置为qt_meta_data_Teacher[14] 即下面的signals
1, 50, // properties //50标识属性起始位置为qt_meta_data_Teacher[14] 即下面的properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
3, // signalCount
// signals: name 信号名称 qt_meta_stringdata_Teacher.data的索引
// signals: argc 参数个数
// signals: parameters 参数信息 qt_meta_data_Teacher[](当前数组下标)即signals: parameters
// signals: tag 信号标识 qt_meta_stringdata_Teacher.data的索引
// signals: flags信号类型 参考MethodFlags例: 0x06 = 0x02 | 0x04 既 public signals
// signals: name, argc, parameters, tag, flags
1, 1, 34, 2, 0x06 /* Public */,
4, 2, 37, 2, 0x06 /* Public */,
8, 1, 42, 2, 0x06 /* Public */,
// slots: name, argc, parameters, tag, flags
9, 2, 45, 2, 0x0a /* Public */,
// signals: parameters : return 返回值类型
// signals: parameters : argv 参数类型 0x80000000标识未定类型,5 qt_meta_stringdata_Teacher.data的索引(std::string)
// signals: parameters : argvname:参数名称 qt_meta_stringdata_Teacher.data的索引
// signals: parameters : return, argv, argvname
QMetaType::Void, QMetaType::QString, 3,
QMetaType::Void, 0x80000000 | 5, QMetaType::QTime, 6, 7,
QMetaType::Void, QMetaType::QString, 3,
// slots: parameters
QMetaType::Void, QMetaType::QString, 0x80000000 | 5, 3, 10,
// flags 参数类型 对应PropertyFlags(qmetaobject_p.h) 0x00495103即 Notify|Stored|ResolveEditable|Scriptable|Designable|StdCppSet|Writable|Readable
// properties: name, type, flags
3, QMetaType::QString, 0x00495103,
// properties: notify_signal_id
2,
0 // eod
};
该方法通过索引(qt_meta_stringdata_Teacher.data数组下标),实现运行时对Qt类内部方法变量和动态调用(以Teacher类为例则是qt_meta_data_Teacher中记录的方法);下面还是以Teacher类为例,编写main测试动态方法调用;
//main.cpp
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Teacher teacher;
for(int i =0; i < Teacher::staticMetaObject.propertyCount(); ++i) {
QMetaProperty pro = Teacher::staticMetaObject.property(i);
if(pro.name() == QByteArray("name"))
qDebug() << pro.read(&teacher);
}
for(int i = 0; i < Teacher::staticMetaObject.methodCount(); ++ i) {
QMetaMethod method = Teacher::staticMetaObject.method(i);
if(method.name() == QByteArray("homework"))
method.invoke(&teacher, Qt::AutoConnection, Q_ARG(QString, "Tom"), Q_ARG(std::string, "12345678790"));
}
return a.exec();
}
QMetaMethod的结构非常简单
class QMetaMethod {
const QMetaObject *mobj; //指向QMetaObejct的指针
uint handle; //指向qt_meta_data_Teacher中方法起始下标: 例如获取homework则是qt_meta_data_Teacher[29]
}
homework调用链
participant Main as MA
participant QMetaObject as M
participant QMetaMethod as MM
participant Teacher as T
MA -> M: Teacher::staticMetaObject.method()
MA -> MM: QMetaMethod::invoke()
MM -> T: Teacher::qt_static_metacall()
T -> T: Teacher::homework()
以上只是描述了同一线程中动态调用Qt类方法过程;
Q_OBJECT为Teacher类中引入并声明了静态类成员staticMetaObject,这个变量的的定义和初始化同样是在moc文件中进行的;
const QMetaObject Teacher::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_Teacher.data,
qt_meta_data_Teacher, qt_static_metacall, Q_NULLPTR, Q_NULLPTR}
};
struct QMetaObject
{
const QMetaObject *superdata; //父类对象的元信息
const QByteArrayData *stringdata; //存储类的详细信息
const uint *data; //记录Qt类的基本类型,存储数据结构和QMetaObjectPrivate内部定义保持一致
StaticMetacallFunction static_metacall;//运行时获取类信息
const QMetaObject * const *relatedMetaObjects;
void *extradata;
};
Qt类在继承过程依然要保留超类的元对象信息,这样的继承才是有意义的;Teacher::staticMetaObject.methodCount()返回的方法数就包含了QObject中定义一些方法,superdata便是存放超类元对象信息;
1.3.4节的问题: 问题:connectionLists的向量(vector)中存储的是信号函数标识,Teacher类中只了一个信号,向量为何下标是从3开始? connectionLists第一维(vector)数组每个元素都对应类中定义的信号,下标不是从0开始是因为Teacher类继承自QObject,QObject里面还包含3个信号;通过QMetaObjectPrivate::signalOffset计算 connectionLists长度时,会计算当前类staticMetaObject和superdata的信号总和;这样当前类对外链接超类的信号才不至于无处存放; 细心的同学可能已经发现了QObject中只定义了两个信号(destroyed/objectNameChanged),这是因为destroyed拥有默认参数,Qt对含有一个或多个默认参数的函数采用全组合的方式在qt_meta_data_##ClassName##[]数组中生成多条记录;
//moc_qobject.cpp
static const qt_meta_stringdata_QObject_t qt_meta_stringdata_QObject = {
{
QT_MOC_LITERAL(0, 0, 7), // "QObject"
QT_MOC_LITERAL(1, 8, 9), // "destroyed"
QT_MOC_LITERAL(2, 18, 0), // ""
QT_MOC_LITERAL(3, 19, 17), // "objectNameChanged"
QT_MOC_LITERAL(4, 37, 10), // "objectName"
QT_MOC_LITERAL(5, 48, 11), // "deleteLater"
QT_MOC_LITERAL(6, 60, 19), // "_q_reregisterTimers"
QT_MOC_LITERAL(7, 80, 6) // "parent"
},
"QObject\0destroyed\0\0objectNameChanged\0"
"objectName\0deleteLater\0_q_reregisterTimers\0"
"parent"
};
static const uint qt_meta_data_QObject[] = {
// content:
7, // revision
0, // classname
0, 0, // classinfo
5, 14, // methods
1, 54, // properties
0, 0, // enums/sets
2, 58, // constructors
0, // flags
3, // signalCount
// signals: name, argc, parameters, tag, flags
1, 1, 39, 2, 0x06 /* Public */,
1, 0, 42, 2, 0x26 /* Public | MethodCloned */,
3, 1, 43, 2, 0x06 /* Public */,
//...
};
根据类名获取或判断当前类或超类指针;参考QObject中的qobject_cast/inherits;
参考1.6.3:qt_static_metacall(静态方法);
1.2节问题: 问题:emit相当于直接函数调用,但是声明一个信号时并不需要实现函数定义,发出信号时调用的是什么? 元编译会在生成moc_##ClassName##文件中补充信号函数定义
moc文件最后就是对类中声明的信号函数给出其定义,值得注意的是刚刚我们提到QObject中只定义了两个信号,由于destroyed拥有默认参数,qt_meta_data_QObject[]数组中生成3条信号记录,但是对于QObject的信号函数定义只有两个,C++自身支持函数默认参数;qt_meta_data_QObject[]数组的记录只是为了动态调用函数时可以正确的构造参数;
//moc_qobject.cpp
// SIGNAL 0
void QObject::destroyed(QObject * _t1)
{
void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
// SIGNAL 2
void QObject::objectNameChanged(const QString & _t1, QPrivateSignal)
{
void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 2, _a);
}
根据我们上面学到的内容,简单脑补下信号发送过程无非就是查询connectionLists中信号对应双向链表,遍历链表中的每个recv触发一遍槽,Qt是否就是这样实现的呢?第三章信号与槽会给出答案;
Qt只是一个C++的库,不是语言,它没有任何特性会干扰编译器的运行;自此本节开头提到Teacher类缺失的定义,通过Qt元编译器生成的moc_##ClassName##.cpp补充完整了,接下来就可以交给原生C++编译器,编译成二进制目标文件了;
闻一言以贯万物,谓之知道。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。