Qt MetaObject 詳解


這個系列的幾篇文章通過閱讀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。

  1. #include   
  2. class TestObject : public QObject  
  3. {  
  4.     Q_OBJECT  
  5.     Q_PROPERTY(QString propertyA  READ getPropertyA WRITE getPropertyA RESET resetPropertyA DESIGNABLE true SCRIPTABLE true STORED true USER false)  
  6.     Q_PROPERTY(QString propertyB  READ getPropertyB WRITE getPropertyB RESET resetPropertyB)  
  7.     Q_CLASSINFO("Author""Long Huihu")  
  8.     Q_CLASSINFO("Version""TestObjectV1.0")  
  9.     Q_ENUMS(TestEnum)  
  10. public:  
  11.     enum TestEnum {  
  12.         EnumValueA,  
  13.         EnumValueB  
  14.     };  
  15. public:  
  16.     TestObject();  
  17. signals:  
  18.     void clicked();  
  19.     void pressed();  
  20. public slots:  
  21.     void onEventA(const QString &);  
  22.     void onEventB(int );  
  23. }  
 

 

示例類TestObject的moc文件:

  1. #include "TestObject.h"  
  2. #if !defined(Q_MOC_OUTPUT_REVISION)  
  3. #error "The header file 'TestObject.h' doesn't include ."  
  4. #elif Q_MOC_OUTPUT_REVISION != 62  
  5. #error "This file was generated using the moc from 4.6.0. It"  
  6. #error "cannot be used with the include files from this version of Qt."  
  7. #error "(The moc has changed too much.)"  
  8. #endif  
  9. QT_BEGIN_MOC_NAMESPACE  
  10. static const uint qt_meta_data_TestObject[] = {  
  11.  // content:  
  12.        4,       // revision  
  13.        0,       // classname  
  14.        2,   14, // classinfo  
  15.        4,   18, // methods  
  16.        2,   38, // properties  
  17.        1,   44, // enums/sets  
  18.        0,    0, // constructors  
  19.        0,       // flags  
  20.        2,       // signalCount  
  21.  // classinfo: key, value  
  22.       22,   11,  
  23.       44,   29,  
  24.  // signals: signature, parameters, type, tag, flags  
  25.       53,   52,   52,   52, 0x05,  
  26.       63,   52,   52,   52, 0x05,  
  27.  // slots: signature, parameters, type, tag, flags  
  28.       73,   52,   52,   52, 0x0a,  
  29.       91,   52,   52,   52, 0x0a,  
  30.  // properties: name, type, flags  
  31.      113,  105, 0x0a095007,  
  32.      123,  105, 0x0a095007,  
  33.  // enums: name, flags, count, data  
  34.      133, 0x0,    2,   48,  
  35.  // enum data: key, value  
  36.      142, uint(TestObject::EnumValueA),  
  37.      153, uint(TestObject::EnumValueB),  
  38.        0        // eod  
  39. };  
  40. static const char qt_meta_stringdata_TestObject[] = {  
  41.     "TestObject\0Long Huihu\0Author\0"  
  42.     "TestObjectV1.0\0Version\0\0clicked()\0"  
  43.     "pressed()\0onEventA(QString)\0onEventB(int)\0"  
  44.     "QString\0propertyA\0propertyB\0TestEnum\0"  
  45.     "EnumValueA\0EnumValueB\0"  
  46. };  
  47. const QMetaObject TestObject::staticMetaObject = {  
  48.     { &QObject::staticMetaObject, qt_meta_stringdata_TestObject,  
  49.       qt_meta_data_TestObject, 0 }  
  50. };  
  51. #ifdef Q_NO_DATA_RELOCATION  
  52. const QMetaObject &TestObject::getStaticMetaObject() { return staticMetaObject; }  
  53. #endif //Q_NO_DATA_RELOCATION  
  54. const QMetaObject *TestObject::metaObject() const  
  55. {  
  56.     return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;  
  57. }  
  58. void *TestObject::qt_metacast(const char *_clname)  
  59. {  
  60.     if (!_clname) return 0;  
  61.     if (!strcmp(_clname, qt_meta_stringdata_TestObject))  
  62.         return static_cast<void*>(const_cast< TestObject*>(this));  
  63.     return QObject::qt_metacast(_clname);  
  64. }  
  65. int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)  
  66. {  
  67.     _id = QObject::qt_metacall(_c, _id, _a);  
  68.     if (_id < 0)  
  69.         return _id;  
  70.     if (_c == QMetaObject::InvokeMetaMethod) {  
  71.         switch (_id) {  
  72.         case 0: clicked(); break;  
  73.         case 1: pressed(); break;  
  74.         case 2: onEventA((*reinterpret_castconst QString(*)>(_a[1]))); break;  
  75.         case 3: onEventB((*reinterpret_castint(*)>(_a[1]))); break;  
  76.         default: ;  
  77.         }  
  78.         _id -= 4;  
  79.     }  
  80. #ifndef QT_NO_PROPERTIES  
  81.       else if (_c == QMetaObject::ReadProperty) {  
  82.         void *_v = _a[0];  
  83.         switch (_id) {  
  84.         case 0: *reinterpret_cast< QString*>(_v) = getPropertyA(); break;  
  85.         case 1: *reinterpret_cast< QString*>(_v) = getPropertyB(); break;  
  86.         }  
  87.         _id -= 2;  
  88.     } else if (_c == QMetaObject::WriteProperty) {  
  89.         void *_v = _a[0];  
  90.         switch (_id) {  
  91.         case 0: getPropertyA(*reinterpret_cast< QString*>(_v)); break;  
  92.         case 1: getPropertyB(*reinterpret_cast< QString*>(_v)); break;  
  93.         }  
  94.         _id -= 2;  
  95.     } else if (_c == QMetaObject::ResetProperty) {  
  96.         switch (_id) {  
  97.         case 0: resetPropertyA(); break;  
  98.         case 1: resetPropertyB(); break;  
  99.         }  
  100.         _id -= 2;  
  101.     } else if (_c == QMetaObject::QueryPropertyDesignable) {  
  102.         _id -= 2;  
  103.     } else if (_c == QMetaObject::QueryPropertyScriptable) {  
  104.         _id -= 2;  
  105.     } else if (_c == QMetaObject::QueryPropertyStored) {  
  106.         _id -= 2;  
  107.     } else if (_c == QMetaObject::QueryPropertyEditable) {  
  108.         _id -= 2;  
  109.     } else if (_c == QMetaObject::QueryPropertyUser) {  
  110.         _id -= 2;  
  111.     }  
  112. #endif // QT_NO_PROPERTIES  
  113.     return _id;  
  114. }  
  115. // SIGNAL 0  
  116. void TestObject::clicked()  
  117. {  
  118.     QMetaObject::activate(this, &staticMetaObject, 0, 0);  
  119. }  
  120. // SIGNAL 1  
  121. void TestObject::pressed()  
  122. {  
  123.     QMetaObject::activate(this, &staticMetaObject, 1, 0);  
  124. }  
  125. 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的其他方法。

