2 Star 17 Fork 5

Bruce / Qt观止

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

前言

本文旨在介绍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的元编译器会扫描这些标志来记录类的信息和特征,具体的信息和特征后面会仔细介绍;

//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++编译器完成

//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的类图继承关系(只列关键的属性),再来分析;

尽管这个类图已经删减了很多东西,但是结构关系依然复杂,假如我们不考虑二进制兼容(后面会详细介绍),面向对象,把所有的属性都聚合到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内部实现:

1.3.1 parent/children 【半自动内存管理等】


//示例
//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()

1.3.2 extraData 【动态属性】

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

1.3.3 threadData 【对象线程绑定等】

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;

1.3.4 connectionLists/senders【信号与槽】

先对connectionLists结构有个概念,vercet里面存放了一个双线链表,结构如下图所示,每个链表节点都是QMetaObject::Connection对象,先不关注,在信号与槽实现在做展开;

connectionLists

让我们看一个简单的例子,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

1.3.5 currentSender【信号与槽】

在槽函数中调用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值可靠性?

1.4QMetaObject

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中

1.5 Q_OBJECT

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

1.6 MOC编译

上面并没有对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

这个代码看上去似乎挺复杂,但是只需要初略了解其中部分代码的含义即可

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的起始位置,字符串长度

//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部分就不做详细介绍了;

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测试动态方法调用;

//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类方法过程;

1.6.4 staticMetaObject

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 */,
 //...
};

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[]数组的记录只是为了动态调用函数时可以正确的构造参数;

//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是否就是这样实现的呢?第三章信号与槽会给出答案;

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

闻一言以贯万物,谓之知道。

空文件

简介

介绍windows下qt(5.6.1)源码的具体实现; 展开 收起
C++
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
C++
1
https://gitee.com/fmldd/qt-view.git
git@gitee.com:fmldd/qt-view.git
fmldd
qt-view
Qt观止
master

搜索帮助