Qt--QMetaObject
来源:互联网 发布:汽车销售网站模板 cms 编辑:程序博客网 时间:2024/05/16 06:06
QMetaObject接口分析
上一节中我们概述了Meta-Object Model的架构,它通过提供了一个QObject的基类,使用Q_OBJECT宏,这样moc编译器就会自动帮我们生成带有QMetaObject记录了元信息的源码。
这节我们看下QMetaObject提供了哪些接口去获取或使用这些元信息
上源代码:
struct Q_CORE_EXPORT QMetaObject{ class Connection; const char *className() const; const QMetaObject *superClass() const; bool inherits(const QMetaObject *metaObject) const Q_DECL_NOEXCEPT; QObject *cast(QObject *obj) const; const QObject *cast(const QObject *obj) const;#ifndef QT_NO_TRANSLATION QString tr(const char *s, const char *c, int n = -1) const;#endif // QT_NO_TRANSLATION int methodOffset() const; int enumeratorOffset() const; int propertyOffset() const; int classInfoOffset() const; int constructorCount() const; int methodCount() const; int enumeratorCount() const; int propertyCount() const; int classInfoCount() const; int indexOfConstructor(const char *constructor) const; int indexOfMethod(const char *method) const; int indexOfSignal(const char *signal) const; int indexOfSlot(const char *slot) const; int indexOfEnumerator(const char *name) const; int indexOfProperty(const char *name) const; int indexOfClassInfo(const char *name) const; QMetaMethod constructor(int index) const; QMetaMethod method(int index) const; QMetaEnum enumerator(int index) const; QMetaProperty property(int index) const; QMetaClassInfo classInfo(int index) const; QMetaProperty userProperty() const; static bool checkConnectArgs(const char *signal, const char *method); static bool checkConnectArgs(const QMetaMethod &signal, const QMetaMethod &method); static QByteArray normalizedSignature(const char *method); static QByteArray normalizedType(const char *type); // internal index-based connect static Connection connect(const QObject *sender, int signal_index, const QObject *receiver, int method_index, int type = 0, int *types = Q_NULLPTR); // internal index-based disconnect static bool disconnect(const QObject *sender, int signal_index, const QObject *receiver, int method_index); static bool disconnectOne(const QObject *sender, int signal_index, const QObject *receiver, int method_index); // internal slot-name based connect static void connectSlotsByName(QObject *o); // internal index-based signal activation static void activate(QObject *sender, int signal_index, void **argv); static void activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv); static void activate(QObject *sender, int signal_offset, int local_signal_index, void **argv); static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument(Q_NULLPTR), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()); static inline bool invokeMethod(QObject *obj, const char *member, QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument(Q_NULLPTR), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()) { return invokeMethod(obj, member, Qt::AutoConnection, ret, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9); } static inline bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericArgument val0 = QGenericArgument(Q_NULLPTR), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()) { return invokeMethod(obj, member, type, QGenericReturnArgument(), val0, val1, val2, val3, val4, val5, val6, val7, val8, val9); } static inline bool invokeMethod(QObject *obj, const char *member, QGenericArgument val0 = QGenericArgument(Q_NULLPTR), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()) { return invokeMethod(obj, member, Qt::AutoConnection, QGenericReturnArgument(), val0, val1, val2, val3, val4, val5, val6, val7, val8, val9); } QObject *newInstance(QGenericArgument val0 = QGenericArgument(Q_NULLPTR), QGenericArgument val1 = QGenericArgument(), QGenericArgument val2 = QGenericArgument(), QGenericArgument val3 = QGenericArgument(), QGenericArgument val4 = QGenericArgument(), QGenericArgument val5 = QGenericArgument(), QGenericArgument val6 = QGenericArgument(), QGenericArgument val7 = QGenericArgument(), QGenericArgument val8 = QGenericArgument(), QGenericArgument val9 = QGenericArgument()) const; enum Call { InvokeMetaMethod, ReadProperty, WriteProperty, ResetProperty, QueryPropertyDesignable, QueryPropertyScriptable, QueryPropertyStored, QueryPropertyEditable, QueryPropertyUser, CreateInstance, IndexOfMethod, RegisterPropertyMetaType, RegisterMethodArgumentMetaType }; int static_metacall(Call, int, void **) const; static int metacall(QObject *, Call, int, void **); 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;};
从接口命名就可以简单分析出可以获取类名、类信息、方法(细分为信号、槽、构造函数、一般函数)、属性、枚举等信息
枚举类型Call中实际给出了可以进行的操作,包括调用方法、读写属性、查询属性各种特性、创建实例、注册属性类型、注册方法参数类型
enum Call { InvokeMetaMethod, ReadProperty, WriteProperty, ResetProperty, QueryPropertyDesignable, QueryPropertyScriptable, QueryPropertyStored, QueryPropertyEditable, QueryPropertyUser, CreateInstance, IndexOfMethod, RegisterPropertyMetaType, RegisterMethodArgumentMetaType};
还给出了activate(发射信号实际调用的函数),connect(连接信号与槽),disconnect(断连信号与槽)的接口
QMetaObject实现分析
获取超类QMetaObject 指针
inline const QMetaObject *QMetaObject::superClass() const{ return d.superdata; }
获取类名
先看一个转换函数
static inline const QMetaObjectPrivate *priv(const uint* data){ return reinterpret_cast<const QMetaObjectPrivate*>(data); }
这个函数将uint*指针转化为QMetaObjectPrivate ,也就是说QMetaObject中的d.data实际就是一个QMetaObjectPrivate 结构体指针
那么让我们看看QMetaObjectPrivate 这个结构体
struct QMetaObjectPrivate{ enum { OutputRevision = 7 }; // Used by moc, qmetaobjectbuilder and qdbus int revision; int className; int classInfoCount, classInfoData; int methodCount, methodData; int propertyCount, propertyData; int enumeratorCount, enumeratorData; int constructorCount, constructorData; //since revision 2 int flags; //since revision 3 int signalCount; //since revision 4 //...}
原来这个结构体就是对应我们上节的qt_meta_data_MyClass整型数组,里面记录了类、方法、属性等名称字符串的索引、参数的类型信息等
那么再来看获取类名的步骤
const char *QMetaObject::className() const{ return objectClassName(this);}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();}static inline const QByteArray stringData(const QMetaObject *mo, int index){ const QByteArrayDataPtr data = { const_cast<QByteArrayData*>(&mo->d.stringdata[index]) }; return data;}
实际就是通过d.data中记录的索引找到d.stringdata中的字符串,即mo->d.stringdata[index]
判断继承关系
bool QMetaObject::inherits(const QMetaObject *metaObject){ const QMetaObject *m = this; do { if (metaObject == m) return true; } while ((m = m->d.superdata)); return false;}
就是循环比较超类的QMetaObject是否等于这个metaObject
方法偏移
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::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::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;}
说明下decodeMethodSignature用来解析一个函数字符串如func(int a, QMap<int, QString> b)
,返回值是函数名func,types中记录参数类型
然后通过indexOfMethodRelative去匹配是否有函数名和参数个数,参数类型都一样的,有就返回索引,最后和方法偏移相加,就是该方法的最终索引
根据索引获取方法
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等接口。
其它QMetaObejct中classinfo、property、enum的接口与上面method的接口实现类似。
继续探索QMetaOjbect中invokeMethod接口的实现
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);}
嗯,就是根据函数名、函数返回类型、参数类型,构建函数签名sig,然后通过indexOfMethod获取索引号,再通过method得到QMetaMethod ,最后实际调用的是QMetaMethod 的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;}
代码首先检查返回值的类型是否正确;
再检查参数的个数是否匹配;
再依据当前线程和被调对象所属 线程来调整connnection type;
如果是Qt::DirectConnection,直接调用
QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param)
如果是Qt::QueuedConnection,即异步调用
QCoreApplication::postEvent(object, new QMetaCallEvent(idx_offset, idx_relative, callFunction,0, -1, nargs, types, args));
当然还有一种Qt::BlockingQueuedConnection阻塞式异步调用,通过QSemaphore信号量来等待调用结束后再继续执行
QSemaphore semaphore;QCoreApplication::postEvent(object, new QMetaCallEvent(idx_offset, idx_relative, callFunction, 0, -1, 0, 0, param, &semaphore));semaphore.acquire();
关于异步调用,是在对象所属线程中的事件循环中,从事件队列中取得这个事件时,再去调用这个函数
int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv){ if (object->d_ptr->metaObject) return object->d_ptr->metaObject->metaCall(object, cl, idx, argv); else return object->qt_metacall(cl, idx, argv);}
如果是QObject类,那就直接调用metaCall,否则就是QObject的派生类,调用qt_metacall,就是moc文件自动生成的代码了。
moc_myclass.cpp参考上一章,下面回顾下qt_metacall的代码加深理解
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;}
可以看到如果是QMetaObject::InvokeMetaMethod,那么将调用
qt_static_metacall(this, _c, _id, _a);
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}
这段代码将调用最终转到我们自己的实现的函数(这里是signal1、slot1、method1)中来。这个函数不仅提供了method的动态调用,而且也提供了property的动态操作方法。可想而知,property的动态调用的实现方式一定和invocalbe method是类似的。
总结
通过上一节moc文件分析,和这一节的QMetaObject源码分析,我们应该是对Qt Meta-Object Model有了一个清楚的认识
- Qt提供了一个QObject的基类
- Qt扩展了C++语法,提供了Q_OBJECT、Q_INVOKABLE、signals、slots、emit、SIGNAL,SLOT、Q_PROPERT、Q_ENUM、Q_FLAG、Q_CLASSINFO等宏,moc会识别这些宏并生成对应的moc源代码
- Q_OBJECT宏中声明了QMetaObject的静态对象,QMetaObject中记录了classiofo、method、property、enum,并提供了metacall来操作
*关于信号与槽机制关键几个接口activate和connect、disconect的实现,见下一节
- Qt--QMetaObject
- Qt 静态函数QMetaObject::connectSlotsByName(QObject * object)
- Qt -- 编译Audiorecord 报 QMetaObject 错误
- Qt 源码之元对象(QMetaObject)
- Inside QT Series (九):QMetaObject class data members
- Inside Qt Series (九):QMetaObject class data members
- Qt函数connectSlotsByName(),即"QMetaObject::connectSlotsByName(QObject *o)"的说明
- VS中Qt 开发《无法解析的外部符号 QMetaObject 》
- Inside Qt Series (九):QMetaObject class data members
- Inside Qt Series (九):QMetaObject class data members
- Qt 编译错误: QMetaObject::connectSlotsByName: No matching signal for xxx
- QMetaObject::invokeMethod()---------Qt 类外调用一个 private slots 函数
- 【qt错误】unresolved external symbol "public: virtual struct QMetaObject
- 解读QT信号与槽机制里 QMetaObject::connectSlotsByName(QObject *o)的源码
- 解读QT信号与槽机制里 QMetaObject::connectSlotsByName(QObject *o)的源码
- 解读QT信号与槽机制里 QMetaObject::connectSlotsByName(QObject *o)的源码
- 解读QT信号与槽机制里 QMetaObject::connectSlotsByName(QObject *o)的源码
- 解读QT信号与槽机制里 QMetaObject::connectSlotsByName(QObject *o)的源码
- 为销售范围维护允许的销售凭证
- 通配符的匹配很全面, 但无法找到元素 'context:property-placeholder' 的声明
- 阿里云RDS SQL Server 2008 R2 使用本地SQL备份文件还原全过程
- golang中container/list包中的坑
- ARGB与RGB区别及透明度和RGB颜色对照表
- Qt--QMetaObject
- MySQL的下载及安装
- 关于多态的一点学习心得
- VS 版本号问题
- 【干货#003】自定义小程序客服按钮
- VS查找 问题解决
- 开通博客
- Retrofit 和 Rxjava 的结合使用
- cuckoo沙箱的搭建