這個方法的實現如下:

  1. if (!obj)  
  2.         return false;  
  3.     QVarLengthArray<char, 512> sig;  
  4.     int len = qstrlen(member);  
  5.     if (len <= 0)  
  6.         return false;  
  7.     sig.append(member, len);  
  8.     sig.append('(');  
  9.     const char *typeNames[] = {ret.name(), val0.name(), val1.name(), val2.name(), val3.name(),  
  10.                                val4.name(), val5.name(), val6.name(), val7.name(), val8.name(),  
  11.                                val9.name()};  
  12.     int paramCount;  
  13.     for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {  
  14.         len = qstrlen(typeNames[paramCount]);  
  15.         if (len <= 0)  
  16.             break;  
  17.         sig.append(typeNames[paramCount], len);  
  18.         sig.append(',');  
  19.     }  
  20.     if (paramCount == 1)  
  21.         sig.append(')'); // no parameters  
  22.     else  
  23.         sig[sig.size() - 1] = ')';  
  24.     sig.append('\0');  
  25.     int idx = obj->metaObject()->indexOfMethod(sig.constData());  
  26.     if (idx < 0) {  
  27.         QByteArray norm = QMetaObject::normalizedSignature(sig.constData());  
  28.         idx = obj->metaObject()->indexOfMethod(norm.constData());  
  29.     }  
  30.     if (idx < 0 || idx >= obj->metaObject()->methodCount())  
  31.         return false;  
  32.     QMetaMethod method = obj->metaObject()->method(idx);  
  33.     return method.invoke(obj, type, ret,  
  34.                          val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);  
  35. }  

