研一的时候开始使用Qt,感觉用Qt开发图形界面比MFC的一套框架来方便的多。后来由于项目的需要,也没有再接触Qt了。现在要重新拾起来,于是要从基础学起。
Now,开始学习Qt事件处理机制。
元对象系统的构成
- QObject为所有需要利用元对象系统的对象提供一个基类。
- Q_OBJECT宏,在类的声明体内激活meta-object功能,比如动态属性、信号和槽。
- Meta Object Compiler(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信息。
virtual QObject::metaObject();
该方法返回一个QObject对应的metaObject对象,如上文所说,如果一个类的声明中包含了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)基本信息
struct Q_CORE_EXPORT QMetaObject { const char *className() const; const QMetaObject *superClass() const;
struct { // private data
const QMetaObject *superdata; //父类QMetaObject实例的指针 const char *stringdata; //一段字符串内存块,包含MetaObject信息之字符串信息 const uint *data; //一段二级制内存块,包含MetaObject信息之二进制信息 const void *extradata; //额外字段,暂未使用 } d;
...
};
2)classinfo:提供额外的类信息-名值对。用户可以在类的生命中以Q_CLASSINFO(name,value)的方式添加。
int classInfoOffset() const; int classInfoCount() const; int indexOfClassInfo(const char *name) const; QMetaClassInfo classInfo(int index) const;
example:
class MyClass : public QObject { Q_OBJECT Q_CLASSINFO("author", "Sabrina Schweinsteiger") Q_CLASSINFO("url", "http://doc.moosesoft.co.uk/1.0/") public: ... };
3)constructor:提供该类的构造方法信息。
int constructorCount() const; int indexOfConstructor(const char *constructor) const; QMetaMethod constructor(int index) const;
4)enum:描述该类声明体重所包含的枚举类型信息。
int enumeratorOffset() const; int enumeratorCount() const; int indexOfEnumerator(const char *name) const; QMetaEnum enumerator(int index) const;
5)method:描述类中所包含方法信息:包括property,signal,slot等。
int methodOffset() const; int methodCount() const; int indexOfMethod(const char *method) const; int indexOfSignal(const char *signal) const; int indexOfSlot(const char *slot) const; QMetaMethod method(int index) const;
6)property:类型的属性信息。
int propertyOffset() const; int propertyCount() const; int indexOfProperty(const char *name) const; QMetaProperty property(int index) const;
注意:对于类里面定义的函数,构造函数,枚举,只有加上一些宏才表示你希望为方法提供meta信息。比如 Q_ENUMS用来注册宏,Q_INVACABLE用来注册方法(包括构造函数)。Qt这么设计的原因应该是避免meta信息的臃肿。
举例说明MOS(Meta Object System)
TestObject继承QObject,定义了两个Property:PropertyA和PropertyB;两个classinfo:Author,version;一个枚举:TestEnum。
#include <QObject> class TestObject : public QObject { Q_OBJECT Q_PROPERTY(QString propertyA READ getPropertyA WRITE getPropertyA RESET resetPropertyA DESIGNABLE true SCRIPTABLE true STORED true USER false) Q_PROPERTY(QString propertyB READ getPropertyB WRITE getPropertyB RESET resetPropertyB) Q_CLASSINFO("Author", "Long Huihu") Q_CLASSINFO("Version", "TestObjectV1.0") Q_ENUMS(TestEnum) public: enum TestEnum { EnumValueA, EnumValueB }; public: TestObject(); signals: void clicked(); void pressed(); public slots: void onEventA(const QString &); void onEventB(int ); }
TestObject的moc文件:
#include "TestObject.h" #if !defined(Q_MOC_OUTPUT_REVISION) #error "The header file 'TestObject.h' doesn't include <QObject>." #elif Q_MOC_OUTPUT_REVISION != 62 #error "This file was generated using the moc from 4.6.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 static const uint qt_meta_data_TestObject[] = { // content: 4, // revision 0, // classname 2, 14, // classinfo 4, 18, // methods 2, 38, // properties 1, 44, // enums/sets 0, 0, // constructors 0, // flags 2, // signalCount // classinfo: key, value 22, 11, 44, 29, // signals: signature, parameters, type, tag, flags 53, 52, 52, 52, 0x05, 63, 52, 52, 52, 0x05, // slots: signature, parameters, type, tag, flags 73, 52, 52, 52, 0x0a, 91, 52, 52, 52, 0x0a, // properties: name, type, flags 113, 105, 0x0a095007, 123, 105, 0x0a095007, // enums: name, flags, count, data 133, 0x0, 2, 48, // enum data: key, value 142, uint(TestObject::EnumValueA), 153, uint(TestObject::EnumValueB), 0 // eod }; static const char qt_meta_stringdata_TestObject[] = { "TestObject\0Long Huihu\0Author\0" "TestObjectV1.0\0Version\0\0clicked()\0" "pressed()\0onEventA(QString)\0onEventB(int)\0" "QString\0propertyA\0propertyB\0TestEnum\0" "EnumValueA\0EnumValueB\0" }; const QMetaObject TestObject::staticMetaObject = { { &QObject::staticMetaObject, qt_meta_stringdata_TestObject, qt_meta_data_TestObject, 0 } }; #ifdef Q_NO_DATA_RELOCATION const QMetaObject &TestObject::getStaticMetaObject() { return staticMetaObject; } #endif //Q_NO_DATA_RELOCATION const QMetaObject *TestObject::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; } void *TestObject::qt_metacast(const char *_clname) { if (!_clname) return 0; if (!strcmp(_clname, qt_meta_stringdata_TestObject)) return static_cast<void*>(const_cast< TestObject*>(this)); return QObject::qt_metacast(_clname); } int TestObject::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) { switch (_id) { case 0: clicked(); break; case 1: pressed(); break; case 2: onEventA((*reinterpret_cast< const QString(*)>(_a[1]))); break; case 3: onEventB((*reinterpret_cast< int(*)>(_a[1]))); break; default: ; } _id -= 4; } #ifndef QT_NO_PROPERTIES else if (_c == QMetaObject::ReadProperty) { void *_v = _a[0]; switch (_id) { case 0: *reinterpret_cast< QString*>(_v) = getPropertyA(); break; case 1: *reinterpret_cast< QString*>(_v) = getPropertyB(); break; } _id -= 2; } else if (_c == QMetaObject::WriteProperty) { void *_v = _a[0]; switch (_id) { case 0: getPropertyA(*reinterpret_cast< QString*>(_v)); break; case 1: getPropertyB(*reinterpret_cast< QString*>(_v)); break; } _id -= 2; } else if (_c == QMetaObject::ResetProperty) { switch (_id) { case 0: resetPropertyA(); break; case 1: resetPropertyB(); break; } _id -= 2; } else if (_c == QMetaObject::QueryPropertyDesignable) { _id -= 2; } else if (_c == QMetaObject::QueryPropertyScriptable) { _id -= 2; } else if (_c == QMetaObject::QueryPropertyStored) { _id -= 2; } else if (_c == QMetaObject::QueryPropertyEditable) { _id -= 2; } else if (_c == QMetaObject::QueryPropertyUser) { _id -= 2; } #endif // QT_NO_PROPERTIES return _id; } // SIGNAL 0 void TestObject::clicked() { QMetaObject::activate(this, &staticMetaObject, 0, 0); } // SIGNAL 1 void TestObject::pressed() { QMetaObject::activate(this, &staticMetaObject, 1, 0); } QT_END_MOC_NAMESPACE
- qt_meta_data_TestObject:定义的正是QMetaObject::d.data指向的信息块;
- qt_meta_stringdata_TestObject:定义的是QMetaObject::d.dataString指向的信息块;
- const QMetaObject TestObject::staticMetaObject:定义TestObject类的MetaObject实例,从中可以看出QMetaObject各个字段是如何被赋值的;
- const QMetaObject *TestObject::metaObject() const:重写了QObject::metaObject函数,返回上述的MetaObject实例指针。
- TestObject::qt_metacall()是重写QObject的方法,依据传入的参数来调用signal&slot或访问property,动态方法调用属性访问正是依赖于这个方法。
- TestObject::clicked()和TestObject::pressed()正是对两个signal的实现,可见,signal其实就是一种方法,只不过这种方法由qt meta system来实现,不用我们自己实现。
TestObject类的所有meta信息就存储在 qt_meta_data_TestObject和qt_meta_stringdata_TestObject这两个静态数据中。 QMetaObject的接口的实现正是基于这两块数据。下面就对这两个数据进行分块说明。
static const uint qt_meta_data_TestObject[] = { 数据块一: // content: 4, // revision 0, // classname 2, 14, // classinfo 4, 18, // methods 2, 38, // properties 1, 44, // enums/sets 0, 0, // constructors 0, // flags 2, // signalCount 这块数据可以被看做meta信息的头部,正好和QMetaObjectPrivate数据结构相对应,在QMetaObject的实现中,正是将这块数据映射为QMetaObjectPrivate进行使用的。 第一行数据“4”:版本号; 第二行数据“0”:类型名,该值是qt_meta_stringdata_TestObject的索引,qt_meta_stringdata_TestObject[0]这个字符串不正是类型名“TestObject”吗。 第三行数据“2,14”,第一个表明有2个classinfo被定义,第二个是说具体的 classinfo信息在qt_meta_data_TestObject中的索引,qt_meta_data_TestObject[14]的位置两个 classinfo名值对的定义; 第四行数据“4,18”,指明method的信息,模式同上; 第五行数据“2,38”,指明property的信息,模式同上; 第六行数据“1,14”,指明enum的信息,模式同上。 数据块二: // classinfo: key, value 22, 11, 44, 29, classinfo信息块。第一行“22,11”,22表明 qt_meta_stringdata_TestObject[22]处定义的字符串是classinfo的key,11表明 qt_meta_stringdata_TestObject[11]处的字符串就是value。第二行“44,29”定义第二个classinfo。 数据块三: // signals: signature, parameters, type, tag, flags 53, 52, 52, 52, 0x05, 63, 52, 52, 52, 0x05, signal信息块。第一行“53, 52, 52, 52, 0x05”定义第一个signal clicked()。qt_meta_stringdata_TestObject[53]是signal名称字符串。parameters 52, type 52, tag 52, flags如何解释暂未知。 数据块四: // slots: signature, parameters, type, tag, flags 73, 52, 52, 52, 0x0a, 91, 52, 52, 52, 0x0a, slots信息,模式类似signal。 数据块五: // properties: name, type, flags 113, 105, 0x0a095007, 123, 105, 0x0a095007, property性信息,模式类signal和slots,105如何和type对应暂未知。 数据块六: // enums: name, flags, count, data 133, 0x0, 2, 48, // enum data: key, value 142, uint(TestObject::EnumValueA), 153, uint(TestObject::EnumValueB), enum信息,第一行定义的是枚举名,flag,值的数目,data48不知是什么。 几行定义的是各枚举项的名称和值。名称同上都是qt_meta_stringdata_TestObject的索引值。 0 // eod }; static const char qt_meta_stringdata_TestObject[] = { 这块数据就是meta信息所需的字符串。是一个字符串的序列。 "TestObject\0Long Huihu\0Author\0" "TestObjectV1.0\0Version\0\0clicked()\0" "pressed()\0onEventA(QString)\0onEventB(int)\0" "QString\0propertyA\0propertyB\0TestEnum\0" "EnumValueA\0EnumValueB\0" };
QMetaObjectPrivate的数据定义
QMetaObjectPrivate是QMetaObject的私有实现类,其数据定义部分如下(见头文件qmetaobject_p.h)。该数据结构全是int类型,一些是直接的int型信息,比如classInfoCount、
methodCount等,还有一些是用于在QMetaObject的stringdata和data内存块中定位信息的索引值。
struct QMetaObjectPrivate { 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 // revision 5 introduces changes in normalized signatures, no new members // revision 6 added qt_static_metacall as a member of each Q_OBJECT and inside QMetaObject itself };
QMetaObject::className()
inline const char *QMetaObject::className() const { return d.stringdata; }
d.stringdata就是那块字符串数据,包含若干c字符串(以'\0')结尾。如果把d.stringdata当做一个c字符串指针的话,就是这个字符串序列的第一个字符串,正是类名。
QMetaObject::superClass()
inline const QMetaObject *QMetaObject::superClass() const { return d.superdata; }
QMetaObject::classInfoCount()
int QMetaObject::classInfoCount() const { int n = priv(d.data)->classInfoCount; const QMetaObject *m = d.superdata; while (m) { n += priv(m->d.data)->classInfoCount; m = m->d.superdata; } return n; }
从代码可以看出,返回该类的所有classinfo数目,包括所有基类的。
函数priv是一个简单inline函数:
static inline const QMetaObjectPrivate *priv(const uint* data) { return reinterpret_cast<const QMetaObjectPrivate*>(data); }
d.data指向的是那块二进制信息,priv将d.data解释为QMetaObjectPrivate。
QMetaObject::classInfoOffet()
int QMetaObject::classInfoOffset() const { int offset = 0; const QMetaObject *m = d.superdata; while (m) { offset += priv(m->d.data)->classInfoCount; m = m->d.superdata; } return offset; }
该类的含义是返回这个类所定义的classinfo的起始索引值,相当于它的祖先类所定义的classinfo的数量.
QMetaObject::classInfo(int index)
QMetaClassInfo QMetaObject::classInfo(int index) const { int i = index; i -= classInfoOffset(); if (i < 0 && d.superdata) return d.superdata->classInfo(index); QMetaClassInfo result; if (i >= 0 && i < priv(d.data)->classInfoCount) { result.mobj = this; result.handle = priv(d.data)->classInfoData + 2*i; } return result; } class Q_CORE_EXPORT QMetaClassInfo { public: inline QMetaClassInfo() : mobj(0),handle(0) {} const char *name() const; const char *value() const; inline const QMetaObject *enclosingMetaObject() const { return mobj; } private: const QMetaObject *mobj; uint handle; friend struct QMetaObject; };
这个代码的流程比较简单。priv(d.data)->classInfoData是classinfo的信息在d.data中的偏移;每条classinfo信息占2个UINT的大小,因此“priv(d.data)->classInfoData + 2*i”这个表达式的值就是第i个classinfo的信息在d.data中的偏移。
QMetaObject::indexOfClassInfo()
int QMetaObject::indexOfClassInfo(const char *name) const { int i = -1; const QMetaObject *m = this; while (m && i < 0) { for (i = priv(m->d.data)->classInfoCount-1; i >= 0; --i) if (strcmp(name, m->d.stringdata + m->d.data[priv(m->d.data)->classInfoData + 2*i]) == 0) { i += m->classInfoOffset(); break; } m = m->d.superdata; } return i; }
按照继承层次,从下往上寻找名字为name的classinfo。
参考前一函数的解释,表达式m->d.data[priv(m->d.data)->classInfoData + 2*i]的值是第i个classinfo信息的第一个32位值。该值是字符信息块d.stringdata中的索引值。因此 m->d.stringdata+ m->d.data[priv(m->d.data)->classInfoData + 2*i]就是classinfo名称的字符串。
int constructorCount() const
1 int QMetaObject::constructorCount() const 2 { 3 if (priv(d.data)->revision < 2) 4 return 0; 5 return priv(d.data)->constructorCount; 6 }
QMetaMethod constructor ( int index ) const
1 QMetaMethod QMetaObject::constructor(int index) const 2 { 3 int i = index; 4 QMetaMethod result; 5 if (priv(d.data)->revision >= 2 && i >= 0 && i < priv(d.data)->constructorCount) { 6 result.mobj = this; 7 result.handle = priv(d.data)->constructorData + 5*i; 8 } 9 return result; 10 }
int indexOfConstructor ( const char * constructor ) const
1 int QMetaObject::indexOfConstructor(const char *constructor) const 2 { 3 if (priv(d.data)->revision < 2) 4 return -1; 5 for (int i = priv(d.data)->constructorCount-1; i >= 0; --i) { 6 const char *data = d.stringdata + d.data[priv(d.data)->constructorData + 5*i]; 7 if (data[0] == constructor[0] && strcmp(constructor + 1, data + 1) == 0) { 8 return i; 9 } 10 } 11 return -1; 12 }
1 int enumeratorCount () const 2 3 int enumeratorOffset () const 4 5 QMetaEnum enumerator ( int index ) const 6 7 int indexOfEnumerator ( const char * name ) const
查找Signal
一般函数的查找方式
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; }
int methodCount () const 略; int methodOffset () const 略; int indexOfMethod(const char *method)const;
signal的查找函数
int QMetaObject::indexOfSignal(const char *signal) const { const QMetaObject *m = this; int i = QMetaObjectPrivate::indexOfSignalRelative(&m, signal, false); if (i < 0) { m = this; i = QMetaObjectPrivate::indexOfSignalRelative(&m, signal, true); } if (i >= 0) i += m->methodOffset(); return i; } int QMetaObjectPrivate::indexOfSignalRelative(const QMetaObject **baseObject, const char *signal, bool normalizeStringData) { int i = indexOfMethodRelative<MethodSignal>(baseObject, signal, normalizeStringData); #ifndef QT_NO_DEBUG const QMetaObject *m = *baseObject; if (i >= 0 && m && m->d.superdata) { int conflict = m->d.superdata->indexOfMethod(signal); if (conflict >= 0) qWarning("QMetaObject::indexOfSignal: signal %s from %s redefined in %s", signal, m->d.superdata->d.stringdata, m->d.stringdata); } #endif return i; }
template<int MethodType> static inline int indexOfMethodRelative(const QMetaObject **baseObject, const char *method, bool normalizeStringData) { for (const QMetaObject *m = *baseObject; m; m = m->d.superdata) { int i = (MethodType == MethodSignal && priv(m->d.data)->revision >= 4) ? (priv(m->d.data)->signalCount - 1) : (priv(m->d.data)->methodCount - 1); const int end = (MethodType == MethodSlot && priv(m->d.data)->revision >= 4) ? (priv(m->d.data)->signalCount) : 0; if (!normalizeStringData) { for (; i >= end; --i) { const char *stringdata = m->d.stringdata + m->d.data[priv(m->d.data)->methodData + 5*i]; if (method[0] == stringdata[0] && strcmp(method + 1, stringdata + 1) == 0) { *baseObject = m; return i; } } } else if (priv(m->d.data)->revision < 5) { for (; i >= end; --i) { const char *stringdata = (m->d.stringdata + m->d.data[priv(m->d.data)->methodData + 5 * i]); const QByteArray normalizedSignature = QMetaObject::normalizedSignature(stringdata); if (normalizedSignature == method) { *baseObject = m; return i; } } } } return -1; }
可以看出,查找signal的特别之处在于,通过method元数据的第五项来判断这是不是一个signal。
1 int indexOfSlot ( const char * slot ) const 略; 2 int propertyCount () const 略; 3 int propertyOffset () const 略; 4 int indexOfProperty ( const char * name ) const 略;
QMetaProperty QMetaObject::property(int index) const { int i = index; i -= propertyOffset(); if (i < 0 && d.superdata) return d.superdata->property(index); QMetaProperty result; if (i >= 0 && i < priv(d.data)->propertyCount) { int handle = priv(d.data)->propertyData + 3*i; int flags = d.data[handle + 2]; const char *type = d.stringdata + d.data[handle + 1]; result.mobj = this; result.handle = handle; result.idx = i; if (flags & EnumOrFlag) { result.menum = enumerator(indexOfEnumerator(type)); if (!result.menum.isValid()) { QByteArray enum_name = type; QByteArray scope_name = d.stringdata; int s = enum_name.lastIndexOf("::"); if (s > 0) { scope_name = enum_name.left(s); enum_name = enum_name.mid(s + 2); } const QMetaObject *scope = 0; if (scope_name == "Qt") scope = &QObject::staticQtMetaObject; else scope = QMetaObject_findMetaObject(this, scope_name); if (scope) result.menum = scope->enumerator(scope->indexOfEnumerator(enum_name)); } } } return result; }
该函数的特别之处在于,如果这个propery是一个枚举类型的话,就为返回值QMetaPropery赋上正确QMetaEnum属性值。
QMetaProperty QMetaObject::userProperty() const { const int propCount = propertyCount(); for (int i = propCount - 1; i >= 0; --i) { const QMetaProperty prop = property(i); if (prop.isUser()) return prop; } return QMetaProperty(); }
从这个函数的实现来看,一个QObject应该只会有一个打开USER flag的property。
meta call就是通过object的meta system的支持来动态调用object的方法,metacall也是signal&slot的机制的基石。
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0 = QGenericArgument(0), 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(0), 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(0), 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(0), 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); }
QMetaObject这个静态方法可以动态地调用obj对象名字为member的方法,type参数表明该调用时同步的还是异步的。ret是一个 通用的用来存储返回值的类型,后面的9个参数是用来传递调用参数的,QGenericArgument()是一种通用的存储参数值的类型。
所调用的方法必须是invocable的,也就是signal,slot或者是加了声明为Q_INVOCABLE的其他方法。
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'); int idx = obj->metaObject()->indexOfMethod(sig.constData()); if (idx < 0) { QByteArray norm = QMetaObject::normalizedSignature(sig.constData()); idx = obj->metaObject()->indexOfMethod(norm.constData()); } if (idx < 0 || idx >= obj->metaObject()->methodCount()) { qWarning("QMetaObject::invokeMethod: No such method %s::%s", obj->metaObject()->className(), sig.constData()); return false; } QMetaMethod method = obj->metaObject()->method(idx); return method.invoke(obj, type, ret, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9); }
先依据传递的方法名称和参数,构造完整的函数签名(存储在局部变量sig)。参数的类型名就是调用时传递时的参数静态类型,这里可不会有什么类型转换,这是运行时的行为,参数类型转换是编译时的行为。
然后通过这个sig签名在obj中去查找该方法,查询的结果就是一个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 // the trick here is to make a function signature out of the return type // so that we can call normalizedSignature() and avoid duplicating code QByteArray unnormalized; int len = qstrlen(returnValue.name()); unnormalized.reserve(len + 3); unnormalized = "_("; // the function is called "_" unnormalized.append(returnValue.name()); unnormalized.append(')'); QByteArray normalized = QMetaObject::normalizedSignature(unnormalized.constData()); normalized.truncate(normalized.length() - 1); // drop the ending ')' if (qstrcmp(normalized.constData() + 2, retType) != 0) 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; } int metaMethodArgumentCount = 0; { // based on QMetaObject::parameterNames() const char *names = mobj->d.stringdata + mobj->d.data[handle + 1]; if (*names == 0) { // do we have one or zero arguments? const char *signature = mobj->d.stringdata + mobj->d.data[handle]; while (*signature && *signature != '(') ++signature; if (*++signature != ')') ++metaMethodArgumentCount; } else { --names; do { ++names; while (*names && *names != ',') ++names; ++metaMethodArgumentCount; } while (*names); } } if (paramCount <= metaMethodArgumentCount) 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() }; // recompute the methodIndex by reversing the arithmetic in QMetaObject::property() int idx_relative = ((handle - priv(mobj->d.data)->methodData) / 5); int idx_offset = mobj->methodOffset(); QObjectPrivate::StaticMetaCallFunction callFunction = (QMetaObjectPrivate::get(mobj)->revision >= 6 && mobj->d.extradata) ? reinterpret_cast<const QMetaObjectExtraData *>(mobj->d.extradata)->static_metacall : 0; 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 **) qMalloc(paramCount * sizeof(void *)); Q_CHECK_PTR(args); int *types = (int *) qMalloc(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]) { args[i] = QMetaType::construct(types[i], param[i]); ++nargs; } else if (param[i]) { 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]); } qFree(types); qFree(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; }
代码首先检查返回值的类型是否正确;再检查参数的个数是否匹配,看懂这段代码需要参考该系列之二对moc文件的解析;再依据当前线程和被调对象所属 线程来调整connnection type;如果是directconnection,直接调用 QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param),param是将所有参数值指针排列组成的指针数组。如果不是directconnection,也即异步调用,就通过一个post一个 QMetaCallEvent到obj,此时须将所有的参数复制一份存入event对象。
QMetaObject::metacall的实现如下:
int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv) { if (QMetaObject *mo = object->d_ptr->metaObject) return static_cast<QAbstractDynamicMetaObject*>(mo)->metaCall(cl, idx, argv); else return object->qt_metacall(cl, idx, argv); }
如果object->d_ptr->metaObject(QMetaObjectPrivate)存在,通过该metaobject 来调用,这里要参考该系列之三对QMetaObjectPrivate的介绍,这个条件实际上就是object就是QObject类型,而不是派生类型。 否则调用object::qt_metacall。
对于异步调用,QObject的event函数里有如下代码:
class Q_CORE_EXPORT QMetaCallEvent : public QEvent { public: QMetaCallEvent(ushort method_offset, ushort method_relative, QObjectPrivate::StaticMetaCallFunction callFunction , const QObject *sender, int signalId, int nargs = 0, int *types = 0, void **args = 0, QSemaphore *semaphore = 0); ~QMetaCallEvent(); inline int id() const { return method_offset_ + method_relative_; } inline const QObject *sender() const { return sender_; } inline int signalId() const { return signalId_; } inline void **args() const { return args_; } virtual void placeMetaCall(QObject *object); private: const QObject *sender_; int signalId_; int nargs_; int *types_; void **args_; QSemaphore *semaphore_; QObjectPrivate::StaticMetaCallFunction callFunction_; ushort method_offset_; ushort method_relative_; };
class Q_CORE_EXPORT QObjectPrivate : public QObjectData { Q_DECLARE_PUBLIC(QObject) public: struct ExtraData { ExtraData() {} #ifndef QT_NO_USERDATA QVector<QObjectUserData *> userData; #endif QList<QByteArray> propertyNames; QList<QVariant> propertyValues; }; typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **); struct Connection { QObject *sender; QObject *receiver; StaticMetaCallFunction callFunction; // The next pointer for the singly-linked ConnectionList Connection *nextConnectionList; //senders linked list Connection *next; Connection **prev; QBasicAtomicPointer<int> argumentTypes; ushort method_offset; ushort method_relative; ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking ~Connection(); int method() const { return method_offset + method_relative; } }; // ConnectionList is a singly-linked list struct ConnectionList { ConnectionList() : first(0), last(0) {} Connection *first; Connection *last; }; struct Sender { QObject *sender; int signal; int ref; };
case QEvent::MetaCall: { #ifdef QT_JAMBI_BUILD d_func()->inEventHandler = false; #endif QMetaCallEvent *mce = static_cast<QMetaCallEvent*>(e); QObjectPrivate::Sender currentSender; currentSender.sender = const_cast<QObject*>(mce->sender()); currentSender.signal = mce->signalId(); currentSender.ref = 1; QObjectPrivate::Sender * const previousSender = QObjectPrivate::setCurrentSender(this, ¤tSender); #if defined(QT_NO_EXCEPTIONS) mce->placeMetaCall(this); #else QT_TRY { mce->placeMetaCall(this); } QT_CATCH(...) { QObjectPrivate::resetCurrentSender(this, ¤tSender, previousSender); QT_RETHROW; } #endif QObjectPrivate::resetCurrentSender(this, ¤tSender, previousSender); break; }
QMetaCallEvent的代码很简单:
void QMetaCallEvent::placeMetaCall(QObject *object) { if (callFunction_) { callFunction_(object, QMetaObject::InvokeMetaMethod, method_relative_, args_); } else { QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, method_offset_ + method_relative_, args_); } }
最后来看一下object->qt_metacall是如何实现的,这又回到了上面所提供的示例moc文件中去了。该文件提供了该方法的实现:
int TestObject::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) { # switch (_id) { # case 0: clicked(); break; # case 1: pressed(); break; # case 2: onEventA((*reinterpret_cast< const QString(*)>(_a[1]))); break; # case 3: onEventB((*reinterpret_cast< int(*)>(_a[1]))); break; # default: ; # } # _id -= 4; # } # #ifndef QT_NO_PROPERTIES # else if (_c == QMetaObject::ReadProperty) { # void *_v = _a[0]; # switch (_id) { # case 0: *reinterpret_cast< QString*>(_v) = getPropertyA(); break; # case 1: *reinterpret_cast< QString*>(_v) = getPropertyB(); break; # } # _id -= 2; # } else if (_c == QMetaObject::WriteProperty) { # void *_v = _a[0]; # switch (_id) { # case 0: getPropertyA(*reinterpret_cast< QString*>(_v)); break; # case 1: getPropertyB(*reinterpret_cast< QString*>(_v)); break; # } # _id -= 2; # } else if (_c == QMetaObject::ResetProperty) { # switch (_id) { # case 0: resetPropertyA(); break; # case 1: resetPropertyB(); break; # } # _id -= 2; # } else if (_c == QMetaObject::QueryPropertyDesignable) { # _id -= 2; # } else if (_c == QMetaObject::QueryPropertyScriptable) { # _id -= 2; # } else if (_c == QMetaObject::QueryPropertyStored) { # _id -= 2; # } else if (_c == QMetaObject::QueryPropertyEditable) { # _id -= 2; # } else if (_c == QMetaObject::QueryPropertyUser) { # _id -= 2; # } # #endif // QT_NO_PROPERTIES # return _id; # }
这段代码将调用最终转到我们自己的实现的函数中来。这个函数不经提供了metamethod的动态调用,而且也提供了property的动态操作方法。可想而知,property的动态调用的实现方式一定和invocalbe method是一致的。
在qobjectdefs.h中有这样的定义:
1 # define METHOD(a) "0"#a 2 # define SLOT(a) "1"#a 3 # define SIGNAL(a) "2"#a
不过是在方法签名之前加了一个数字标记。因为我们既可以将signal连接到slot,也可以将signal连接到signal,所有必须要有某种方法区分一下。
QObject::connect()
bool QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type) { { const void *cbdata[] = { sender, signal, receiver, method, &type }; if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata)) return true; } #ifndef QT_NO_DEBUG bool warnCompat = true; #endif if (type == Qt::AutoCompatConnection) { type = Qt::AutoConnection; #ifndef QT_NO_DEBUG warnCompat = false; #endif } if (sender == 0 || receiver == 0 || signal == 0 || method == 0) { qWarning("QObject::connect: Cannot connect %s::%s to %s::%s", sender ? sender->metaObject()->className() : "(null)", (signal && *signal) ? signal+1 : "(null)", receiver ? receiver->metaObject()->className() : "(null)", (method && *method) ? method+1 : "(null)"); return false; } QByteArray tmp_signal_name; if (!check_signal_macro(sender, signal, "connect", "bind")) return false; const QMetaObject *smeta = sender->metaObject(); const char *signal_arg = signal; ++signal; //skip code int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, false); if (signal_index < 0) { // check for normalized signatures tmp_signal_name = QMetaObject::normalizedSignature(signal - 1); signal = tmp_signal_name.constData() + 1; smeta = sender->metaObject(); signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, false); } if (signal_index < 0) { // re-use tmp_signal_name and signal from above smeta = sender->metaObject(); signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, true); } if (signal_index < 0) { err_method_notfound(sender, signal_arg, "connect"); err_info_about_objects("connect", sender, receiver); return false; } signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index); int signalOffset, methodOffset; computeOffsets(smeta, &signalOffset, &methodOffset); int signal_absolute_index = signal_index + methodOffset; signal_index += signalOffset; QByteArray tmp_method_name; int membcode = extract_code(method); if (!check_method_code(membcode, receiver, method, "connect")) return false; const char *method_arg = method; ++method; // skip code const QMetaObject *rmeta = receiver->metaObject(); int method_index_relative = -1; switch (membcode) { case QSLOT_CODE: method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, false); break; case QSIGNAL_CODE: method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, false); break; } if (method_index_relative < 0) { // check for normalized methods tmp_method_name = QMetaObject::normalizedSignature(method); method = tmp_method_name.constData(); // rmeta may have been modified above rmeta = receiver->metaObject(); switch (membcode) { case QSLOT_CODE: method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, false); if (method_index_relative < 0) method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, true); break; case QSIGNAL_CODE: method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, false); if (method_index_relative < 0) method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, true); break; } } if (method_index_relative < 0) { err_method_notfound(receiver, method_arg, "connect"); err_info_about_objects("connect", sender, receiver); return false; } if (!QMetaObject::checkConnectArgs(signal, method)) { qWarning("QObject::connect: Incompatible sender/receiver arguments" "\n %s::%s --> %s::%s", sender->metaObject()->className(), signal, receiver->metaObject()->className(), method); return false; } int *types = 0; if ((type == Qt::QueuedConnection) && !(types = queuedConnectionTypes(smeta->method(signal_absolute_index).parameterTypes()))) return false; #ifndef QT_NO_DEBUG if (warnCompat) { QMetaMethod smethod = smeta->method(signal_absolute_index); QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset()); check_and_warn_compat(smeta, smethod, rmeta, rmethod); } #endif if (!QMetaObjectPrivate::connect(sender, signal_index, receiver, method_index_relative, rmeta ,type, types)) return false; const_cast<QObject*>(sender)->connectNotify(signal - 1); return true; }
忽略细节,只关注主要的流程,这段代码的做的事情就是将signal在sender的meta system中的signal索引找出,以及接受者方法(signal或slot)在receiver的meta system中的索引找出来。在委托QMetaObjectPrivate::connect()执行连接。
bool QMetaObjectPrivate::connect(const QObject *sender, int signal_index, const QObject *receiver, int method_index, const QMetaObject *rmeta, int type, int *types) { QObject *s = const_cast<QObject *>(sender); QObject *r = const_cast<QObject *>(receiver); int method_offset = rmeta ? rmeta->methodOffset() : 0; QObjectPrivate::StaticMetaCallFunction callFunction = (rmeta && QMetaObjectPrivate::get(rmeta)->revision >= 6 && rmeta->d.extradata) ? reinterpret_cast<const QMetaObjectExtraData *>(rmeta->d.extradata)->static_metacall : 0; QOrderedMutexLocker locker(signalSlotLock(sender), signalSlotLock(receiver)); if (type & Qt::UniqueConnection) { QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists; if (connectionLists && connectionLists->count() > signal_index) { const QObjectPrivate::Connection *c2 = (*connectionLists)[signal_index].first; int method_index_absolute = method_index + method_offset; while (c2) { if (c2->receiver == receiver && c2->method() == method_index_absolute) return false; c2 = c2->nextConnectionList; } } type &= Qt::UniqueConnection - 1; } QObjectPrivate::Connection *c = new QObjectPrivate::Connection; c->sender = s; c->receiver = r; c->method_relative = method_index; c->method_offset = method_offset; c->connectionType = type; c->argumentTypes = types; c->nextConnectionList = 0; c->callFunction = callFunction; QT_TRY { QObjectPrivate::get(s)->addConnection(signal_index, c); } QT_CATCH(...) { delete c; QT_RETHROW; } c->prev = &(QObjectPrivate::get(r)->senders); c->next = *c->prev; *c->prev = c; if (c->next) c->next->prev = &c->next; QObjectPrivate *const sender_d = QObjectPrivate::get(s); if (signal_index < 0) { sender_d->connectedSignals[0] = sender_d->connectedSignals[1] = ~0; } else if (signal_index < (int)sizeof(sender_d->connectedSignals) * 8) { sender_d->connectedSignals[signal_index >> 5] |= (1 << (signal_index & 0x1f)); } return true; }
同样忽略细节,这段代码首先在connecttype要求UniqueConnection的时候检查一下是不是有重复的连接。然后创建一个
QObjectPrivate::Connection结构,这个结构包含了sender,receiver,接受方法的method_index,然后加入到某个连接存储表中;连接存储表可能是一种hash结构,signal_index就是key。可以推测,当signal方法被调用时,一定会到这个结构中查找所有连接到该signal的connection,connection保存了receiver和method_index,由上一篇可知,可以很容易调用到receiver的对应method。