這個系列的幾篇文章通過閱讀Qt幫助文檔和相關的源代碼來學習研究Qt meta-object所提供的功能,及其實現的方式。
Qt meta-object系統基於三個方面:
1、QObject提供一個基類,方便派生類使用meta-object系統的功能;
2、Q_OBJECT宏,在類的聲明體內激活meta-object功能,比如動態屬性、信號、槽;
3、Meta Object編譯器(MOC),為每個QObject派生類生成代碼,以支持meta-object功能。
QObject定義了從一個QObject對象訪問meta-object功能的接口,Q_OBJECT宏用來告訴編譯器該類需要激活meta- object功能,編譯器在掃描一個源文件時,如果發現類的聲明中有這個宏,就會生成一些代碼來為支持meta-object功能——主要是生成該類對應 MetaObject類以及對QObject的函數override。
QObject和QMetaObject:
顧名思義,QMetaObject包含了QObject的所謂的元數據,也就是QObject信息的一些描述信息:除了類型信息外,還包含QT中特 有的signal&slot信息。
QObject::metaObject ()方法返回一個QObject對象對應的metaobject對象,注意這個方法是virtual方法。如上文所說,如果一個類的聲明中包含了 Q_OBJECT宏,編譯器會生成代碼來實現這個類對應的QMetaObject類,並重載QObject::metaObject()方法來返回這個 QMetaObject類的實例引用。這樣當通過QObject類型的引用調用metaObejct方法時,返回的是這個引用的所指的真實對象的 metaobject。
如果一個類從QObject派生,確沒有聲明Q_OBJECT宏,那么這個類的metaobject對象不會被生成,這樣這個類所聲明的 signal slot都不能使用,而這個類實例調用metaObject()返回的就是其父類的metaobject對象,這樣導致的后果就是你從這個類實例獲得的元 數據其實都是父類的數據,這顯然給你的代碼埋下隱患。因此如果一個類從QOBject派生,它都應該聲明Q_OBJECT宏,不管這個類有沒有定義 signal&slot和Property。
這樣每個QObject類都有一個對應的QMetaObject類,形成一個平行的類型層次。
QMetaObject提供的信息:
下面通過QMetaObject的接口來解讀QMetaObject提供的信息:
1、基本信息
const char * className () const;
const QMetaObject * superClass () const
2、classinfo: 提供額外的類信息。其實就是一些名值對。 用戶可以在類的聲明中以Q_CLASSINFO(name, value)方式添加。
int classInfoCount () const
int classInfoOffset () const
QMetaClassInfo classInfo ( int index ) const
int indexOfClassInfo ( const char * name ) const
3、contructor:提供該類的構造方法信息
QMetaMethod constructor ( int index ) const
int constructorCount () const
int indexOfConstructor ( const char * constructor ) const
4、enum:描述該類聲明體中所包含的枚舉類型信息
QMetaEnum enumerator ( int index ) const
int enumeratorCount () const
int enumeratorOffset () const
int indexOfEnumerator ( const char * name ) const
5、method:描述類中所包含方法信息:包括property,signal,slot等,包括祖先類,如何組織暫時不確定。
QMetaMethod method ( int index ) const
int methodCount () const
int methodOffset () const
int indexOfMethod ( const char * method ) const
int indexOfSignal ( const char * signal ) const
int indexOfSlot ( const char * slot ) const
6、property:類型的屬性信息
QMetaProperty property ( int index ) const
int propertyCount () const
int propertyOffset () const
int indexOfProperty ( const char * name ) const
QMetaProperty userProperty () const //返回類中設置了USER flag的屬性,(難道只能有一個這樣的屬性?)
注意:對於類里面定義的函數,構造函數,枚舉,只有加上一些宏才表示你希望為方法提供meta信息。比如 Q_ENUMS用來注冊宏,
Q_INVACABLE用來注冊方法(包括構造函數)。Qt這么設計的原因應該是避免meta信息的臃腫。
如果一個類的聲明中包含Q_OBJECT宏,那么qmake將為這個類生成 meta信息,這個信息在前一篇中所提到的moc文件中。這一篇通過解析這個一個示例moc文件來闡述這些meta信息的存儲方式和格式;本篇先說明了一 下QMetaObject的數據結構,然后呈現了一個簡單的類TestObject類及其生成的moc文件,最后對這個moc文件個內容進行了詳細解釋。
QMetaObject的數據定義:
QMetaObject包含唯一的數據成員如下(見頭文件qobjectdefs.h)
struct QMetaObject
{
private:
struct { // private data
const QMetaObject *superdata; //父類QMetaObject實例的指針
const char *stringdata; //一段字符串內存塊,包含MetaObject信息之字符串信息
const uint *data; //一段二級制內存塊,包含MetaObject信息之二進制信息
const void *extradata; //預留字段,暫未使用
} d;
}
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
}
下文利用一個示例QObject子類及其moc文件,來分析QMetaObject的信息結構。
示例類TestObject:
TestObject類繼承自QObject,定義了兩個Property:propertyA,propertyB;兩個classinfo:Author,Version;一個枚舉:TestEnum。
- #include
- 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 ."
- #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"
};
可以看出,meta信息在moc文件中以靜態數據的形式被定義,其排列有點類似可執行文件中靜態數據信息的排布。
QtMetaObjectsysmtem詳解之三:QMetaObject接口實現
本篇從Qt MetaObject源代碼解讀相關接口的實現,這些接口都定義於qmetaobject.cpp中。
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(data); }
由前一篇可知,d.data指向的是那塊二進制信息,priv將d.data解釋為QMetaObjectPrivate。
QMetaObjectPrivate是QMetaObject的私有實現類,其數據定義部分如下(見頭文件qmetaobject_p.h)。和前一篇的示例moc文件內容一對應,其含義一目了然。
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
}
QMetaObject:: classInfoOffset ()
int 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 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;
}
這個代碼的流程比較簡單。priv(d.data)->classInfoData是classinfo的信息在d.data中的偏移;每條 classinfo信息占2個UINT的大小,因此“priv(d.data)->classInfoData + 2*i”這個表達式的值就是第i個classinfo的信息在d.data中的偏移。
QMetaObject:: indexOfClassInfo ()
int 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
int QMetaObject::constructorCount() const
{
if (priv(d.data)->revision < 2)
return 0;
return priv(d.data)->constructorCount;
}
QMetaMethod constructor ( int index ) const
QMetaMethod QMetaObject::constructor(int index) const
{
int i = index;
QMetaMethod result;
if (priv(d.data)->revision >= 2 && i >= 0 && i < priv(d.data)->constructorCount) {
result.mobj = this;
result.handle = priv(d.data)->constructorData + 5*i;
}
return result;
}
int indexOfConstructor ( const char * constructor ) const
int QMetaObject::indexOfConstructor(const char *constructor) const
{
if (priv(d.data)->revision < 2)
return -1;
for (int i = priv(d.data)->constructorCount-1; i >= 0; --i) {
if (strcmp(constructor, d.stringdata
+ d.data[priv(d.data)->constructorData + 5*i]) == 0) {
return i;
}
}
return -1;
}
int enumeratorCount () const
int enumeratorOffset () const
QMetaEnum enumerator ( int index ) const
int indexOfEnumerator ( const char * name ) const
這組函數與classinfo那一組的實現及其相似。
int methodCount () const 略;
int methodOffset () const 略;
QMetaMethod 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 indexOfMethod ( const char * method ) const 略;
int indexOfSignal ( const char * signal ) const
{
const QMetaObject *m = this;
int i = QMetaObjectPrivate::indexOfSignalRelative(&m, signal);
if (i >= 0)
i += m->methodOffset();
return i;
}
int QMetaObjectPrivate::indexOfSignalRelative(const QMetaObject **baseObject, const char *signal)
{
int i = -1;
while (*baseObject) {
const QMetaObject *const m = *baseObject;
for (i = priv(m->d.data)->methodCount-1; i >= 0; --i)
if ((m->d.data[priv(m->d.data)->methodData + 5*i + 4] & MethodTypeMask) == MethodSignal
&& strcmp(signal, m->d.stringdata
+ m->d.data[priv(m->d.data)->methodData + 5*i]) == 0) {
break;
}
if (i >= 0)
break;
*baseObject = m->d.superdata;
}
}
可以看出,查找signal的特別之處在於,通過method元數據的第五項來判斷這是不是一個signal。
int indexOfSlot ( const char * slot ) const 略;
int propertyCount () const 略;
int propertyOffset () const 略;
int indexOfProperty ( const char * name ) const 略;
QMetaProperty 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 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。
Qt MetaObject System詳解之四:meta call
所謂meta call就是通過object的meta system的支持來動態調用object的方法,metacall也是signal&slot的機制的基石。本篇通過參考源代碼來探究meta call的實現方法。
QMetaObject::invokeMethod():
bool invokeMethod ( QObject * obj , const char * member , Qt::ConnectionType type , 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() )
QMetaObject這個靜態方法可以動態地調用obj對象名字為member的方法,type參數表明該調用時同步的還是異步的。ret是一個 通用的用來存儲返回值的類型,后面的9個參數是用來傳遞調用參數的,QGenericArgument()是一種通用的存儲參數值的類型。(這里讓人感覺 比較奇怪的是Qt為什么不將這個參數列表弄成某種動態的形式,而是最多九個)
所調用的方法必須是invocable的,也就是signal,slot或者是加了聲明為Q_INVOCABLE的其他方法。
這個方法的實現如下:
- 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())
- 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;
- // 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;
- }
- // 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 methodIndex = ((handle - priv(mobj->d.data)->methodData) / 5) + mobj->methodOffset();
- if (connectionType == Qt::DirectConnection) {
- return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param) < 0;
- } else {
- 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;
- }
- }
- if (connectionType == Qt::QueuedConnection) {
- QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,
- 0,
- -1,
- nargs,
- types,
- args));
- } else {
- if (currentThread == objectThread) {
- qWarning("QMetaMethod::invoke: Dead lock detected in "
- "BlockingQueuedConnection: Receiver is %s(%p)",
- mobj->className(), object);
- }
- // blocking queued connection
- #ifdef QT_NO_THREAD
- QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,
- 0,
- -1,
- nargs,
- types,
- args));
- #else
- QSemaphore semaphore;
- QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,
- 0,
- -1,
- nargs,
- types,
- args,
- &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的實現如下:
- /*!
- \internal
- */
- int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)
- {
- if (QMetaObject *mo = object->d_ptr->metaObject)
- return static_cast(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函數里有如下代碼:
- case QEvent::MetaCall:
- {
- d_func()->inEventHandler = false;
- QMetaCallEvent *mce = static_cast(e);
- QObjectPrivate::Sender currentSender;
- currentSender.sender = const_cast(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的代碼很簡單:
int QMetaCallEvent::placeMetaCall(QObject *object)
{ return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, id_, 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是一致的。
Qt MetaObject System詳解之五:signal&slot
本篇探析signal slot的連接和調用是如何實現的。
宏SLOT,SIGNAL
在qobjectdefs.h中有這樣的定義:
# define METHOD(a) "0"#a
# define SLOT(a) "1"#a
# 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);
- 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);
- 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 = -1;
- switch (membcode) {
- case QSLOT_CODE:
- method_index = rmeta->indexOfSlot(method);
- break;
- case QSIGNAL_CODE:
- method_index = rmeta->indexOfSignal(method);
- break;
- }
- if (method_index < 0) {
- // check for normalized methods
- tmp_method_name = QMetaObject::normalizedSignature(method);
- method = tmp_method_name.constData();
- switch (membcode) {
- case QSLOT_CODE:
- method_index = rmeta->indexOfSlot(method);
- break;
- case QSIGNAL_CODE:
- method_index = rmeta->indexOfSignal(method);
- break;
- }
- }
- if (method_index < 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 || type == Qt::BlockingQueuedConnection)
- && !(types = queuedConnectionTypes(smeta->method(signal_absolute_index).parameterTypes())))
- return false;
- #ifndef QT_NO_DEBUG
- {
- QMetaMethod smethod = smeta->method(signal_absolute_index);
- QMetaMethod rmethod = rmeta->method(method_index);
- if (warnCompat) {
- if(smethod.attributes() & QMetaMethod::Compatibility) {
- if (!(rmethod.attributes() & QMetaMethod::Compatibility))
- qWarning("QObject::connect: Connecting from COMPAT signal (%s::%s)", smeta->className(), signal);
- } else if(rmethod.attributes() & QMetaMethod::Compatibility && membcode != QSIGNAL_CODE) {
- qWarning("QObject::connect: Connecting from %s::%s to COMPAT slot (%s::%s)",
- smeta->className(), signal, rmeta->className(), method);
- }
- }
- }
- #endif
- if (!QMetaObjectPrivate::connect(sender, signal_index, receiver, method_index, type, types))
- return false;
- const_cast(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, int type, int *types)
- {
- QObject *s = const_cast(sender);
- QObject *r = const_cast(receiver);
- 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;
- while (c2) {
- if (c2->receiver == receiver && c2->method == method_index)
- return false;
- c2 = c2->nextConnectionList;
- }
- }
- type &= Qt::UniqueConnection - 1;
- }
- QObjectPrivate::Connection *c = new QObjectPrivate::Connection;
- c->sender = s;
- c->receiver = r;
- c->method = method_index;
- c->connectionType = type;
- c->argumentTypes = types;
- c->nextConnectionList = 0;
- 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。