Qt源码版本
Qt 5.12.0
moc文件
Qt在编译之前会分析源文件,当发现包含了Q_OBJECT宏,则会生成另外一个标准的C++源文件(包含Q_OBJECT宏实现的代码,文件名为moc_+原文件名),该文件同样进入编译系统,最终被链接到二进制代码中去。此时,Qt将自己增加的扩展转换成了标准的C++文件,moc全称是Meta-Object Compiler,也就是“元对象编译器”。
Q_OBJECT的宏定义
位置:qobjectdefs.h
/* qmake ignore 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, "")
Moc文件分析
测试代码
#include <QObject>
class CTestMoc : public QObject
{
Q_OBJECT
public:
CTestMoc(){}
~CTestMoc(){}
signals:
void Test1();
void Test2(int iTemp);
private slots:
void OnTest1(){}
void OnTest2(int iTemp){}
};
Moc代码
测试代码编译之后,生成moc_ctestmoc.cpp文件,包含几个重要的数据结构。
第一个结构
struct qt_meta_stringdata_CTestMoc_t {
QByteArrayData data[];
char stringdata0[];
};
data字段是一个由数组组成的数组,数组大小取决于信号/槽个数。这个数组在调用QObject::connect时用来匹配信号名或槽名。
stringdata存放的是字符资源,存放全部的信号名、槽名、类名。
static const qt_meta_stringdata_CTestMoc_t qt_meta_stringdata_CTestMoc = {
{
QT_MOC_LITERAL(, , ), // "CTestMoc"
QT_MOC_LITERAL(, , ), // "Test1"
QT_MOC_LITERAL(, , ), // ""
QT_MOC_LITERAL(, , ), // "Test2"
QT_MOC_LITERAL(, , ), // "iTemp"
QT_MOC_LITERAL(, , ), // "OnTest1"
QT_MOC_LITERAL(, , ) // "OnTest2" },
"CTestMoc\0Test1\0\0Test2\0iTemp\0OnTest1\0"
"OnTest2"
};
qt_meta_stringdata_CTestMoc_t是一个qt_meta_stringdata_CTestMoc的实例
QT_MOC_LITERAL(0, 0, 8),这个宏生成一个byte数组,第一参数是索引,可以看到索引是由 0-6共7个组成,对应的是data字段的长度7;第二个参数是在stringdata字段中的开始位置;第三个参数是长度。QT_MOC_LITERAL(0, 0, 8) 索引是0, 开始位置是0,长度是8,对应的字符是"CTestMoc",后面的以此类推。
第二个结构
static const uint qt_meta_data_CTestMoc[] = { // content:
, // revision
, // classname
, , // classinfo
, , // methods
, , // properties
, , // enums/sets
, , // constructors
, // flags
, // signalCount // signals: name, argc, parameters, tag, flags
, , , , 0x06 /* Public */,
, , , , 0x06 /* Public */, // slots: name, argc, parameters, tag, flags
, , , , 0x08 /* Private */,
, , , , 0x08 /* Private */, // signals: parameters
QMetaType::Void,
QMetaType::Void, QMetaType::Int, , // slots: parameters
QMetaType::Void,
QMetaType::Void, QMetaType::Int, , // eod
};
该数组的前14个uint 描述的是元对象的私有信息,定义在qmetaobject_p.h文件的QMetaObjectPrivate结构体当中;这个结构体中“4, 14, // methods”描述的是信号/槽的个数和在表中的偏移量,即14个uint之后是信号/槽的信息。
从qt_meta_data_CTestMoc这个表中可以看到每描述一个信号或槽需要5个uint。
例如,从表的第14个uint开始描述的信号信息
// signals: name, argc, parameters, tag, flags
1, 0, 34, 2, 0x06,
3, 1, 35, 2, 0x06,
name:对应的是qt_meta_stringdata_CTestMoc 索引,例如1 对应的是Test1
argc:参数个数
parameters : 参数的在qt_meta_data_CTestMoc这个表中的索引位置。
// signals: parameters
QMetaType::Void,
QMetaType::Void, QMetaType::Int, 4,
void 是信号的返回值,QMetaType::Int是参数类型, 4 是参数名,在qt_meta_stringdata_CTestMoc中的索引值。
tag:这个字段的数值对应的是qt_meta_stringdata_CTestMoc 索引,在这个moc文件里对应的是一个空字符串,具体怎么用,在源代码里没看懂。
flags:是一个特征值,是在 enum MethodFlags 枚举中定义。
enum MethodFlags {
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
};
可以看到,槽对应的是MethodSlot 0x08, 信号对应的是MethodSignal 和AccessPublic 相或。
第三部分
void CTestMoc::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
CTestMoc *_t = static_cast<CTestMoc *>(_o);
Q_UNUSED(_t)
switch (_id) {
case : _t->Test1(); break;
case : _t->Test2((*reinterpret_cast< int(*)>(_a[]))); break;
case : _t->OnTest1(); break;
case : _t->OnTest2((*reinterpret_cast< int(*)>(_a[]))); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[]);
{
using _t = void (CTestMoc::*)();
if (*reinterpret_cast<_t *>(_a[]) == static_cast<_t>(&CTestMoc::Test1)) {
*result = ;
return;
}
}
{
using _t = void (CTestMoc::*)(int );
if (*reinterpret_cast<_t *>(_a[]) == static_cast<_t>(&CTestMoc::Test2)) {
*result = ;
return;
}
}
}
}
qt_metacall方法通过索引调用其它内部方法。Qt动态机制不采用指针,而由索引实现。实际调用方法的工作由编译器实现。这使得信号和槽的机制执行效率比较高。
参数由一个指向指针数组的指针进行传递,并在调用方法时进行适当的转换。使用指针是将不同类型的参数放在一个数组的唯一办法。参数索引从1开始,因为0号代表函数返回值。
第四部分
QT_INIT_METAOBJECT const QMetaObject CTestMoc::staticMetaObject = { {
&QObject::staticMetaObject,
qt_meta_stringdata_CTestMoc.data,
qt_meta_data_CTestMoc,
qt_static_metacall,
nullptr,
nullptr
} };
这个静态变量保存了moc文件的信号/槽的调用索引信息。
在信号/槽绑定的时候,就是通过这些信息建立绑定关系。
第五部分
// SIGNAL 0
void CTestMoc::Test1()
{
QMetaObject::activate(this, &staticMetaObject, , nullptr);
} // SIGNAL 1
void CTestMoc::Test2(int _t1)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, , _a);
}
从以上代码可见,信号即函数。
结论
- Qt的信号/槽的调用不是通过指针方式调用的,而是通过索引方式来调用的。
- 信号也是一个函数。
- emit某个信号,就相当于调用这个信号connect时所关联的槽函数。
- 所谓信号槽,实际就是观察者模式。
注意事项
- 信号与反应槽的定义是在类中实现的。那么,非类成员的函数,比如说一个全局函数可不可以也这样做呢?答案是不行,只有是自身定义了信号的类或其子类才可以发出该种信号。
- 一个对象的不同信号可以连接至不同的对象。当一个信号被释放时,与之连接的反应槽将被立刻执行,就象是在程序中直接调用该函数一样。信号的释放过程是阻塞的,这意味着只有当反应槽执行完毕后该信号释放过程才返回。如果一个信号与多个反应槽连接,则这些反应槽将被顺序执行,排序过程则是任意的。因此如果程序中对这些反应槽的先后执行次序有严格要求的,应特别注意。
- 使用信号时还应注意:信号的定义过程是在类的定义过程即头文件中实现的。为了中间编译工具moc的正常运行,不要在源文件(.cpp)中定义信号,同时信号本身不应返回任何数据类型,即是空值(void)。如果你要设计一个通用的类或控件,则在信号或反应槽的参数中应尽可能使用常规数据以增加通用性。如代码中valueChanged的参数为int型,如果它使用了特殊类型如QRangeControl::Range,那么这种信号只能与RangeControl中的反应槽连接。如前所述,反应槽也是常规函数,与未定义slots的用户函数在执行上没有任何区别。
- 但在程序中不可把信号与常规函数连接在一起,否则信号的释放不会引起对应函数的执行。要命的是中间编译程序moc并不会对此种情况报错,C++编译程序更不会报错。初学者比较容易忽略这一点,往往是程序编好了没有错误,逻辑上也正确,但运行时就是不按自己的意愿出现结果,这时候应检查一下是不是这方面的疏忽。
- 信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。
- SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串。
测试Moc全部代码
/****************************************************************************
** Meta object code from reading C++ file 'ctestmoc.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.0)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/ #include "../CTestMoc/ctestmoc.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'ctestmoc.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.12.0. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif QT_BEGIN_MOC_NAMESPACE
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
struct qt_meta_stringdata_CTestMoc_t {
QByteArrayData data[];
char stringdata0[];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_CTestMoc_t, stringdata0) + ofs \
- idx * sizeof(QByteArrayData)) \
)
static const qt_meta_stringdata_CTestMoc_t qt_meta_stringdata_CTestMoc = {
{
QT_MOC_LITERAL(, , ), // "CTestMoc"
QT_MOC_LITERAL(, , ), // "Test1"
QT_MOC_LITERAL(, , ), // ""
QT_MOC_LITERAL(, , ), // "Test2"
QT_MOC_LITERAL(, , ), // "iTemp"
QT_MOC_LITERAL(, , ), // "OnTest1"
QT_MOC_LITERAL(, , ) // "OnTest2" },
"CTestMoc\0Test1\0\0Test2\0iTemp\0OnTest1\0"
"OnTest2"
};
#undef QT_MOC_LITERAL static const uint qt_meta_data_CTestMoc[] = { // content:
, // revision
, // classname
, , // classinfo
, , // methods
, , // properties
, , // enums/sets
, , // constructors
, // flags
, // signalCount // signals: name, argc, parameters, tag, flags
, , , , 0x06 /* Public */,
, , , , 0x06 /* Public */, // slots: name, argc, parameters, tag, flags
, , , , 0x08 /* Private */,
, , , , 0x08 /* Private */, // signals: parameters
QMetaType::Void,
QMetaType::Void, QMetaType::Int, , // slots: parameters
QMetaType::Void,
QMetaType::Void, QMetaType::Int, , // eod
}; void CTestMoc::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
CTestMoc *_t = static_cast<CTestMoc *>(_o);
Q_UNUSED(_t)
switch (_id) {
case : _t->Test1(); break;
case : _t->Test2((*reinterpret_cast< int(*)>(_a[]))); break;
case : _t->OnTest1(); break;
case : _t->OnTest2((*reinterpret_cast< int(*)>(_a[]))); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[]);
{
using _t = void (CTestMoc::*)();
if (*reinterpret_cast<_t *>(_a[]) == static_cast<_t>(&CTestMoc::Test1)) {
*result = ;
return;
}
}
{
using _t = void (CTestMoc::*)(int );
if (*reinterpret_cast<_t *>(_a[]) == static_cast<_t>(&CTestMoc::Test2)) {
*result = ;
return;
}
}
}
} QT_INIT_METAOBJECT const QMetaObject CTestMoc::staticMetaObject = { {
&QObject::staticMetaObject,
qt_meta_stringdata_CTestMoc.data,
qt_meta_data_CTestMoc,
qt_static_metacall,
nullptr,
nullptr
} }; const QMetaObject *CTestMoc::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
} void *CTestMoc::qt_metacast(const char *_clname)
{
if (!_clname) return nullptr;
if (!strcmp(_clname, qt_meta_stringdata_CTestMoc.stringdata0))
return static_cast<void*>(this);
return QObject::qt_metacast(_clname);
} int CTestMoc::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < )
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
if (_id < )
qt_static_metacall(this, _c, _id, _a);
_id -= ;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < )
*reinterpret_cast<int*>(_a[]) = -;
_id -= ;
}
return _id;
} // SIGNAL 0
void CTestMoc::Test1()
{
QMetaObject::activate(this, &staticMetaObject, , nullptr);
} // SIGNAL 1
void CTestMoc::Test2(int _t1)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, , _a);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE