研一的時候開始使用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。