上一篇文章简单介绍了Qt框架中的三大编译器(MOC、UIC、RCC),其中我认为最核心,最重要的就是元对象编译器(MOC),下面我们将深入探索MOC生成的代码,并逐步解析。
本文将以下面的源码来解析MOC生成了哪些代码。
test_moc.h:
#ifndef TEST_MOC_H
#define TEST_MOC_H#include <QDebug>
#include <QObject>class test_moc : public QObject {Q_OBJECT
public:test_moc(QObject* parent = nullptr): QObject(parent){}public slots:void on_TestSlot() { qDebug() << __FUNCTION__; }void on_TestSlot_Param(int num) { qDebug() << __FUNCTION__ << num; }signals:void sigTestSignals();void sigTestSignals_Param(int num);
};#endif // TEST_MOC_H
main.cpp:
#include "test_moc.h"#include <QApplication>int main(int argc, char* argv[])
{QApplication a(argc, argv);test_moc m1, m2;QObject::connect(&m1, SIGNAL(sigTestSignals()), &m2, SLOT(on_TestSlot()));QObject::connect(&m2, SIGNAL(sigTestSignals_Param(int)), &m1, SLOT(on_TestSlot_Param(int)));return a.exec();
}
元对象编译器(MOC)
Qt信号槽和属性系统基于在运行时自省对象的能力,能够列出对象的方法和属性,并拥有有关它们的各种信息,例如参数类型。
因为C++本身是不支持运行时自省对象的能力的,因此Qt附带了一个工具来支持它。这个工具就是MOC。它是一个代码生成器(通过编译来生成新的代码)。
MOC解析头文件并生成一个附加的C++文件,该文件与程序的其余部分一起编译。生成的C++文件包含自省所需的所有信息。
Qt宏定义
在Qt程序中我们可以看到一些关键字不属于纯C++关键字:signals
,slots
,Q_OBJECT
,emit
,SIGNAL
,SLOT
。这些被称为C++的Qt扩展宏,它们实际上就是简单的宏,在qobjectdefs.h
文件中定义。
signals,slots
#ifndef QT_ANNOTATE_ACCESS_SPECIFIER
# define QT_ANNOTATE_ACCESS_SPECIFIER(x)
#endif
...
# define Q_SLOTS QT_ANNOTATE_ACCESS_SPECIFIER(qt_slot)
# define Q_SIGNALS public QT_ANNOTATE_ACCESS_SPECIFIER(qt_signal)
...
#define slots Q_SLOTS
#define signals Q_SIGNALS
上面代码主要是定义了slots
和signals
两个宏,摘自qobjectdefs.h
文件。
我们可以看到这两个宏展开后就是一个空宏,因为# define QT_ANNOTATE_ACCESS_SPECIFIER(x)
定义的宏是一个空宏。
Q_OBJECT
#define Q_OBJECT \
public: \QT_WARNING_PUSH \Q_OBJECT_NO_OVERRIDE_WARNING \static const QMetaObject staticMetaObject; \virtual const QMetaObject *metaObject() const; \virtual void *qt_metacast(const char *); \virtual int qt_metacall(QMetaObject::Call, int, void **); \QT_TR_FUNCTIONS \
private: \Q_OBJECT_NO_ATTRIBUTES_WARNING \Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \QT_WARNING_POP \struct QPrivateSignal {}; \QT_ANNOTATE_CLASS(qt_qobject, "")
Q_OBJECT
定义了一堆函数和一个静态对象staticMetaObject
,这些函数在MOC生成的文件中会有实现。
emit
#ifndef QT_NO_EMIT
# define emit
#endif
emit
也是一个空宏。而且它不会被MOC解析,也就是说emit
没有任何实际意义(除了用来给开发人员提示外)。
SIGNAL,SLOT
Q_CORE_EXPORT const char *qFlagLocation(const char *method);
# define QLOCATION "\0" __FILE__ ":" QT_STRINGIFY(__LINE__)
...
# define SLOT(a) qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
这两个宏只是使用预处理器将参数转换为字符串,并在前面添加一段代码。
在调试模式下,如果信号连接不起作用,会以警告的形式输出代码的具体位置,QLOCATION
宏就是对应的位置字符串。
MOC生成的代码
上面示例代码通过MOC生成的代码将逐一展示并解释。
字符串表
struct qt_meta_stringdata_test_moc_t {QByteArrayData data[7];char stringdata0[80];
};
#define QT_MOC_LITERAL(idx, ofs, len) \Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \qptrdiff(offsetof(qt_meta_stringdata_test_moc_t, stringdata0) + ofs \- idx * sizeof(QByteArrayData)) \)
static const qt_meta_stringdata_test_moc_t qt_meta_stringdata_test_moc = {{
QT_MOC_LITERAL(0, 0, 8), // "test_moc"
QT_MOC_LITERAL(1, 9, 14), // "sigTestSignals"
QT_MOC_LITERAL(2, 24, 0), // ""
QT_MOC_LITERAL(3, 25, 20), // "sigTestSignals_Param"
QT_MOC_LITERAL(4, 46, 3), // "num"
QT_MOC_LITERAL(5, 50, 11), // "on_TestSlot"
QT_MOC_LITERAL(6, 62, 17) // "on_TestSlot_Param"},"test_moc\0sigTestSignals\0\0sigTestSignals_Param\0""num\0on_TestSlot\0on_TestSlot_Param"
};
#undef QT_MOC_LITERAL
这段代码的核心是初始化静态的qt_meta_stringdata_test_moc
对象,其中QByteArrayData
数组是用来存储类名、信号和槽的名字。QT_MOC_LITERAL
宏用来创建一个静态QByteArray
,它引用下面字符串中的索引值。qt_meta_stringdata_test_moc_t::stringdata0
数据就是存储类名、信号和槽的名字、参数名的字符串表。
自省表
static const uint qt_meta_data_test_moc[] = {// content:8, // revision0, // classname0, 0, // classinfo4, 14, // methods0, 0, // properties0, 0, // enums/sets0, 0, // constructors0, // flags2, // signalCount// signals: name, argc, parameters, tag, flags1, 0, 34, 2, 0x06 /* Public */,3, 1, 35, 2, 0x06 /* Public */,// slots: name, argc, parameters, tag, flags5, 0, 38, 2, 0x0a /* Public */,6, 1, 39, 2, 0x0a /* Public */,// signals: parametersQMetaType::Void,QMetaType::Void, QMetaType::Int, 4,// slots: parametersQMetaType::Void,QMetaType::Void, QMetaType::Int, 4,0 // eod
};
content
部分中,每行有一个值或两个值组成,如果有两个值,那么第一个值代表计数,第二个值代表该数组中描述开始的索引。比如方法methods
有四个,分别是两个槽函数和两个信号,所以methods
的第一个值是4。前面content
包含了索引0~13,第14开始是信号和槽,所以methods
的第二个值是14。
方法(信号和槽)字段由5个int
组成:
- 第一个是名称
这里的值对应的是上面字符串表中的索引值,比如当前信号名称是sigTestSignals
,对应字符串表中的索引是1。
- 第二个是参数的数量
- 第三个是参数描述的索引,该索引对应的是当前自省表中的索引值(即后面
parameters
部分对应的索引) - 后面两个值先忽略
调用信号和槽
void test_moc::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{if (_c == QMetaObject::InvokeMetaMethod) {auto *_t = static_cast<test_moc *>(_o);Q_UNUSED(_t)switch (_id) {case 0: _t->sigTestSignals(); break;case 1: _t->sigTestSignals_Param((*reinterpret_cast< int(*)>(_a[1]))); break;case 2: _t->on_TestSlot(); break;case 3: _t->on_TestSlot_Param((*reinterpret_cast< int(*)>(_a[1]))); break;default: ;}} else if (_c == QMetaObject::IndexOfMethod) {int *result = reinterpret_cast<int *>(_a[0]);{using _t = void (test_moc::*)();if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&test_moc::sigTestSignals)) {*result = 0;return;}}{using _t = void (test_moc::*)(int );if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&test_moc::sigTestSignals_Param)) {*result = 1;return;}}}
}
这段代码分为两部分:
- 调用信号和槽
当_c == QMetaObject::InvokeMetaMethod
时,通过id
索引对应的方法,然后调用。
- 信号索引
当_c == QMetaObject::IndexOfMethod
时,通过_a
来匹配信号,并返回对应信号的索引值(result)。
QMetaObject
QT_INIT_METAOBJECT const QMetaObject test_moc::staticMetaObject = { {QMetaObject::SuperData::link<QObject::staticMetaObject>(),qt_meta_stringdata_test_moc.data,qt_meta_data_test_moc,qt_static_metacall,nullptr,nullptr
} };const QMetaObject *test_moc::metaObject() const
{return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}void *test_moc::qt_metacast(const char *_clname)
{if (!_clname) return nullptr;if (!strcmp(_clname, qt_meta_stringdata_test_moc.stringdata0))return static_cast<void*>(this);return QObject::qt_metacast(_clname);
}int test_moc::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 < 4)qt_static_metacall(this, _c, _id, _a);_id -= 4;} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {if (_id < 4)*reinterpret_cast<int*>(_a[0]) = -1;_id -= 4;}return _id;
}
这段代码实现了Q_OBJECT
宏声明的接口和对象:test_moc::staticMetaObject
、metaObject()
、qt_metacast()
、qt_metacall()
。
staticMetaObject
这部分代码是用来初始化静态元对象的,主要包含字符串表中的索引数据(qt_meta_stringdata_test_moc.data
)、自省表数据(qt_meta_data_test_moc
)以及调用信号和槽的接口(qt_static_metacall
)。
metaObject()
该函数是返回元对象,其中QObject::d_ptr->dynamicMetaObject()
仅用于动态元对象(QML对象),一般情况下,该函数只返回staticMetaObject
。
qt_metacast()
该函数是Qt的动态类型转换工具,类似于C++中的dynamic_cast
。
调用该函数可以安全地将QObject
或其派生类的实例向下转换为更具体的类型。
qt_metacall()
该函数是QObject的一个核心功能,它在运行时调用对象的方法(如信号、槽或其他通过Q_INVOKABLE
宏定义的方法)。信号槽的调用就是通过该函数实现的。
信号
// SIGNAL 0
void test_moc::sigTestSignals()
{QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}// SIGNAL 1
void test_moc::sigTestSignals_Param(int _t1)
{void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
其实信号经过元对象编译后就是一个普通的函数,将该类的指针、静态元对象数据以及参数数组传递给QMetaObject::activate
。