Meta_Object Model
Qt meta-object系统基于三个方面:
1、QObject提供一个基类,方便派生类使用meta-object系统的功能;
2、Q_OBJECT宏,在类的声明体内激活meta-object功能,比如动态属性、信号、槽;
3、Meta Object编译器(MOC),为每个QObject派生类生成代码,以支持meta-object功能。
QObject定义了从一个QObject对象访问meta-object功能的接口,Q_OBJECT宏用来告诉编译器该类需要激活meta- object功能,编译器在扫描一个源文件时,如果发现类的声明中有这个宏,就会生成一些代码来为支持meta-object功能——主要是生成该类对应 MetaObject类以及对QObject的函数override。
QObject和QMetaObject:
顾名思义,QMetaObject包含了QObject的所谓的元数据,也就是QObject信息的一些描述信息:除了类型信息外,还包含QT中特 有的signal&slot信息。
QObject::metaObject ()方法返回一个QObject对象对应的metaobject对象,注意这个方法是virtual方法。如上文所说,如果一个类的声明中包含了 Q_OBJECT宏,编译器会生成代码来实现这个类对应的QMetaObject类,并重载QObject::metaObject()方法来返回这个 QMetaObject类的实例引用。这样当通过QObject类型的引用调用metaObejct方法时,返回的是这个引用的所指的真实对象的 metaobject。
如果一个类从QObject派生,确没有声明Q_OBJECT宏,那么这个类的metaobject对象不会被生成,这样这个类所声明的 signal slot都不能使用,而这个类实例调用metaObject()返回的就是其父类的metaobject对象,这样导致的后果就是你从这个类实例获得的元 数据其实都是父类的数据,这显然给你的代码埋下隐患。因此如果一个类从QOBject派生,它都应该声明Q_OBJECT宏,不管这个类有没有定义 signal&slot和Property。
这样每个QObject类都有一个对应的QMetaObject类,形成一个平行的类型层次。
QMetaObject提供的信息:
下面通过QMetaObject的接口来解读QMetaObject提供的信息:
1、基本信息
const char * className () const;
const QMetaObject * superClass () const
2、classinfo: 提供额外的类信息。其实就是一些名值对。 用户可以在类的声明中以Q_CLASSINFO(name, value)方式添加。
int classInfoCount () const
int classInfoOffset () const
QMetaClassInfo classInfo ( int index ) const
int indexOfClassInfo ( const char * name ) const
3、contructor:提供该类的构造方法信息
QMetaMethod constructor ( int index ) const
int constructorCount () const
int indexOfConstructor ( const char * constructor ) const
4、enum:描述该类声明体中所包含的枚举类型信息
QMetaEnum enumerator ( int index ) const
int enumeratorCount () const
int enumeratorOffset () const
int indexOfEnumerator ( const char * name ) const
5、method:描述类中所包含方法信息:包括property,signal,slot等,包括祖先类,如何组织暂时不确定。
QMetaMethod method ( int index ) const
int methodCount () const
int methodOffset () const
int indexOfMethod ( const char * method ) const
int indexOfSignal ( const char * signal ) const
int indexOfSlot ( const char * slot ) const
6、property:类型的属性信息
QMetaProperty property ( int index ) const
int propertyCount () const
int propertyOffset () const
int indexOfProperty ( const char * name ) const
QMetaProperty userProperty () const //返回类中设置了USER flag的属性,(难道只能有一个这样的属性?)
注意:对于类里面定义的函数,构造函数,枚举,只有加上一些宏才表示你希望为方法提供meta信息。比如 Q_ENUMS用来注册宏,
Q_INVACABLE用来注册方法(包括构造函数)。Qt这么设计的原因应该是避免meta信息的臃肿。
Qt扩展了C++的语法,加入了signals,slots,emit,
moc文件分析
我们知道Qt 不是使用的“标准的” C++ 语言,而是对其进行了一定程度的“扩展”。这里我们从Qt新增加的关键字就可以看出来:signals、slots 或者 emit。所以有人会觉得 Qt 的程序编译速度慢,这主要是因为在 Qt 将源代码交给标准 C++ 编译器,如 gcc 之前,需要事先将这些扩展的语法去除掉。完成这一操作的就是 moc。
moc 全称是 Meta-Object Compiler,也就是“元对象编译器”。Qt 程序在交由标准编译器编译之前,先要使用 moc 分析 C++ 源文件。如果它发现在一个头文件中包含了宏 Q_OBJECT,则会生成另外一个 C++ 源文件。这个源文件中包含了 Q_OBJECT 宏的实现代码。这个新的文件名字将会是原文件名前面加上 moc_ 构成。这个新的文件同样将进入编译系统,最终被链接到二进制代码中去。因此我们可以知道,这个新的文件不是“替换”掉旧的文件,而是与原文件一起参与编译。另外,我们还可以看出一点,moc 的执行是在预处理器之前。因为预处理器执行之后,Q_OBJECT 宏就不存在了。
让我们用一个实例来分析下moc在搞什么鬼,添加一个新类MyClass派生自QObject且使用Q_OBJECT宏,
编译后发现在生成目录下会产生一个moc_myclass.cpp文件,代码贴上来:
myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QObject>
class MyClass : public QObject
{
Q_OBJECT
Q_CLASSINFO("AUTHOR", "HeWei")
Q_CLASSINFO("VERSION", "1.0")
Q_CLASSINFO("TIME", "2017/01/01")
Q_PROPERTY(int prop1)
public:
enum EMyClass{
EMyClass_1 = 1,
EMyClass_2 = 2,
};
Q_ENUMS(EMyClass)
public:
MyClass(QObject *parent)
: QObject(parent)
{
m_1 = 0;
}
Q_INVOKABLE MyClass(int initparam, QObject *parent2)
: QObject(parent2)
{
m_1 = initparam;
}
Q_INVOKABLE int method1(int methodparam) {return 0;}
int method2(int method2param) {return 0;}
signals:
void signal1(int signalparam);
public slots:
int slot1(int slotparam) {return 0;}
private:
int m_1;
};
#endif // MYCLASS_H
moc_myclass.cpp
/****************************************************************************
** Meta object code from reading C++ file 'myclass.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.7.0)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/
#include "../../core01/myclass.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'myclass.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.7.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
struct qt_meta_stringdata_MyClass_t {
QByteArrayData data[20];
char stringdata0[161];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_MyClass_t, stringdata0) + ofs \
- idx * sizeof(QByteArrayData)) \
)
static const qt_meta_stringdata_MyClass_t qt_meta_stringdata_MyClass = {
{
QT_MOC_LITERAL(0, 0, 7), // "MyClass"
QT_MOC_LITERAL(1, 8, 6), // "AUTHOR"
QT_MOC_LITERAL(2, 15, 5), // "HeWei"
QT_MOC_LITERAL(3, 21, 7), // "VERSION"
QT_MOC_LITERAL(4, 29, 3), // "1.0"
QT_MOC_LITERAL(5, 33, 4), // "TIME"
QT_MOC_LITERAL(6, 38, 10), // "2017/01/01"
QT_MOC_LITERAL(7, 49, 7), // "signal1"
QT_MOC_LITERAL(8, 57, 0), // ""
QT_MOC_LITERAL(9, 58, 11), // "signalparam"
QT_MOC_LITERAL(10, 70, 5), // "slot1"
QT_MOC_LITERAL(11, 76, 9), // "slotparam"
QT_MOC_LITERAL(12, 86, 7), // "method1"
QT_MOC_LITERAL(13, 94, 11), // "methodparam"
QT_MOC_LITERAL(14, 106, 9), // "initparam"
QT_MOC_LITERAL(15, 116, 7), // "parent2"
QT_MOC_LITERAL(16, 124, 5), // "prop1"
QT_MOC_LITERAL(17, 130, 8), // "EMyClass"
QT_MOC_LITERAL(18, 139, 10), // "EMyClass_1"
QT_MOC_LITERAL(19, 150, 10) // "EMyClass_2"
},
"MyClass\0AUTHOR\0HeWei\0VERSION\0""1.0\0"
"TIME\0""2017/01/01\0signal1\0\0signalparam\0"
"slot1\0slotparam\0method1\0methodparam\0"
"initparam\0parent2\0prop1\0EMyClass\0"
"EMyClass_1\0EMyClass_2"
};
#undef QT_MOC_LITERAL
static const uint qt_meta_data_MyClass[] = {
// content:
7, // revision
0, // classname
3, 14, // classinfo
3, 20, // methods
1, 49, // properties
1, 52, // enums/sets
1, 60, // constructors
0, // flags
1, // signalCount
// classinfo: key, value
1, 2,
3, 4,
5, 6,
// signals: name, argc, parameters, tag, flags
7, 1, 35, 8, 0x06 /* Public */,
// slots: name, argc, parameters, tag, flags
10, 1, 38, 8, 0x0a /* Public */,
// methods: name, argc, parameters, tag, flags
12, 1, 41, 8, 0x02 /* Public */,
// signals: parameters
QMetaType::Void, QMetaType::Int, 9,
// slots: parameters
QMetaType::Int, QMetaType::Int, 11,
// methods: parameters
QMetaType::Int, QMetaType::Int, 13,
// constructors: parameters
0x80000000 | 8, QMetaType::Int, QMetaType::QObjectStar, 14, 15,
// properties: name, type, flags
16, QMetaType::Int, 0x00095000,
// enums: name, flags, count, data
17, 0x0, 2, 56,
// enum data: key, value
18, uint(MyClass::EMyClass_1),
19, uint(MyClass::EMyClass_2),
// constructors: name, argc, parameters, tag, flags
0, 2, 44, 8, 0x0e /* Public */,
0 // eod
};
void MyClass::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::CreateInstance) {
switch (_id) {
case 0: { MyClass *_r = new MyClass((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< QObject*(*)>(_a[2])));
if (_a[0]) *reinterpret_cast<QObject**>(_a[0]) = _r; } break;
default: break;
}
} else if (_c == QMetaObject::InvokeMetaMethod) {
MyClass *_t = static_cast<MyClass *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->signal1((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: { int _r = _t->slot1((*reinterpret_cast< int(*)>(_a[1])));
if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; } break;
case 2: { int _r = _t->method1((*reinterpret_cast< int(*)>(_a[1])));
if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; } break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
void **func = reinterpret_cast<void **>(_a[1]);
{
typedef void (MyClass::*_t)(int );
if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&MyClass::signal1)) {
*result = 0;
return;
}
}
}
#ifndef QT_NO_PROPERTIES
else if (_c == QMetaObject::ReadProperty) {
} else if (_c == QMetaObject::WriteProperty) {
} else if (_c == QMetaObject::ResetProperty) {
}
#endif // QT_NO_PROPERTIES
}
const QMetaObject MyClass::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_MyClass.data,
qt_meta_data_MyClass, qt_static_metacall, Q_NULLPTR, Q_NULLPTR}
};
const QMetaObject *MyClass::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
void *MyClass::qt_metacast(const char *_clname)
{
if (!_clname) return Q_NULLPTR;
if (!strcmp(_clname, qt_meta_stringdata_MyClass.stringdata0))
return static_cast<void*>(const_cast< MyClass*>(this));
return QObject::qt_metacast(_clname);
}
int MyClass::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 < 3)
qt_static_metacall(this, _c, _id, _a);
_id -= 3;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 3)
*reinterpret_cast<int*>(_a[0]) = -1;
_id -= 3;
}
#ifndef QT_NO_PROPERTIES
else if (_c == QMetaObject::ReadProperty || _c == QMetaObject::WriteProperty
|| _c == QMetaObject::ResetProperty || _c == QMetaObject::RegisterPropertyMetaType) {
qt_static_metacall(this, _c, _id, _a);
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyDesignable) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyScriptable) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyStored) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyEditable) {
_id -= 1;
} else if (_c == QMetaObject::QueryPropertyUser) {
_id -= 1;
}
#endif // QT_NO_PROPERTIES
return _id;
}
// SIGNAL 0
void MyClass::signal1(int _t1)
{
void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
QT_END_MOC_NAMESPACE
moc源文件中先声明了一个qt_meta_stringdata_MyClass_t结构体和一个 QT_MOC_LITERAL(idx, ofs, len)宏
struct qt_meta_stringdata_MyClass_t {
QByteArrayData data[20];
char stringdata0[161];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_MyClass_t, stringdata0) + ofs \
- idx * sizeof(QByteArrayData)) \
)
接着定义了一个static const静态常量qt_meta_stringdata_MyClass_t结构体
static const qt_meta_stringdata_MyClass_t qt_meta_stringdata_MyClass = {
{
QT_MOC_LITERAL(0, 0, 7), // "MyClass"
QT_MOC_LITERAL(1, 8, 6), // "AUTHOR"
QT_MOC_LITERAL(2, 15, 5), // "HeWei"
QT_MOC_LITERAL(3, 21, 7), // "VERSION"
QT_MOC_LITERAL(4, 29, 3), // "1.0"
QT_MOC_LITERAL(5, 33, 4), // "TIME"
QT_MOC_LITERAL(6, 38, 10), // "2017/01/01"
QT_MOC_LITERAL(7, 49, 7), // "signal1"
QT_MOC_LITERAL(8, 57, 0), // ""
QT_MOC_LITERAL(9, 58, 11), // "signalparam"
QT_MOC_LITERAL(10, 70, 5), // "slot1"
QT_MOC_LITERAL(11, 76, 9), // "slotparam"
QT_MOC_LITERAL(12, 86, 7), // "method1"
QT_MOC_LITERAL(13, 94, 11), // "methodparam"
QT_MOC_LITERAL(14, 106, 9), // "initparam"
QT_MOC_LITERAL(15, 116, 7), // "parent2"
QT_MOC_LITERAL(16, 124, 5), // "prop1"
QT_MOC_LITERAL(17, 130, 8), // "EMyClass"
QT_MOC_LITERAL(18, 139, 10), // "EMyClass_1"
QT_MOC_LITERAL(19, 150, 10) // "EMyClass_2"
},
"MyClass\0AUTHOR\0HeWei\0VERSION\0""1.0\0"
"TIME\0""2017/01/01\0signal1\0\0signalparam\0"
"slot1\0slotparam\0method1\0methodparam\0"
"initparam\0parent2\0prop1\0EMyClass\0"
"EMyClass_1\0EMyClass_2"
};
QT_MOC_LITERAL(idx, off, len)三个参数分别代表索引、偏移、长度
通过偏移off[1]和长度len[1]我们可以得到下一个数据的偏移位置off[2]=off[1]+len[1]+1(\0字符串结束符占一个字节)
qt_meta_stringdata_MyClass.stringdata0是一个字符数组,里面记录了MyClass类中所有的标识符,包括类名classname、类信息classinfo、方法(包括信号、槽、一般函数)名method、方法参数、属性名property、枚举名enum、枚举项名
接着定义了一个qt_meta_data_MyClass,这是一个const static uint[]静态常量整型数组。
static const uint qt_meta_data_MyClass[] = {
// content:
7, // revision
0, // classname
3, 14, // classinfo
3, 20, // methods
1, 49, // properties
1, 52, // enums/sets
1, 60, // constructors
0, // flags
1, // signalCount
// classinfo: key, value
1, 2,
3, 4,
5, 6,
// signals: name, argc, parameters, tag, flags
7, 1, 35, 8, 0x06 /* Public */,
// slots: name, argc, parameters, tag, flags
10, 1, 38, 8, 0x0a /* Public */,
// methods: name, argc, parameters, tag, flags
12, 1, 41, 8, 0x02 /* Public */,
// signals: parameters
QMetaType::Void, QMetaType::Int, 9,
// slots: parameters
QMetaType::Int, QMetaType::Int, 11,
// methods: parameters
QMetaType::Int, QMetaType::Int, 13,
// constructors: parameters
0x80000000 | 8, QMetaType::Int, QMetaType::QObjectStar, 14, 15,
// properties: name, type, flags
16, QMetaType::Int, 0x00095000,
// enums: name, flags, count, data
17, 0x0, 2, 56,
// enum data: key, value
18, uint(MyClass::EMyClass_1),
19, uint(MyClass::EMyClass_2),
// constructors: name, argc, parameters, tag, flags
0, 2, 44, 8, 0x0e /* Public */,
0 // eod
};
根据注释,我们可以知道里面定义了revision(moc修正版本号5.7),索引为0的字符串表示classname
3, 14, // classinfo 3代表有3个classinfo,14代表qt_meta_data_MyClass[14]就是classinfo的详细信息记录起点,即
// classinfo: key, value
1, 2,
3, 4,
5, 6,
methods,properties,enums/sets,constructors记录方法类似
flags
signalCount代表信号的数量
接下来的数据也就记录了各自的详细信息
然后就是实现了Q_OBJECT宏声明的函数staticMetaObject、metaObject、qt_metacast、qt_metacall、qt_static_metacall
#define Q_OBJECT \
public: \
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: \
static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
staticMetaObject直接返回的是一个QMetaObject的内部数据
const QMetaObject MyClass::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_MyClass.data,
qt_meta_data_MyClass, qt_static_metacall, Q_NULLPTR, Q_NULLPTR}
};
对应的是QMetaObject的结构体数据,即超类QMetaObject 指针,标识符字符串数据,元信息,qt_static_metacall调用指针,相关的QMetaObject 指针(一般为空),额外的数据(一般为空)
struct { // private data
const QMetaObject *superdata;
const QByteArrayData *stringdata;
const uint *data;
typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
StaticMetacallFunction static_metacall;
const QMetaObject * const *relatedMetaObjects;
void *extradata; //reserved for future use
} d;
qt_metacast在转换类时中做了一个类名检查,如果不是相关继承类,会返回NuLL。
qt_metacall提供方法调用(qt_static_metacall),属性查询,读写等操作。
此外moc文件中还添加上了我们声明的信号,这就是为什么信号只用我们声明不用定义的原因。
信号实际上也就是一个函数,最终会调用QMetaObject::activate。
emit sig;实际上就是调用这个信号函数罢了