元对象系统可以说是QT最核心的功能了,就是因为元对象系统的存在,才有了QT的信号槽和动态属性,今天打算复习下这套机制。
以下内容都是自己的理解,有不正确的地方欢迎指正!
Qt元对象系统的实现主要依赖于三点:继承QObject,包含Q_OBJEC宏以及moc编译器。
对于继承了QObject且在头文件声明了Q_OBJECT宏的类classA,moc编译器会扩展生成一个moc_classA.cpp的文件,在里面添加了一些支持信号槽等功能的代码————主要是生成classA对性的metaObject类,以及一些接口的重载。
Q_OBJECT
我们先看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宏的类,会定义一个QMetaObject类型的静态常量成员变量staticMetaObject,以及一个静态函数:
static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
然后有几个宏我们再来分析下:
#define QT_DO_PRAGMA(text) _Pragma(#text)
# define QT_WARNING_PUSH QT_DO_PRAGMA(GCC diagnostic push)
QT_WARNING_PUSH告诉编译器实际采用了C++11的语法
# define QT_WARNING_DISABLE_GCC(text) QT_DO_PRAGMA(GCC diagnostic ignored text)
# define Q_OBJECT_NO_OVERRIDE_WARNING QT_WARNING_DISABLE_GCC("-Wsuggest-override")
Q_OBJECT_NO_OVERRIDE_WARNING,让编译器忽略"-Wsuggest-override"这个警告。
# define QT_TR_FUNCTIONS \
static inline QString tr(const char *s, const char *c = Q_NULLPTR, int n = -1) \
{ return staticMetaObject.tr(s, c, n); } \
QT_DEPRECATED static inline QString trUtf8(const char *s, const char *c = Q_NULLPTR, int n = -1) \
{ return staticMetaObject.tr(s, c, n); }
QT_TR_FUNCTIONS宏用于国际化功能,实际是两个内联函数tr和trUtf8,也是我们常用的接口.
Q_OBJECT_NO_ATTRIBUTES_WARNING在我用的5.9版本是个空宏。
我们先看其他三个虚函数,moc_classA.cpp里面会对他们进行重写,先看下大概的含义:
virtual const QMetaObject *metaObject() const; \ 用于获取类拥有的元对象
virtual void *qt_metacast(const char *); \ 通过元对象获取对象指针
virtual int qt_metacall(QMetaObject::Call, int, void **); \ 用于信号槽机制
等下我们在分析moc_classA.cpp文件的时候再详细看这几个接口。
struct QPrivateSignal {};
是一个私有的空结构体,对函数功能来说没啥用,就是在信号被触发时,挂在参数里提醒程序员这是一个私有信号的触发
QMetaObject
现在先来看下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;
这个结构体定义了元对象的所有数据:
const QMetaObject *superdata; 指向父类的staticMetaObject
const QByteArrayData *stringdata; 保存类名,信号名以及槽函数名的字符串
const uint *data; 只知道保存了一些二进制信息,具体不太理解
接下来我们看一些重要的接口。
inline const QMetaObject *QMetaObject::superClass() const
{ return d.superdata; }
之前也说过,superdata保存的是父类的QMetaObject指针,所以这就是返回父类QMetaObject指针。
static inline const QMetaObjectPrivate *priv(const uint* data)
{ return reinterpret_cast<const QMetaObjectPrivate*>(data); }
static inline const char *objectClassName(const QMetaObject *m)
{
return rawStringData(m, priv(m->d.data)->className);
}
static inline const char *rawStringData(const QMetaObject *mo, int index)
{
return stringData(mo, index).data();
}
const char *QMetaObject::className() const
{
return objectClassName(this);
}
static inline const QByteArray stringData(const QMetaObject *mo, int index)
{
const QByteArrayDataPtr data = { const_cast<QByteArrayData*>(&mo->d.stringdata[index]) };
return data;
}
priv接口将d.data转换成了QMetaObjectPrivate指针,我们之前说d.data保存了一些二进制数据,现在来看就是其实就是QMetaObjectPrivate的指针。而之前也说了,
stringdata保存了类名以及信号槽名的字符串,所以className()就是根据d.data中记录的索引,从d.stringdata获取类的名字。
简单看下QMetaObjectPrivate的部分定义:
struct QMetaObjectPrivate
{
enum { OutputRevision = 7 }; // Used by moc, qmetaobjectbuilder and qdbus
int revision; 版本号
int className; 类名称索引,刚刚我们用过
int classInfoCount, classInfoData; classInfo的数量,及索引 ,需要有宏定义才会被MOC生成
int methodCount, methodData; 函数数量及索引,需要有宏定义才会被MOC生成
int propertyCount, propertyData; property数量及索引,需要有宏定义才会被MOC生成
int enumeratorCount, enumeratorData; 枚举成员数量及索引,需要有宏定义才会被MOC生成
int constructorCount, constructorData; //since revision 2 构造函数数量及索引,需要有宏定义才会被MOC生成
int flags; //since revision 3 flags的数量
int signalCount; //since revision 4 信号数量
...
}
以后有机会再详细分析这个类,先继续看QMetaObject的接口:
bool QMetaObject::inherits(const QMetaObject *metaObject) const Q_DECL_NOEXCEPT
{
const QMetaObject *m = this;
do {
if (metaObject == m)
return true;
} while ((m = m->d.superdata));
return false;
}
这个接口用来判断是否与目标metaobject有继承关系。
int QMetaObject::methodCount() const
{
int n = priv(d.data)->methodCount;
const QMetaObject *m = d.superdata;
while (m) {
n += priv(m->d.data)->methodCount;
m = m->d.superdata;
}
return n;
}
返回自身和所有基类的方法总和。
int QMetaObject::methodOffset() const
{
int offset = 0;
const QMetaObject *m = d.superdata;
while (m) {
offset += priv(m->d.data)->methodCount;
m = m->d.superdata;
}
return offset;
}
这个接口返回方法的偏移量。
int QMetaObject::indexOfMethod(const char *method) const
{
const QMetaObject *m = this;
int i;
QArgumentTypeArray types;
QByteArray name = QMetaObjectPrivate::decodeMethodSignature(method, types);
i = indexOfMethodRelative<0>(&m, name, types.size(), types.constData());
if (i >= 0)
i += m->methodOffset();
return i;
}
返回方法的索引。
QMetaMethod QMetaObject::method(int index) const
{
int i = index;
i -= methodOffset();
if (i < 0 && d.superdata)
return d.superdata->method(index);
QMetaMethod result;
if (i >= 0 && i < priv(d.data)->methodCount) {
result.mobj = this;
result.handle = priv(d.data)->methodData + 5*i;
}
return result;
}
根据索引,获取方法。返回的是一个QMetaMethod类型,Qt提供了QMetaClassInfo,QMetaMethod、QMetaProperty、QMetaEnum来包装这些元信息和对其的一些操作,比如QMetaMethod就提供了name、parameterTypes、invoke等接口。以后有机会再详细记录一下
bool QMetaObject::invokeMethod(QObject *obj,
const char *member,
Qt::ConnectionType type,
QGenericReturnArgument ret,
QGenericArgument val0,
QGenericArgument val1,
QGenericArgument val2,
QGenericArgument val3,
QGenericArgument val4,
QGenericArgument val5,
QGenericArgument val6,
QGenericArgument val7,
QGenericArgument val8,
QGenericArgument val9)
{
if (!obj)
return false;
QVarLengthArray<char, 512> sig;
int len = qstrlen(member);
if (len <= 0)
return false;
sig.append(member, len);
sig.append('(');
const char *typeNames[] = {ret.name(), val0.name(), val1.name(), val2.name(), val3.name(),
val4.name(), val5.name(), val6.name(), val7.name(), val8.name(),
val9.name()};
int paramCount;
for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {
len = qstrlen(typeNames[paramCount]);
if (len <= 0)
break;
sig.append(typeNames[paramCount], len);
sig.append(',');
}
if (paramCount == 1)
sig.append(')'); // no parameters
else
sig[sig.size() - 1] = ')';
sig.append('\0');
const QMetaObject *meta = obj->metaObject();
int idx = meta->indexOfMethod(sig.constData());
if (idx < 0) {
QByteArray norm = QMetaObject::normalizedSignature(sig.constData());
idx = meta->indexOfMethod(norm.constData());
}
if (idx < 0 || idx >= meta->methodCount()) {
// This method doesn't belong to us; print out a nice warning with candidates.
qWarning("QMetaObject::invokeMethod: No such method %s::%s%s",
meta->className(), sig.constData(), findMethodCandidates(meta, member).constData());
return false;
}
QMetaMethod method = meta->method(idx);
return method.invoke(obj, type, ret,
val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
}
通过对比函数名,返回值类型,函数形参来构建标签信息,通过调用QMetaObject::indexOfMethod来获取函数索引,在调用QMetaObject::method来获取对应的QMetaMethod,在调用了invoke实现调用。我们来看一下invoke的实现。
bool QMetaMethod::invoke(QObject *object,
Qt::ConnectionType connectionType,
QGenericReturnArgument returnValue,
QGenericArgument val0,
QGenericArgument val1,
QGenericArgument val2,
QGenericArgument val3,
QGenericArgument val4,
QGenericArgument val5,
QGenericArgument val6,
QGenericArgument val7,
QGenericArgument val8,
QGenericArgument val9) const
{
if (!object || !mobj)
return false;
Q_ASSERT(mobj->cast(object));
// check return type
if (returnValue.data()) {
const char *retType = typeName();
if (qstrcmp(returnValue.name(), retType) != 0) {
// normalize the return value as well
QByteArray normalized = QMetaObject::normalizedType(returnValue.name());
if (qstrcmp(normalized.constData(), retType) != 0) {
// String comparison failed, try compare the metatype.
int t = returnType();
if (t == QMetaType::UnknownType || t != QMetaType::type(normalized))
return false;
}
}
}
// check argument count (we don't allow invoking a method if given too few arguments)
const char *typeNames[] = {
returnValue.name(),
val0.name(),
val1.name(),
val2.name(),
val3.name(),
val4.name(),
val5.name(),
val6.name(),
val7.name(),
val8.name(),
val9.name()
};
int paramCount;
for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {
if (qstrlen(typeNames[paramCount]) <= 0)
break;
}
if (paramCount <= QMetaMethodPrivate::get(this)->parameterCount())
return false;
// check connection type
QThread *currentThread = QThread::currentThread();
QThread *objectThread = object->thread();
if (connectionType == Qt::AutoConnection) {
connectionType = currentThread == objectThread
? Qt::DirectConnection
: Qt::QueuedConnection;
}
#ifdef QT_NO_THREAD
if (connectionType == Qt::BlockingQueuedConnection) {
connectionType = Qt::DirectConnection;
}
#endif
// invoke!
void *param[] = {
returnValue.data(),
val0.data(),
val1.data(),
val2.data(),
val3.data(),
val4.data(),
val5.data(),
val6.data(),
val7.data(),
val8.data(),
val9.data()
};
int idx_relative = QMetaMethodPrivate::get(this)->ownMethodIndex();
int idx_offset = mobj->methodOffset();
Q_ASSERT(QMetaObjectPrivate::get(mobj)->revision >= 6);
QObjectPrivate::StaticMetaCallFunction callFunction = mobj->d.static_metacall;
if (connectionType == Qt::DirectConnection) {
if (callFunction) {
callFunction(object, QMetaObject::InvokeMetaMethod, idx_relative, param);
return true;
} else {
return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, idx_relative + idx_offset, param) < 0;
}
} else if (connectionType == Qt::QueuedConnection) {
if (returnValue.data()) {
qWarning("QMetaMethod::invoke: Unable to invoke methods with return values in "
"queued connections");
return false;
}
int nargs = 1; // include return type
void **args = (void **) malloc(paramCount * sizeof(void *));
Q_CHECK_PTR(args);
int *types = (int *) malloc(paramCount * sizeof(int));
Q_CHECK_PTR(types);
types[0] = 0; // return type
args[0] = 0;
for (int i = 1; i < paramCount; ++i) {
types[i] = QMetaType::type(typeNames[i]);
if (types[i] != QMetaType::UnknownType) {
args[i] = QMetaType::create(types[i], param[i]);
++nargs;
} else if (param[i]) {
// Try to register the type and try again before reporting an error.
void *argv[] = { &types[i], &i };
QMetaObject::metacall(object, QMetaObject::RegisterMethodArgumentMetaType,
idx_relative + idx_offset, argv);
if (types[i] == -1) {
qWarning("QMetaMethod::invoke: Unable to handle unregistered datatype '%s'",
typeNames[i]);
for (int x = 1; x < i; ++x) {
if (types[x] && args[x])
QMetaType::destroy(types[x], args[x]);
}
free(types);
free(args);
return false;
}
}
}
QCoreApplication::postEvent(object, new QMetaCallEvent(idx_offset, idx_relative, callFunction,
0, -1, nargs, types, args));
} else { // blocking queued connection
#ifndef QT_NO_THREAD
if (currentThread == objectThread) {
qWarning("QMetaMethod::invoke: Dead lock detected in "
"BlockingQueuedConnection: Receiver is %s(%p)",
mobj->className(), object);
}
QSemaphore semaphore;
QCoreApplication::postEvent(object, new QMetaCallEvent(idx_offset, idx_relative, callFunction,
0, -1, 0, 0, param, &semaphore));
semaphore.acquire();
#endif // QT_NO_THREAD
}
return true;
}
先检查了返回值类型,在检查参数个数是否相同,如果在连接的时候使用了默认的Qt::AutoConnection连接,就根据信号和槽的对象的所处的线程来调整,是使用Qt::DirectConnection还是Qt::QueuedConnection。
如果是Qt::DirectConnection,直接调用,如果是Qt::AutoConnection,就使用QCoreApplication::postEvent发送到对方线程的事件循环中,对应线程收到该事件,再调用。
此外,还有Qt::BlockingQueuedConnection,会通过等待信号量QSemaphore再发送。
moc_classA.cpp
最后,我们再来看下生成的moc文件。值得注意的是,生成的moc文件应该是在预处理之后,编译之前,然后生成的文件一起编译。
class MyObject : public QObject
{
Q_OBJECT
public:
explicit MyObject(QObject *parent = nullptr);
signals:
void testSiagnal1();
void testSiagnal2(int);
public:
void testSlot1();
void testSlot2(int);
};
这个是.h文件的内容,我们看一下生成的moc_myobject.cpp都有些啥。
QT_BEGIN_MOC_NAMESPACE
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
struct qt_meta_stringdata_MyObject_t {
QByteArrayData data[6];
char stringdata0[56];
};
#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 = {
{
QT_MOC_LITERAL(0, 0, 8), // "MyObject"
QT_MOC_LITERAL(1, 9, 12), // "testSiagnal1"
QT_MOC_LITERAL(2, 22, 0), // ""
QT_MOC_LITERAL(3, 23, 12), // "testSiagnal2"
QT_MOC_LITERAL(4, 36, 9), // "testSlot1"
QT_MOC_LITERAL(5, 46, 9) // "testSlot2"
},
"MyObject\0testSiagnal1\0\0testSiagnal2\0"
"testSlot1\0testSlot2"
};
#undef QT_MOC_LITERAL
static const uint qt_meta_data_MyObject[] = {
// content:
7, // revision
0, // classname
0, 0, // classinfo
4, 14, // methods 信号与槽的个数加起来是四个
0, 0, // properties 没有自定义属性
0, 0, // enums/sets 没有枚举
0, 0, // constructors
0, // flags
2, // signalCount 信号为两个
// signals: name, argc, parameters, tag, flags
1, 0, 34, 2, 0x06 /* Public */,
3, 1, 35, 2, 0x06 /* Public */,
// slots: name, argc, parameters, tag, flags
4, 0, 38, 2, 0x0a /* Public */,
5, 1, 39, 2, 0x0a /* Public */,
// signals: parameters
QMetaType::Void,
QMetaType::Void, QMetaType::Int, 2,
// slots: parameters
QMetaType::Void,
QMetaType::Void, QMetaType::Int, 2,
0 // eod
};
void MyObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
MyObject *_t = static_cast<MyObject *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->testSiagnal1(); break;
case 1: _t->testSiagnal2((*reinterpret_cast< int(*)>(_a[1]))); break;
case 2: _t->testSlot1(); break;
case 3: _t->testSlot2((*reinterpret_cast< int(*)>(_a[1]))); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
void **func = reinterpret_cast<void **>(_a[1]);
{
typedef void (MyObject::*_t)();
if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&MyObject::testSiagnal1)) {
*result = 0;
return;
}
}
{
typedef void (MyObject::*_t)(int );
if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&MyObject::testSiagnal2)) {
*result = 1;
return;
}
}
}
}
const QMetaObject MyObject::staticMetaObject = {
{ &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 < 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;
}
// SIGNAL 0
void MyObject::testSiagnal1()
{
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
// SIGNAL 1
void MyObject::testSiagnal2(int _t1)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE
我们看之前说到的三个虚方法:
const QMetaObject *MyObject::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
返回了QMetaObject指针,这个是个静电常量指针。
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);
}
其实就是返回了实现了到void*的转换。
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;
}
利用槽函数在qt_static_metacall 函数的索引位置来调用槽函数。
void MyObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
MyObject *_t = static_cast<MyObject *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->testSiagnal1(); break;
case 1: _t->testSiagnal2((*reinterpret_cast< int(*)>(_a[1]))); break;
case 2: _t->testSlot1(); break;
case 3: _t->testSlot2((*reinterpret_cast< int(*)>(_a[1]))); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
void **func = reinterpret_cast<void **>(_a[1]);
{
typedef void (MyObject::*_t)();
if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&MyObject::testSiagnal1)) {
*result = 0;
return;
}
}
{
typedef void (MyObject::*_t)(int );
if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&MyObject::testSiagnal2)) {
*result = 1;
return;
}
}
}
}
void MyObject::testSiagnal2(int _t1)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
_a为一个指向参数的指针的数组,并将指针数组传给QMetaObject::activate函数。数组的第一个元素是返回值。active的第三个参数为信号的参数个数
其实有些流程我也还不是很清楚,以后进一步补充。