moc介绍
Q_OBJECT,SLOT,SIGNAL,emit, Q_INVOKABLE等宏是Qt扩展的语法,它们其实定义在qobjectdefs.h中,编译时被moc展开。
总结:Moc可以理解位是一个C++预处理程序 ,作用就是把Q_OBJECT SIGNAL Q_INVOKABLE等宏展开,并保存类中特定函数(signals,slots标签下的函数及Q_INVOKABLE修饰的函数等)的信息,创建函数的回调。
文章中描述的类型用*号或者X代替。
类信息结构体
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.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是数据指针和当前对象指针的距离(偏移量)。
看第一模块的时候,添加信号、槽函数的参数没有找到生成对应信息。看到此处就明白了。
这个结构体描述的是信号&槽在调用时的索引、参数、返回值等信息 。
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 };
函数原型
void X::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
qt_metacall方法通过索引调用其它内部方法。Qt动态机制不采用指针,而由索引实现。实际调用方法的工作由编译器实现。这使得信号和槽的机制执行效率比较高。
参数由一个指向指针数组的指针进行传递,并在调用方法时进行适当的转换。当然,使用指针是将不同类型的参数放在一个数组的唯一办法。参数索引从1开始,因为0号代表函数返回值。
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;
归根结底,信号也是函数。MOC在生成的moc_xxx.cpp文件中生成了信号的函数,创建了一个指向参数的指针的数组(就是参数集合,就是参数传入传出机制),并将指针数组传给QMetaObject::activate函数。数组的第一个元素是返回值。本例中值是0,因为返回值是void。传给activate函数的第三个参数是信号的索引。
QMetaObject::activate(this, &staticMetaObject, 2, _a);
槽函数是由我们自己实现的。
利用槽函数在qt_static_metacall 函数的索引位置来调用槽函数 。
通过connect绑定信号槽,建立连接,利用已有的信息实现最终调用。这个单独再写文章。