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。

#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。

  • 13
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值