Qt源码解析3-信号和槽机制

Qt源码解析 索引

1、MOC

moc介绍

Q_OBJECT,SLOT,SIGNAL,emit, Q_INVOKABLE等宏是Qt扩展的语法,它们其实定义在qobjectdefs.h中,编译时被moc展开。

总结:Moc可以理解位是一个C++预处理程序 ,作用就是把Q_OBJECT SIGNAL Q_INVOKABLE等宏展开,并保存类中特定函数(signals,slots标签下的函数及Q_INVOKABLE修饰的函数等)的信息,创建函数的回调。

文章中描述的类型用*号或者X代替。

2、MOC生成文件

2.1 moc文件构成

2.1.1 类信息qt_meta_stringdata_*

  • 类信息结构体

struct qt_meta_stringdata_QtSingalSlotTest_t {
    QByteArrayData data[6];
    char stringdata0[52];
};

data字段是一个由QByteArrayData数组组成的数组,数组大小根据信号&槽个数有关,这个数组在调用QObject的connect函数时用来匹配信号名或槽名。

stringdata 存放的是字符信息,存放全部的类名、信号名、槽名 。

  • 类信息结构体的实例化对象

static const qt_meta_stringdata_QtSingalSlotTest_t qt_meta_stringdata_QtSingalSlotTest = {
    {
        QT_MOC_LITERAL(0, 0, 16), // "QtSingalSlotTest"`
        QT_MOC_LITERAL(1, 17, 8), // "mySignal"
        QT_MOC_LITERAL(2, 26, 0), // ""
        QT_MOC_LITERAL(3, 27, 6), // "mySlot"
        QT_MOC_LITERAL(4, 34, 7) // "mySlot2"
​
    },
    "QtSingalSlotTest\0mySignal\0\0mySlot\0"
    "mySlot2"
};

第一个花括号,初始化data数组,每一个数字由QT_MOC_LITERAL初始化QByteArrayData类型。QT_MOC_LITERAL将类名、信号、槽函数等字符在字符信息中的位置。

2.1.1.1 QT_MOC_LITERAL说明-指针说明

template <int> struct QIntegerForSize;
template <>    struct QIntegerForSize<1> { typedef quint8  Unsigned; typedef qint8  Signed; };
template <>    struct QIntegerForSize<2> { typedef quint16 Unsigned; typedef qint16 Signed; };
template <>    struct QIntegerForSize<4> { typedef quint32 Unsigned; typedef qint32 Signed; };
template <>    struct QIntegerForSize<8> { typedef quint64 Unsigned; typedef qint64 Signed; };
template <class T> struct QIntegerForSizeof: QIntegerForSize<sizeof(T)> { };
typedef QIntegerForSize<Q_PROCESSOR_WORDSIZE>::Signed qregisterint;
typedef QIntegerForSize<Q_PROCESSOR_WORDSIZE>::Unsigned qregisteruint;
typedef QIntegerForSizeof<void*>::Unsigned quintptr;
typedef QIntegerForSizeof<void*>::Signed qptrdiff;

2.1.1.2 QT_MOC_LITERAL说明-Offsetof宏详解

2.1.1.3 QT_MOC_LITERAL说明-本身说明

#define QT_MOC_LITERAL(idx, ofs, len)

QT_MOC_LITERAL宏的作用是为stringdata0中保存的每个函数名都创建一个QByteArrayData,宏参数为函数的索引值,偏移量,函数名长度。

2.1.1.4 QT_MOC_LITERAL说明-定义说明

#define Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset) \
    Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset)

初始化数组实际用的Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET这个宏定义。

#define Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset) \
    { Q_REFCOUNT_INITIALIZE_STATIC, size, 0, 0, offset } \

QByteArrayData类型定义如下

struct Q_CORE_EXPORT QArrayData
{
    QtPrivate::RefCount ref;
    int size;
    uint alloc : 31;
    uint capacityReserved : 1;
​
    qptrdiff offset; // in bytes from beginning of header
    ......
};

根据以上宏定义,我们关注size和offset两个变量。Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET也只赋值了这两个字段。

size是指针指向数据的大小,offset是数据指针和当前对象指针的距离(偏移量)。

2.1.2 元数据信息qt_meta_data_*

看第一模块的时候,添加信号、槽函数的参数没有找到生成对应信息。看到此处就明白了。

这个结构体描述的是信号&槽在调用时的索引、参数、返回值等信息 。