先依據傳遞的方法名稱和參數,構造完整的函數簽名(存儲在局部變量sig)。參數的類型名就是調用時傳遞時的參數靜態類型,這里可不會有什么類型轉換,這是運行時的行為,參數類型轉換是編譯時的行為。

然后通過這個sig簽名在obj中去查找該方法,查詢的結果就是一個QMetaMethod值,再將調用委托給QMetaMethod::invoke方法。

  1. bool QMetaMethod::invoke(QObject *object,  
  2.                          Qt::ConnectionType connectionType,  
  3.                          QGenericReturnArgument returnValue,  
  4.                          QGenericArgument val0,  
  5.                          QGenericArgument val1,  
  6.                          QGenericArgument val2,  
  7.                          QGenericArgument val3,  
  8.                          QGenericArgument val4,  
  9.                          QGenericArgument val5,  
  10.                          QGenericArgument val6,  
  11.                          QGenericArgument val7,  
  12.                          QGenericArgument val8,  
  13.                          QGenericArgument val9) const  
  14. {  
  15.     if (!object || !mobj)  
  16.         return false;  
  17.     // check return type  
  18.     if (returnValue.data()) {  
  19.         const char *retType = typeName();  
  20.         if (qstrcmp(returnValue.name(), retType) != 0) {  
  21.             // normalize the return value as well  
  22.             // the trick here is to make a function signature out of the return type  
  23.             // so that we can call normalizedSignature() and avoid duplicating code  
  24.             QByteArray unnormalized;  
  25.             int len = qstrlen(returnValue.name());  
  26.             unnormalized.reserve(len + 3);  
  27.             unnormalized = "_(";        // the function is called "_"  
  28.             unnormalized.append(returnValue.name());  
  29.             unnormalized.append(')');  
  30.             QByteArray normalized = QMetaObject::normalizedSignature(unnormalized.constData());  
  31.             normalized.truncate(normalized.length() - 1); // drop the ending ')'  
  32.             if (qstrcmp(normalized.constData() + 2, retType) != 0)  
  33.                 return false;  
  34.         }  
  35.     }  
  36.     // check argument count (we don't allow invoking a method if given too few arguments)  
  37.     const char *typeNames[] = {  
  38.         returnValue.name(),  
  39.         val0.name(),  
  40.         val1.name(),  
  41.         val2.name(),  
  42.         val3.name(),  
  43.         val4.name(),  
  44.         val5.name(),  
  45.         val6.name(),  
  46.         val7.name(),  
  47.         val8.name(),  
  48.         val9.name()  
  49.     };  
  50.     int paramCount;  
  51.     for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {  
  52.         if (qstrlen(typeNames[paramCount]) <= 0)  
  53.             break;  
  54.     }  
  55.     int metaMethodArgumentCount = 0;  
  56.     {  
  57.         // based on QMetaObject::parameterNames()  
  58.         const char *names = mobj->d.stringdata + mobj->d.data[handle + 1];  
  59.         if (*names == 0) {  
  60.             // do we have one or zero arguments?  
  61.             const char *signature = mobj->d.stringdata + mobj->d.data[handle];  
  62.             while (*signature && *signature != '(')  
  63.                 ++signature;  
  64.             if (*++signature != ')')  
  65.                 ++metaMethodArgumentCount;  
  66.         } else {  
  67.             --names;  
  68.             do {  
  69.                 ++names;  
  70.                 while (*names && *names != ',')  
  71.                     ++names;  
  72.                 ++metaMethodArgumentCount;  
  73.             } while (*names);  
  74.         }  
  75.     }  
  76.     if (paramCount <= metaMethodArgumentCount)  
  77.         return false;  
  78.     // check connection type  
  79.     QThread *currentThread = QThread::currentThread();  
  80.     QThread *objectThread = object->thread();  
  81.     if (connectionType == Qt::AutoConnection) {  
  82.         connectionType = currentThread == objectThread  
  83.                          ? Qt::DirectConnection  
  84.                          : Qt::QueuedConnection;  
  85.     }  
  86.     // invoke!  
  87.     void *param[] = {  
  88.         returnValue.data(),  
  89.         val0.data(),  
  90.         val1.data(),  
  91.         val2.data(),  
  92.         val3.data(),  
  93.         val4.data(),  
  94.         val5.data(),  
  95.         val6.data(),  
  96.         val7.data(),  
  97.         val8.data(),  
  98.         val9.data()  
  99.     };  
  100.     // recompute the methodIndex by reversing the arithmetic in QMetaObject::property()  
  101.     int methodIndex = ((handle - priv(mobj->d.data)->methodData) / 5) + mobj->methodOffset();  
  102.     if (connectionType == Qt::DirectConnection) {  
  103.         return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param) < 0;  
  104.     } else {  
  105.         if (returnValue.data()) {  
  106.             qWarning("QMetaMethod::invoke: Unable to invoke methods with return values in "  
  107.                      "queued connections");  
  108.             return false;  
  109.         }  
  110.         int nargs = 1; // include return type  
  111.         void **args = (void **) qMalloc(paramCount * sizeof(void *));  
  112.         Q_CHECK_PTR(args);  
  113.         int *types = (int *) qMalloc(paramCount * sizeof(int));  
  114.         Q_CHECK_PTR(types);  
  115.         types[0] = 0; // return type  
  116.         args[0] = 0;  
  117.         for (int i = 1; i < paramCount; ++i) {  
  118.             types[i] = QMetaType::type(typeNames[i]);  
  119.             if (types[i]) {  
  120.                 args[i] = QMetaType::construct(types[i], param[i]);  
  121.                 ++nargs;  
  122.             } else if (param[i]) {  
  123.                 qWarning("QMetaMethod::invoke: Unable to handle unregistered datatype '%s'",  
  124.                          typeNames[i]);  
  125.                 for (int x = 1; x < i; ++x) {  
  126.                     if (types[x] && args[x])  
  127.                         QMetaType::destroy(types[x], args[x]);  
  128.                 }  
  129.                 qFree(types);  
  130.                 qFree(args);  
  131.                 return false;  
  132.             }  
  133.         }  
  134.         if (connectionType == Qt::QueuedConnection) {  
  135.             QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,  
  136.                                                                    0,  
  137.                                                                    -1,  
  138.                                                                    nargs,  
  139.                                                                    types,  
  140.                                                                    args));  
  141.         } else {  
  142.             if (currentThread == objectThread) {  
  143.                 qWarning("QMetaMethod::invoke: Dead lock detected in "  
  144.                          "BlockingQueuedConnection: Receiver is %s(%p)",  
  145.                          mobj->className(), object);  
  146.             }  
  147.             // blocking queued connection  
  148. #ifdef QT_NO_THREAD  
  149.             QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,  
  150.                                                                    0,  
  151.                                                                    -1,  
  152.                                                                    nargs,  
  153.                                                                    types,  
  154.                                                                    args));  
  155. #else  
  156.             QSemaphore semaphore;  
  157.             QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,  
  158.                                                                    0,  
  159.                                                                    -1,  
  160.                                                                    nargs,  
  161.                                                                    types,  
  162.                                                                    args,  
  163.                                                                    &semaphore));  
  164.             semaphore.acquire();  
  165. #endif // QT_NO_THREAD  
  166.         }  
  167.     }  
  168.     return true;  
  169. }  

