Qt学习:moc生成的元对象信息
Qt的元对象系统提供了对象间通信的信号槽机制、运行时类型信息和动态属性系统。元对象信息是元对象系统的核心内容,需要经过moc(Meta-Object Compiler, 元对象编译器)工具生成相应的附加代码,Qt工程编译时会先调用moc.exe生成Q_OBJECT、Q_GADGET、Q_NAMESPACE等标记的元对象特性代码。
Qt5和Qt6的元对象信息代码结构有些差异,此处以Qt5.15为基础进行学习。
如要调试moc还需要编译下moc源码,Qt5的话直接下载源码,把moc.pro丢到Qt Creator编译。pro需要稍加修改:
# option(host_build)
QT += core core-private
CONFIG += force_bootstrapDEFINES += \QT_MOC \QT_NO_CAST_FROM_ASCII \QT_NO_CAST_FROM_BYTEARRAY \QT_NO_COMPRESS \QT_NO_FOREACH# strerror() is safe to use because moc is single-threaded
msvc: DEFINES += _CRT_SECURE_NO_WARNINGSinclude(moc.pri)
HEADERS += qdatetime_p.h
SOURCES += main.cppQMAKE_TARGET_DESCRIPTION = "Qt Meta Object Compiler"
# load(qt_tool)DESTDIR = $$PWD/bin
moc.exe通过命令行参数生成指定文件的元对象信息:
moc.exe MyObject.h -o moc_MyObject.cpp
先准备一个很简单的QObject子类头文件,仅定义了一个信号和槽:
#pragma once
#include <QObject>class MyObject : public QObject
{Q_OBJECT
public:explicit MyObject(QObject *parent = nullptr);signals:void mySignal(int i, const QString &signalStr);public slots:void mySlot(int i, const QString &slotStr);
};
生成的元对象信息(注释版):
/****************************************************************************
** 前面的注释标明关联的c++文件,以及Qt moc版本
** Meta object code from reading C++ file 'MyObject.h'
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.15.2)
** WARNING! All changes made in this file will be lost!
*****************************************************************************/#include <memory>
#include "MyObject.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'MyObject.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.15.2. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endifQT_BEGIN_MOC_NAMESPACE
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
struct qt_meta_stringdata_MyObject_t {// data存储的stringdata0中的(编号+偏移地址+字符串长度)QByteArrayData data[7];char stringdata0[46];
};
#define QT_MOC_LITERAL(idx, ofs, len) \Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \qptrdiff(offsetof(qt_meta_stringdata_MyObject_t, stringdata0) + ofs \- idx * sizeof(QByteArrayData)) \)
// 元数据的字符串值
static const qt_meta_stringdata_MyObject_t qt_meta_stringdata_MyObject = {{
// moc提取代码中的信号/槽/属性等信息,包括信号槽的参数名
QT_MOC_LITERAL(0, 0, 8), // "MyObject"
QT_MOC_LITERAL(1, 9, 8), // "mySignal"
// 空字符串表示没有设置函数标记tag,tag是给函数添加额外信息,参考QMetaMethod::tag文档
QT_MOC_LITERAL(2, 18, 0), // ""
// 信号槽参数int都命名为i,但是QString是不同命名,重复的命名只会存储一个,qt_meta_data_XX里通过索重新组合起来
QT_MOC_LITERAL(3, 19, 1), // "i"
QT_MOC_LITERAL(4, 21, 9), // "signalStr"
QT_MOC_LITERAL(5, 31, 6), // "mySlot"
QT_MOC_LITERAL(6, 38, 7) // "slotStr"},
// Qt5是将所有信息放到一个字符串数组,用偏移值来截取"MyObject\0mySignal\0\0i\0signalStr\0mySlot\0""slotStr"
};
#undef QT_MOC_LITERAL// 元数据数组,包括类名、信号、槽、参数类型、参数名等。
static const uint qt_meta_data_MyObject[] = {
// content:8, // revision -元对象信息的版本,定义在qmetaobject_p.h/QMetaObjectPrivate::OutputRevision0, // classname -类名0, 0, // classinfo -类信息,Q_CLASSINFO声明的内容2, 14, // methods -信号槽或者Q_INVOKABLE声明的函数(不含构造函数),2个函数,14是qt_meta_data_XX中的偏移量0, 0, // properties -属性个数,第二列是qt_meta_data_XX中的偏移量0, 0, // enums/sets -Q_ENUM/Q_FLAG声明的枚举0, 0, // constructors -Q_INVOKABLE声明的构造函数,MetaObject::newInstance需要构造函数标记Q_INVOKABLE0, // flags -目前只有Q_GADGET和Q_NAMESPACE标记的类/namespace会设置PropertyAccessInStaticMetaCall(0x04),qdbus相关的模块用到1, // signalCount -信号个数,methods总数减去signalCount个数就是其他函数// 信号:name是qt_meta_stringdata_XX中的索引,argc参数个数,parameters是qt_meta_data_XX中参数类型索引
// ,tag函数标记qt_meta_stringdata_XX中的索引,flags参考qmetaobject_p.h/MethodFlags枚举
// signals: name, argc, parameters, tag, flags1, 2, 24, 2, 0x06 /* Public */,// 槽和Q_INVOKLABLE函数信息和信号信息类似
// slots: name, argc, parameters, tag, flags5, 2, 29, 2, 0x0a /* Public */,// 信号的参数,前面的parameters索引到这里,标记了返回值类型,参数类型,最后面是参数名在qt_meta_stringdata_XX中的索引
// signals: parametersQMetaType::Void, QMetaType::Int, QMetaType::QString, 3, 4,// 槽和Q_INVOKLABLE函数参数和信号参数类似
// slots: parametersQMetaType::Void, QMetaType::Int, QMetaType::QString, 3, 6,// 末尾固定0结束0 // eod
};// 用于在运行时根据元对象信息动态调用信号、槽、属性等成员
// _o是对象指针一般是this,_c调用类型,_id是信号槽属性的索引,_a是参数数组(用于传递参数和返回值)
void MyObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{if (_c == QMetaObject::InvokeMetaMethod) { // 调用信号或槽函数auto *_t = static_cast<MyObject *>(_o);Q_UNUSED(_t)switch (_id) {case 0: _t->mySignal((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< const QString(*)>(_a[2]))); break;case 1: _t->mySlot((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< const QString(*)>(_a[2]))); break;default: ;}} else if (_c == QMetaObject::IndexOfMethod) { // 信号函数索引int *result = reinterpret_cast<int *>(_a[0]);{using _t = void (MyObject::*)(int , const QString & );if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MyObject::mySignal)) {*result = 0;return;}}}
}// 静态元对象,运行时通过它获取类的元信息
QT_INIT_METAOBJECT const QMetaObject MyObject::staticMetaObject = { {QMetaObject::SuperData::link<QObject::staticMetaObject>(),qt_meta_stringdata_MyObject.data,qt_meta_data_MyObject,qt_static_metacall,nullptr,nullptr
} };const QMetaObject *MyObject::metaObject() const
{return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}void *MyObject::qt_metacast(const char *_clname)
{if (!_clname) return nullptr;if (!strcmp(_clname, qt_meta_stringdata_MyObject.stringdata0))return static_cast<void*>(this);return QObject::qt_metacast(_clname);
}int MyObject::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 < 2)qt_static_metacall(this, _c, _id, _a);_id -= 2;} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {if (_id < 2)*reinterpret_cast<int*>(_a[0]) = -1;_id -= 2;}return _id;
}// 信号声明会展开为一个函数,函数内部调用QMetaObject::activate触发信号
// SIGNAL 0
void MyObject::mySignal(int _t1, const QString & _t2)
{void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))), const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE
这里借助一下网上别人的图更直观的看到元数据表和函数声明的映射关系。原作者找不到了,从引用处找到的图:https://blog.csdn.net/weixin_42515409/article/details/112580602
信号槽声明:
字符串索引:
元数据索引:
使用时,用indexOfMethod/Signal/Slot等通过字符串得到信号槽id,就是q_meta_data_XX表里得内容,不过id会根据分组再偏移一下。然后通过id就可以由qt_static_metacall调用具体的函数了。
到了Qt6,元对象数据表用了更现代的C++语法,这里截取Qt6.9生成的部分代码:
namespace {
struct qt_meta_tag_ZN8MyObjectE_t {};
} // unnamed namespacetemplate <> constexpr inline auto MyObject::qt_create_metaobjectdata<qt_meta_tag_ZN8MyObjectE_t>()
{namespace QMC = QtMocConstants;QtMocHelpers::StringRefStorage qt_stringData {"MyObject","mySignal","","i","signalStr","mySlot","slotStr"};QtMocHelpers::UintData qt_methods {// Signal 'mySignal'// 信号名偏移1="mySignal",函数tag偏移2="",访问权限,返回值类型,参数个数,参数类型和名称QtMocHelpers::SignalData<void(int, const QString &)>(1, 2, QMC::AccessPublic, QMetaType::Void, {{// 参数类型和参数名称偏移,3="i",4="signalStr"{ QMetaType::Int, 3 }, { QMetaType::QString, 4 },}}),// Slot 'mySlot'// 槽函数名偏移5="mySlot",函数tag偏移2="",访问权限,返回值类型,参数个数,参数类型和名称QtMocHelpers::SlotData<void(int, const QString &)>(5, 2, QMC::AccessPublic, QMetaType::Void, {{{ QMetaType::Int, 3 }, { QMetaType::QString, 6 },}}),};QtMocHelpers::UintData qt_properties {};QtMocHelpers::UintData qt_enums {};return QtMocHelpers::metaObjectData<MyObject, qt_meta_tag_ZN8MyObjectE_t>(QMC::MetaObjectFlag{}, qt_stringData,qt_methods, qt_properties, qt_enums);
}
Q_CONSTINIT const QMetaObject MyObject::staticMetaObject = { {QMetaObject::SuperData::link<QObject::staticMetaObject>(),qt_staticMetaObjectStaticContent<qt_meta_tag_ZN8MyObjectE_t>.stringdata,qt_staticMetaObjectStaticContent<qt_meta_tag_ZN8MyObjectE_t>.data,qt_static_metacall,nullptr,qt_staticMetaObjectRelocatingContent<qt_meta_tag_ZN8MyObjectE_t>.metaTypes,nullptr
} };
不同版本moc生成的元对象附加代码可能不一样,但是存储的内容是差不多的。
参考文档:https://doc.qt.io/qt-6/metaobjects.html
参考文档:https://doc.qt.io/qt-6/moc.html