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 的主要功能是:

  1. metadata, 包括关于 class 的 introspection 信息, property 的 introspection 信息, method(metacal) 信息等
  2. metacast, 实现 qobject_cast
  3. metacall
    1. property 的 READ, WRITE 等功能
    2. 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:

  1. InvokeMetaMethod
  2. IndexOfMethod
  3. 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
  1. 把 metacall 最终调用到的具体函数设置为 virtual 会导致 metacall 的行为受到多态的影响
  2. 由于 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);
}

Author: [email protected]
Date: 2020-01-02 Thu 00:00
Last updated: 2020-01-03 Fri 16:53

知识共享许可协议