# Qt观止 **Repository Path**: fmldd/qt-view ## Basic Information - **Project Name**: Qt观止 - **Description**: 介绍windows下qt(5.6.1)源码的具体实现; - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 17 - **Forks**: 5 - **Created**: 2020-08-31 - **Last Updated**: 2024-05-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ![](./pic/qtview.png) ## 前言 本文旨在介绍Qt(5.6.1)源码如何实现(系统相关特性只针对windows展开),本文并不是Qt的教程;适合具有一定Qt基础的童鞋一起探讨,很多童鞋在翻看源码时经常因为代码篇幅太长而放弃,大多时候我们只需要关心类的属性即可,类中的包含的数据才是其能对外提供功能的关键,了解属性配合调试就能很快掌握Qt的脉络;本文大多Qt类只列出部分关键属性,并简单的介绍其含义,但是为了方便喜欢深究读者也会给出Qt源码的调用链,可以对照源码自行翻看;本文尽量以开发中常用的Qt编程方式作为入口,一步步揭示其背后的原理;希望大家在读完本文后对Qt的具体实现有大致了解;由于本人的水平十分有限,一定各种各样的描述和理解的错误,希望大家可以帮忙指正,十分感谢;由于Qt的东西很多,只能慢慢持续更新,转载请标明出处哈; >阅读源码有什么意义? >阅读源码是一种手段,人是目的;通过阅读源码可以帮助你减少和解决开发中遇到的BUG,知其所以然等;在Qt源码阅读过程中你就是再和作者们一起,重现并参与Qt本身的设计与开发,设计过程中的很多点,最终都变成了使用Qt开发过程中的规则; ## 1 Qt元对象 Qt元对象系统提供了Qt的信号和槽机制,对象之间的互相通信,运行时的信息,动态属性系统,半自动的内存管理等,下面让我们由定义一个简单的Qt类开始,逐步揭开Qt的神秘面纱。 ### 1.1 定义一个简单类 从定义一个简单的继承自QObject的类开始,这里用到了一些传统C\++不曾见到的语法,signals,slots,emit,...,众所周知,Qt底层是使用原生的C\++编译器进行编译的,为什么这些C\++不曾出现的语法,没有导致编译报错呢?仔细探究可发现这些新的语法**大都**被定义成了空的宏(编译时不起任何作用),***这些关键字作只是作为标志,Qt的元编译器会扫描这些标志来记录类的信息和特征***,具体的信息和特征后面会仔细介绍; ```C++ //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; }; ``` ### 1.2 *signal,slots,emit* 等关键字 slot和emit都被定义成了空的宏,**signal总是被定义成public的**;我们平时在书写代码的时候发送一个信号\[emit ClassOver()\]意味着直接的函数调用,然而我们只编写了信号函数的声明并没有实现它,这些工作都由Qt元编译器来完成的,Qt元编译器会为每个信号函数声明实现定义。 *Qt元编译器只是一个辅助生成cpp代码的工具,最终编译工作还是会交给原生C++编译器完成* ``` 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相当于直接函数调用,但是声明一个信号时并不需要实现函数定义,发出信号时调用的是什么?** ### 1.3 *QObject* QObject类是所有Qt对象的基类,这个基类提供了那些功能,可以通过其属性了解到;这里不打算介绍所有的类属性,因为那样会劝退很多人,只会涉猎其中一些关键的属性,我们先看下QObject的类图继承关系(只列关键的属性),再来分析; ![](./pic/qobject.png) 尽管这个类图已经删减了很多东西,但是结构关系依然复杂,假如我们不考虑二进制兼容(后面会详细介绍),面向对象,把所有的属性都聚合到QObejct中,如下所示: ``` C++ class QObject { //--- QObjectData QObject *parent; // 当前QObject对象的父对象 QObjectList children; // 当前QObject对象的子对象列表 //--- QThreadData QStack 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内部实现: #### 1.3.1 **parent/children** 【半自动内存管理等】 ``` C++ //示例 //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的对象列表; **构造/析构调用链:** ```sequence! 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() ``` #### 1.3.2 **extraData** 【动态属性】 Qt开发过程中会经常用到,动态属性的功能,如下: ``` C++ //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调用链** ```sequence! 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 ``` #### 1.3.3 **threadData** 【对象线程绑定等】 *threadData*是一个与线程相关的结构体,该结构是同一线程的所有Qt类对象所共享的;如何实现对同一线程所有QThreadData共享呢?QT使用了Windows的**TLS**\[Thread Local Storage(线程本地存储)\]机制,简单来说就是线程会向Windows申请一个ID,线程第一次创建ThreadData时会将ID和TheadData绑定,这样运行时通过当前线程的ID查询获得的就是当前线程的TheadData结构指针; **TheadData是一个内部结构,这里给出TheadData创建过程的调用链** ```sequence! 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;* #### 1.3.4 **connectionLists/senders**【信号与槽】 先对connectionLists结构有个概念,vercet里面存放了一个双线链表,结构如下图所示,每个链表节点都是QMetaObject::Connection对象,先不关注,在信号与槽实现在做展开; ![connectionLists](./pic/QObjectConnectionListVector.png) 让我们看一个简单的例子,QObject::connect都对connectionLists/senders做了什么? ``` C++ //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的调用链** ```sequence! 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 ``` #### 1.3.5 **currentSender**【信号与槽】 在槽函数中调用sender()便是返回currentSender,信号和槽在同一线程时currentSender会在QMetaObject::activate被设置,不同线程则依赖QEvent::MetaCall类型事件设置(详见信号与槽);示例如下: ``` c++ //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(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的设置过程** ```sequence! 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值可靠性?** ### 1.4*QMetaObject* Qt中的Qt元对象系统负责信号和槽对象间的通信机制、运行时类型信息和Qt属性系统。为了方便阅读我直接将关键的数据放到了QMetaObject中,如下所示: ``` C++ 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中* ### 1.5 *Q_OBJECT* Q_OBJECT只是一个简单的宏定义,如下所示: ``` C++ //--- #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 ### 1.6 *MOC*编译 上面并没有对QMetaObject做详细介绍,这一节会结合Qt元编译器生成信息进一步完善QMetaObject内部的数据结构,还是让我们先定义一个简单的Teacher类 ``` c++ //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文件如下: ``` c++ #include "../../untitled/teacher.h" #include #include #if !defined(Q_MOC_OUTPUT_REVISION) #error "The header file 'teacher.h' doesn't include ." #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(_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(_a[0]); void **func = reinterpret_cast(_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(_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(_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(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(_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(reinterpret_cast(&_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(reinterpret_cast(&_t1)), const_cast(reinterpret_cast(&_t2)) }; QMetaObject::activate(this, &staticMetaObject, 1, _a); } // SIGNAL 2 void Teacher::nameChanged(const QString & _t1) { void *_a[] = { Q_NULLPTR, const_cast(reinterpret_cast(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 2, _a); } QT_END_MOC_NAMESPACE ``` 这个代码看上去似乎挺复杂,但是只需要初略了解其中部分代码的含义即可 #### 1.6.1 **类字符信息** 撇开符号表,原生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的起始位置,字符串长度 ```c++ //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)或属性并不会被记录,这就意味着运行时你无法动态获取它们; #### 1.6.2 **类结构信息** 运行时获取类属性,调用类方法,只记录了类中的字符信息远远不够,属性类型是什么?函数参数和返回值类型什么?...,这时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部分就不做详细介绍了; ```c++ 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 }; ``` #### 1.6.3 **qt_static_metacall** 该方法通过索引(qt_meta_stringdata_Teacher.data数组下标),实现运行时对Qt类内部方法变量和动态调用(以Teacher类为例则是qt_meta_data_Teacher中记录的方法);下面还是以Teacher类为例,编写main测试动态方法调用; ```c++ //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的结构非常简单 ```c++ class QMetaMethod { const QMetaObject *mobj; //指向QMetaObejct的指针 uint handle; //指向qt_meta_data_Teacher中方法起始下标: 例如获取homework则是qt_meta_data_Teacher[29] } ``` **homework调用链** ```sequence! 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类方法过程; #### 1.6.4 **staticMetaObject** Q_OBJECT为Teacher类中引入并声明了静态类成员staticMetaObject,这个变量的的定义和初始化同样是在moc文件中进行的; ```c++ 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##[]数组中生成多条记录; ```c++ //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 */, //... }; ``` #### 1.6.5 **qt_metacast** 根据类名获取或判断当前类或超类指针;参考QObject中的qobject_cast/inherits; #### 1.6.5 **qt_metacall** 参考1.6.3:qt_static_metacall(静态方法); #### 1.6.6 **信号定义** > 1.2节问题: > **问题:emit相当于直接函数调用,但是声明一个信号时并不需要实现函数定义,发出信号时调用的是什么?** > 元编译会在生成moc_##ClassName##文件中补充信号函数定义 moc文件最后就是对类中声明的信号函数给出其定义,值得注意的是刚刚我们提到QObject中只定义了两个信号,由于destroyed拥有默认参数,qt_meta_data_QObject[]数组中生成3条信号记录,但是对于QObject的信号函数定义只有两个,C++自身支持函数默认参数;qt_meta_data_QObject[]数组的记录只是为了动态调用函数时可以正确的构造参数; ```c++ //moc_qobject.cpp // SIGNAL 0 void QObject::destroyed(QObject * _t1) { void *_a[] = { Q_NULLPTR, const_cast(reinterpret_cast(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 0, _a); } // SIGNAL 2 void QObject::objectNameChanged(const QString & _t1, QPrivateSignal) { void *_a[] = { Q_NULLPTR, const_cast(reinterpret_cast(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 2, _a); } ``` 根据我们上面学到的内容,简单脑补下信号发送过程无非就是查询connectionLists中信号对应双向链表,遍历链表中的每个recv触发一遍槽,Qt是否就是这样实现的呢?第三章信号与槽会给出答案; #### 1.6.7 **小结** Qt只是一个C\++的库,不是语言,它没有任何特性会干扰编译器的运行;自此本节开头提到Teacher类缺失的定义,通过Qt元编译器生成的moc_##ClassName##.cpp补充完整了,接下来就可以交给原生C++编译器,编译成二进制目标文件了; ## 2 事件驱动 ### 2.1 windows应用程序 ### 2.2 windows的消息队列 ### 2.3 windowsHook ### 2.4 Qt事件 ### 2.5 Qt事件循环 ## 3 信号与槽 ## 4 QTimer ## 5 QThread ## 6 QWidget 闻一言以贯万物,谓之知道。