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。

posted on 2021-02-05 09:51  莫水千流  阅读(6992)  评论(0编辑  收藏  举报