static const uint qt_meta_data_QtSingalSlotTest[] = {
​
 // content:
       8,       // revision
       0,       // classname类名在stringdata中的索引,总是0,qt_meta_stringdata_X.data[0].data()
       0,    0, // classinfo
       4,   14, // methods
       0,    0, // properties属性数量和在该数组中的起始位置
       0,    0, // enums/sets枚举的位置信息
       0,    0, // constructors
       0,       // flags
       2,       // signalCount信号个数
​
 // signals: name, argc, parameters, tag, flags
       1,    0,   34,    2, 0x06 /* Public */,
       3,    1,   35,    2, 0x06 /* Public */,
​
 // slots: name, argc, parameters, tag, flags
       4,    0,   38,    2, 0x0a /* Public */,
       5,    1,   39,    2, 0x0a /* Public */,
​
 // signals: parameters
    QMetaType::Void,
    QMetaType::Void, QMetaType::Int,    2,
​
 // slots: parameters
    QMetaType::Void,
    QMetaType::Void, QMetaType::Int,    2,
​
       0        // eod
​
};

这个数组的前14个uint 描述的是元对象的私有信息,定义在qmetaobject_p.h文件的QMetaObjectPrivate结构体当中。

在这个结构体中4, 14, // methods ,第一个参数信息描述的是信号槽的总数和在表中的偏移量,第二个参数信息描述,从该数组中的第15个元素(qt_meta_data_X[14]])开始描述 ,14个uint之后是信息&槽的信息。X代表生成的类名。   表中我们可以看到每描述一个信号或槽需要5个uint   // signals: name, argc, parameters, tag, flags   name:对应的是qt_meta_stringdata_X 索引   argc:参数个数   parameters : 参数的在qt_meta_data_X这个表中的索引位置。   例如 // signals: parameters   QMetaType::Void,   QMetaType::Void, QMetaType::Int, 2,   void 是信号的返回值,QMetaType::Int是参数类型, 2是参数名,在qt_meta_stringdata* 中的索引值。   tag:这个字段的数值对应的是qt_meta_stringdata_X 索引,在这个moc文件里对应的是一个空字符串,具体怎么用,待走读到再补充分析。   flags:是一个特征值,是在 enum MethodFlags 枚举中定义。  

enum MethodFlags {undefined
  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
  };

2.1.3 qt_static_metacall

函数原型

void X::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)

qt_metacall方法通过索引调用其它内部方法。Qt动态机制不采用指针,而由索引实现。实际调用方法的工作由编译器实现。这使得信号和槽的机制执行效率比较高。

参数由一个指向指针数组的指针进行传递,并在调用方法时进行适当的转换。当然,使用指针是将不同类型的参数放在一个数组的唯一办法。参数索引从1开始,因为0号代表函数返回值。

2.1.4 QObject 中静态staticMetaObject的赋值 (类的元对象)

 QT_INIT_METAOBJECT const QMetaObject QtSingalSlotTest::staticMetaObject = { {
    &QWidget::staticMetaObject,
    qt_meta_stringdata_QtSingalSlotTest.data,
    qt_meta_data_QtSingalSlotTest,
    qt_static_metacall,
    nullptr,
    nullptr
} };

通过这个静态变量就保存了moc文件的信号&槽的调用索引信息。在信号&槽绑定的时候就是通过这些信息一步一步建立的绑定关系。 分别对应以上各节内容。

 struct { // private data
        const QMetaObject *superdata;//父类的此变量地址
        const QByteArrayData *stringdata;//信号槽索引
        const uint *data;//参数索引
        typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
        StaticMetacallFunction static_metacall;//
        const QMetaObject * const *relatedMetaObjects;
        void *extradata; //reserved for future use
    } d;

2.1.5 信号的实现

归根结底,信号也是函数。MOC在生成的moc_xxx.cpp文件中生成了信号的函数,创建了一个指向参数的指针的数组(就是参数集合,就是参数传入传出机制),并将指针数组传给QMetaObject::activate函数。数组的第一个元素是返回值。本例中值是0,因为返回值是void。传给activate函数的第三个参数是信号的索引。

QMetaObject::activate(this, &staticMetaObject, 2, _a);

2.1.5 槽函数的调用

槽函数是由我们自己实现的。

利用槽函数在qt_static_metacall 函数的索引位置来调用槽函数 。

通过connect绑定信号槽,建立连接,利用已有的信息实现最终调用。这个单独再写文章。

上一篇:Python 中更安全的 eval


下一篇:mybatis-plus的getById方法提示语法错误?打印sql数据库执行正常,为什么代码里面提示语法错误呢