代碼首先檢查返回值的類型是否正確;再檢查參數的個數是否匹配,看懂這段代碼需要參考該系列之二對moc文件的解析;再依據當前線程和被調對象所屬 線程來調整connnection type;如果是directconnection,直接調用 QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param),param是將所有參數值指針排列組成的指針數組。如果不是directconnection,也即異步調用,就通過一個post一個 QMetaCallEvent到obj,此時須將所有的參數復制一份存入event對象。

QMetaObject::metacall的實現如下:

  1. /*! 
  2.     \internal 
  3. */  
  4. int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)  
  5. {  
  6.     if (QMetaObject *mo = object->d_ptr->metaObject)  
  7.         return static_cast(mo)->metaCall(cl, idx, argv);  
  8.     else  
  9.         return object->qt_metacall(cl, idx, argv);  
  10. }   

如果object->d_ptr->metaObject(QMetaObjectPrivate)存在,通過該metaobject 來調用,這里要參考該系列之三對QMetaObjectPrivate的介紹,這個條件實際上就是object就是QObject類型,而不是派生類型。 否則調用object::qt_metacall。

對於異步調用,QObject的event函數里有如下代碼:

  1.     case QEvent::MetaCall:  
  2.         {  
  3.             d_func()->inEventHandler = false;  
  4.             QMetaCallEvent *mce = static_cast(e);  
  5.             QObjectPrivate::Sender currentSender;  
  6.             currentSender.sender = const_cast(mce->sender());  
  7.             currentSender.signal = mce->signalId();  
  8.             currentSender.ref = 1;  
  9.             QObjectPrivate::Sender * const previousSender =  
  10.                 QObjectPrivate::setCurrentSender(this, ¤tSender);  
  11. #if defined(QT_NO_EXCEPTIONS)  
  12.             mce->placeMetaCall(this);  
  13. #else  
  14.             QT_TRY {  
  15.                 mce->placeMetaCall(this);  
  16.             } QT_CATCH(...) {  
  17.                 QObjectPrivate::resetCurrentSender(this, ¤tSender, previousSender);  
  18.                 QT_RETHROW;  
  19.             }  
  20. #endif  
  21.             QObjectPrivate::resetCurrentSender(this, ¤tSender, previousSender);  
  22.             break;  
  23.         }  

