Qt Meta Object
Table of Contents
1. Qt Meta Object
1.1. Overview
对于声明了 Q_OBJECT 的 class xxx, moc 会负责生成一个 moc_xxx.cpp, 包含 xxx 对应的 meta object 的定义.
meta object 的主要功能是:
- metadata, 包括关于 class 的 introspection 信息, property 的 introspection 信息, method(metacal) 信息等
- metacast, 实现 qobject_cast
- metacall
- property 的 READ, WRITE 等功能
- invokeable method, signal, slot 的调用
signal/slot 的具体实现(如何 connect 及调用) 并不包含在 meta object 本身中.
1.2. moc_xxx 的结构
1.2.1. stringdata
meta object 所有功能都依赖于对 class, property, signal, slot 等的 introspection, 所以需要记下所有相关的字符串, 以便后面查找和匹配
static const qt_meta_stringdata_MessageBox_t qt_meta_stringdata_MessageBox = { { // 类名 // (0,0,10) 分别表示 (idx, offset, len) QT_MOC_LITERAL(0, 0, 10), // "MessageBox" // signal QT_MOC_LITERAL(1, 11, 13), // "heightChanged" QT_MOC_LITERAL(2, 25, 0), // "" // signal QT_MOC_LITERAL(3, 26, 15), // "changedTwoTimes" // signal QT_MOC_LITERAL(4, 42, 16), // "oopsWidthChanged" // slot QT_MOC_LITERAL(5, 59, 13), // "onTextChanged" // Q_INVOKABLE method QT_MOC_LITERAL(6, 73, 3), // "foo" // property QT_MOC_LITERAL(7, 77, 6), // "height" // property QT_MOC_LITERAL(8, 84, 5) // "width" }, "MessageBox\0heightChanged\0\0changedTwoTimes\0" "oopsWidthChanged\0onTextChanged\0foo\0" "height\0width" };
1.2.2. metadata
static const uint qt_meta_data_MessageBox[] = { // content: 8, // revision // className 的 idx 为 0, 即 MessageBox 0, // classname 0, 0, // classinfo // 有 5 个 methods, 相关信息所在偏移量为 14, 即下面的 // signals:... 处 // 5 个 method 分别是: 3 个 signal, 1 个 slot, 1 个 invokeable method 5, 14, // methods // 2 个 property, 相关信息的所在的偏移量为 46 2, 46, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 3, // signalCount // signals: name, argc, parameters, tag, flags 1, 0, 39, 2, 0x06 /* Public */, 3, 0, 40, 2, 0x06 /* Public */, 4, 0, 41, 2, 0x06 /* Public */, // slots: name, argc, parameters, tag, flags 5, 1, 42, 2, 0x0a /* Public */, // methods: name, argc, parameters, tag, flags 6, 0, 45, 2, 0x02 /* Public */, // signals: parameters QMetaType::Void, QMetaType::Void, QMetaType::Void, // slots: parameters QMetaType::Void, QMetaType::QString, 2, // methods: parameters QMetaType::Void, // properties: name, type, flags 7, QMetaType::Int, 0x00495003, 8, QMetaType::Int, 0x00095103, // properties: notify_signal_id 0, 0, 0 // eod }
该结构体与 QMetaObjectPrivate 对应:
struct QMetaObjectPrivate { enum { OutputRevision = 8 }; // Used by moc, qmetaobjectbuilder and qdbus int revision; int className; int classInfoCount, classInfoData; int methodCount, methodData; int propertyCount, propertyData; int enumeratorCount, enumeratorData; int constructorCount, constructorData; int flags; int signalCount; // ... }
1.2.3. metacall
metacall 是 meta object 最重要的部分, 它实现三种不同的 Call:
- InvokeMetaMethod
- IndexOfMethod
- Property 相关: READ, WRITE, RESET, …
int MessageBox::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 < 5) qt_static_metacall(this, _c, _id, _a); _id -= 5; } else if (_c == QMetaObject::ReadProperty || _c == QMetaObject::WriteProperty || _c == QMetaObject::ResetProperty || _c == QMetaObject::RegisterPropertyMetaType) { qt_static_metacall(this, _c, _id, _a); _id -= 2; } // else ... return _id; } void MessageBox::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { auto *_t = static_cast<MessageBox *>(_o); Q_UNUSED(_t) switch (_id) { case 0: _t->heightChanged(); break; case 1: _t->changedTwoTimes(); break; case 2: _t->oopsWidthChanged(); break; case 3: _t->onTextChanged((*reinterpret_cast< QString(*)>(_a[1]))); break; case 4: _t->foo(); break; default: ; } } else if (_c == QMetaObject::IndexOfMethod) { int *result = reinterpret_cast<int *>(_a[0]); { using _t = void (MessageBox::*)(); if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MessageBox::heightChanged)) { *result = 0; return; } } { using _t = void (MessageBox::*)(); if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MessageBox::changedTwoTimes)) { *result = 1; return; } } { using _t = void (MessageBox::*)(); if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MessageBox::oopsWidthChanged)) { *result = 2; return; } } } else if (_c == QMetaObject::ReadProperty) { auto *_t = static_cast<MessageBox *>(_o); Q_UNUSED(_t) void *_v = _a[0]; switch (_id) { case 0: *reinterpret_cast< int*>(_v) = _t->mHeight; break; case 1: *reinterpret_cast< int*>(_v) = _t->width(); break; default: break; } } else if (_c == QMetaObject::WriteProperty) { auto *_t = static_cast<MessageBox *>(_o); Q_UNUSED(_t) void *_v = _a[0]; switch (_id) { case 0: if (_t->mHeight != *reinterpret_cast< int*>(_v)) { _t->mHeight = *reinterpret_cast< int*>(_v); Q_EMIT _t->heightChanged(); } break; case 1: _t->setWidth(*reinterpret_cast< int*>(_v)); break; default: break; } } else if (_c == QMetaObject::ResetProperty) { } }
1.2.3.1. InvokeMetaMethod
signal, slot, invokeable method 都可以通过 meta object 的方式被调用, 例如 QML 中可以直接调用这三种函数, 通过 signal 可以间接调用到 slot 等.
调用这些函数最终都要通过 InvokeMetaMethod 进行, 每个方法在编译时确定一个 id, 后面通过 id 来区分不同的方法.
1.2.3.2. IndexOfMethod
connect 时会调用这个方法获得对应的 slot 的 id, 然后在调用 slot 时可以使用这个 id 调用 InvokeMetaMethod(id).
由于这里使用的是 static_cast, 所以它实际是给新的 functor-based connect 使用的, 而非旧的 string-based connect
1.2.3.3. Property READ/WRITE
对 property 的读写会翻译为对应的 metacall
1.2.3.4. 关于 metacall
- 把 metacall 最终调用到的具体函数设置为 virtual 会导致 metacall 的行为受到多态的影响
- 由于 qt_metacall 会先调用基类的 qt_metacall, 所以 property 也会表现出`继承`的行为
1.2.4. metacast
void *MessageBox::qt_metacast(const char *_clname) { if (!_clname) return nullptr; if (!strcmp(_clname, qt_meta_stringdata_MessageBox.stringdata0)) return static_cast<void*>(this); return QObject::qt_metacast(_clname); }
由于 meta object 包含了 class 相关的 introspection 信息, 所以它可以不依赖 c++ RTTI 进行 dynamic_cast, 即 qobject_cast.
qt_metacast 就是用来实现 qobject_cast 的: 能不能 cast 直接比较 classname 是否相同即可…
1.2.5. signal 的实现
xxx 只是声明了 signal 的函数原型, 并不包含具体的实现, moc 会生成一个具体的实现, 用来根据 connection 信息调用到具体的 slot
// SIGNAL 0 void MessageBox::heightChanged() { QMetaObject::activate(this, &staticMetaObject, 0, nullptr); } // SIGNAL 1 void MessageBox::changedTwoTimes() { QMetaObject::activate(this, &staticMetaObject, 1, nullptr); } // SIGNAL 2 void MessageBox::oopsWidthChanged() { QMetaObject::activate(this, &staticMetaObject, 2, nullptr); }