QMetaCallEvent的代碼很簡單:

int QMetaCallEvent::placeMetaCall(QObject *object)
{    return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, id_, args_);}

殊途同歸。

最后來看一下object->qt_metacall是如何實現的,這又回到了該系統之二所提供的示例moc文件中去了。該文件提供了該方法的實現:

  1. # int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)    
  2. # {    
  3. #     _id = QObject::qt_metacall(_c, _id, _a);    
  4. #     if (_id < 0)    
  5. #         return _id;    
  6. #     if (_c == QMetaObject::InvokeMetaMethod) {    
  7. #         switch (_id) {    
  8. #         case 0: clicked(); break;    
  9. #         case 1: pressed(); break;    
  10. #         case 2: onEventA((*reinterpret_cast< const QString(*)>(_a[1]))); break;    
  11. #         case 3: onEventB((*reinterpret_cast< int(*)>(_a[1]))); break;    
  12. #         default: ;    
  13. #         }    
  14. #         _id -= 4;    
  15. #     }    
  16. # #ifndef QT_NO_PROPERTIES    
  17. #       else if (_c == QMetaObject::ReadProperty) {    
  18. #         void *_v = _a[0];    
  19. #         switch (_id) {    
  20. #         case 0: *reinterpret_cast< QString*>(_v) = getPropertyA(); break;    
  21. #         case 1: *reinterpret_cast< QString*>(_v) = getPropertyB(); break;    
  22. #         }    
  23. #         _id -= 2;    
  24. #     } else if (_c == QMetaObject::WriteProperty) {    
  25. #         void *_v = _a[0];    
  26. #         switch (_id) {    
  27. #         case 0: getPropertyA(*reinterpret_cast< QString*>(_v)); break;    
  28. #         case 1: getPropertyB(*reinterpret_cast< QString*>(_v)); break;    
  29. #         }    
  30. #         _id -= 2;    
  31. #     } else if (_c == QMetaObject::ResetProperty) {    
  32. #         switch (_id) {    
  33. #         case 0: resetPropertyA(); break;    
  34. #         case 1: resetPropertyB(); break;    
  35. #         }    
  36. #         _id -= 2;    
  37. #     } else if (_c == QMetaObject::QueryPropertyDesignable) {    
  38. #         _id -= 2;    
  39. #     } else if (_c == QMetaObject::QueryPropertyScriptable) {    
  40. #         _id -= 2;    
  41. #     } else if (_c == QMetaObject::QueryPropertyStored) {    
  42. #         _id -= 2;    
  43. #     } else if (_c == QMetaObject::QueryPropertyEditable) {    
  44. #         _id -= 2;    
  45. #     } else if (_c == QMetaObject::QueryPropertyUser) {    
  46. #         _id -= 2;    
  47. #     }    
  48. # #endif // QT_NO_PROPERTIES    
  49. #     return _id;    
  50. # }    

這段代碼將調用最終轉到我們自己的實現的函數中來。這個函數不經提供了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()

  1. bool QObject::connect(const QObject *sender, const char *signal,  
  2.                       const QObject *receiver, const char *method,  
  3.                       Qt::ConnectionType type)  
  4. {  
  5.     {  
  6.         const void *cbdata[] = { sender, signal, receiver, method, &type };  
  7.         if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))  
  8.             return true;  
  9.     }  
  10. #ifndef QT_NO_DEBUG  
  11.     bool warnCompat = true;  
  12. #endif  
  13.     if (type == Qt::AutoCompatConnection) {  
  14.         type = Qt::AutoConnection;  
  15. #ifndef QT_NO_DEBUG  
  16.         warnCompat = false;  
  17. #endif  
  18.     }  
  19.     if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {  
  20.         qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",  
  21.                  sender ? sender->metaObject()->className() : "(null)",  
  22.                  (signal && *signal) ? signal+1 : "(null)",  
  23.                  receiver ? receiver->metaObject()->className() : "(null)",  
  24.                  (method && *method) ? method+1 : "(null)");  
  25.         return false;  
  26.     }  
  27.     QByteArray tmp_signal_name;  
  28.     if (!check_signal_macro(sender, signal, "connect", "bind"))  
  29.         return false;  
  30.     const QMetaObject *smeta = sender->metaObject();  
  31.     const char *signal_arg = signal;  
  32.     ++signal; //skip code  
  33.     int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal);  
  34.     if (signal_index < 0) {  
  35.         // check for normalized signatures  
  36.         tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);  
  37.         signal = tmp_signal_name.constData() + 1;  
  38.         smeta = sender->metaObject();  
  39.         signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal);  
  40.         if (signal_index < 0) {  
  41.             err_method_notfound(sender, signal_arg, "connect");  
  42.             err_info_about_objects("connect", sender, receiver);  
  43.             return false;  
  44.         }  
  45.     }  
  46.     signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);  
  47.     int signalOffset, methodOffset;  
  48.     computeOffsets(smeta, &signalOffset, &methodOffset);  
  49.     int signal_absolute_index = signal_index + methodOffset;  
  50.     signal_index += signalOffset;  
  51.     QByteArray tmp_method_name;  
  52.     int membcode = extract_code(method);  
  53.     if (!check_method_code(membcode, receiver, method, "connect"))  
  54.         return false;  
  55.     const char *method_arg = method;  
  56.     ++method; // skip code  
  57.     const QMetaObject *rmeta = receiver->metaObject();  
  58.     int method_index = -1;  
  59.     switch (membcode) {  
  60.     case QSLOT_CODE:  
  61.         method_index = rmeta->indexOfSlot(method);  
  62.         break;  
  63.     case QSIGNAL_CODE:  
  64.         method_index = rmeta->indexOfSignal(method);  
  65.         break;  
  66.     }  
  67.     if (method_index < 0) {  
  68.         // check for normalized methods  
  69.         tmp_method_name = QMetaObject::normalizedSignature(method);  
  70.         method = tmp_method_name.constData();  
  71.         switch (membcode) {  
  72.         case QSLOT_CODE:  
  73.             method_index = rmeta->indexOfSlot(method);  
  74.             break;  
  75.         case QSIGNAL_CODE:  
  76.             method_index = rmeta->indexOfSignal(method);  
  77.             break;  
  78.         }  
  79.     }  
  80.     if (method_index < 0) {  
  81.         err_method_notfound(receiver, method_arg, "connect");  
  82.         err_info_about_objects("connect", sender, receiver);  
  83.         return false;  
  84.     }  
  85.     if (!QMetaObject::checkConnectArgs(signal, method)) {  
  86.         qWarning("QObject::connect: Incompatible sender/receiver arguments"  
  87.                  "\n        %s::%s --> %s::%s",  
  88.                  sender->metaObject()->className(), signal,  
  89.                  receiver->metaObject()->className(), method);  
  90.         return false;  
  91.     }  
  92.     int *types = 0;  
  93.     if ((type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)  
  94.             && !(types = queuedConnectionTypes(smeta->method(signal_absolute_index).parameterTypes())))  
  95.         return false;  
  96. #ifndef QT_NO_DEBUG  
  97.     {  
  98.         QMetaMethod smethod = smeta->method(signal_absolute_index);  
  99.         QMetaMethod rmethod = rmeta->method(method_index);  
  100.         if (warnCompat) {  
  101.             if(smethod.attributes() & QMetaMethod::Compatibility) {  
  102.                 if (!(rmethod.attributes() & QMetaMethod::Compatibility))  
  103.                     qWarning("QObject::connect: Connecting from COMPAT signal (%s::%s)", smeta->className(), signal);  
  104.             } else if(rmethod.attributes() & QMetaMethod::Compatibility && membcode != QSIGNAL_CODE) {  
  105.                 qWarning("QObject::connect: Connecting from %s::%s to COMPAT slot (%s::%s)",  
  106.                          smeta->className(), signal, rmeta->className(), method);  
  107.             }  
  108.         }  
  109.     }  
  110. #endif  
  111.     if (!QMetaObjectPrivate::connect(sender, signal_index, receiver, method_index, type, types))  
  112.         return false;  
  113.     const_cast(sender)->connectNotify(signal - 1);  
  114.     return true;  
  115. }  

忽略細節,只關注主要的流程,這段代碼的做的事情就是將signal在sender的meta system中的signal索引找出,以及接受者方法(signal或slot)在receiver的meta system中的索引找出來。在委托QMetaObjectPrivate::connect()執行連接。

  1. bool QMetaObjectPrivate::connect(const QObject *sender, int signal_index,  
  2.                                  const QObject *receiver, int method_index, int type, int *types)  
  3. {  
  4.     QObject *s = const_cast(sender);  
  5.     QObject *r = const_cast(receiver);  
  6.     QOrderedMutexLocker locker(signalSlotLock(sender),  
  7.                                signalSlotLock(receiver));  
  8.     if (type & Qt::UniqueConnection) {  
  9.         QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;  
  10.         if (connectionLists && connectionLists->count() > signal_index) {  
  11.             const QObjectPrivate::Connection *c2 =  
  12.                 (*connectionLists)[signal_index].first;  
  13.             while (c2) {  
  14.                 if (c2->receiver == receiver && c2->method == method_index)  
  15.                     return false;  
  16.                 c2 = c2->nextConnectionList;  
  17.             }  
  18.         }  
  19.         type &= Qt::UniqueConnection - 1;  
  20.     }  
  21.     QObjectPrivate::Connection *c = new QObjectPrivate::Connection;  
  22.     c->sender = s;  
  23.     c->receiver = r;  
  24.     c->method = method_index;  
  25.     c->connectionType = type;  
  26.     c->argumentTypes = types;  
  27.     c->nextConnectionList = 0;  
  28.     QT_TRY {  
  29.         QObjectPrivate::get(s)->addConnection(signal_index, c);  
  30.     } QT_CATCH(...) {  
  31.         delete c;  
  32.         QT_RETHROW;  
  33.     }  
  34.     c->prev = &(QObjectPrivate::get(r)->senders);  
  35.     c->next = *c->prev;  
  36.     *c->prev = c;  
  37.     if (c->next)  
  38.         c->next->prev = &c->next;  
  39.     QObjectPrivate *const sender_d = QObjectPrivate::get(s);  
  40.     if (signal_index < 0) {  
  41.         sender_d->connectedSignals[0] = sender_d->connectedSignals[1] = ~0;  
  42.     } else if (signal_index < (int)sizeof(sender_d->connectedSignals) * 8) {  
  43.         sender_d->connectedSignals[signal_index >> 5] |= (1 << (signal_index & 0x1f));  
  44.     }  
  45.     return true;  
  46. }  

同樣忽略細節,這段代碼首先在connecttype要求UniqueConnection的時候檢查一下是不是有重復的連接。然后創建一個 QObjectPrivate::Connection結構,這個結構包含了sender,receiver,接受方法的method_index,然后 加入到某個連接存儲表中;連接存儲表可能是一種hash結構,signal_index就是key。可以推測,當signal方法被調用時,一定會到這個 結構中查找所有連接到該signal的connection,connection保存了receiver和method index,由上一篇可知,可以很容易調用到receiver的對應method。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM