这个系列的几篇文章通过阅读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。

Qt MetaObject 详解相关推荐

  1. Qt MetaObject 详解之二:QMeta数据以及数据结构信息

    如果一个类的声明中包含Q_OBJECT宏,那么qmake将为这个类生成 meta信息,这个信息在前一篇中所提到的moc文件中.这一篇通过解析这个一个示例moc文件来阐述这些meta信息的存储方式和格式 ...

  2. Qt Phonon详解

    Qt Phonon详解(转帖) 前言 Phonon严格来说其实非为Qt的library,Phonon原本就是KDE 4的开放原始码多媒体API,後来与Qt合并与开发,所以简单来说就是Qt使用Phono ...

  3. Qt QSqlQueryModel详解

    1.功能概述 QSqlQueryModel是QSqlTableModel的父类.QSqlQueryModel封装了执行SELECT语句从数据库查询数据的功能,但是QSqlQueryModel只能作为只 ...

  4. 《Qt 数据库详解》博客系列文章

    本文章原创于www.yafeilinux.com 转载请注明出处. 由于该系列文章图床都挂了,特下载原文博客的word版. 详细博客请点附件下载. 21-Qt数据库(一)简介 22-Qt数据库(二)添 ...

  5. Qt QTreeWidget 详解

    Qt系列文章目录 文章目录 Qt系列文章目录 前言 主要API 示例 1.头文件: 2.实现文件 二.源码下载 前言 官方文档 QTreeWidget类是一个方便的类,它提供了一个标准tree小部件与 ...

  6. Qt QList详解

    1.QList是一种表示链表的模板类. 2.QList是Qt的一种泛型容器类.它以链表方式存储一组值,并能对这组数据进行快速索引,还提供了快速插入和删除等操作. 3.QList.QLinkedList ...

  7. Qt QVector 详解:从底层原理到高级用法

    目录标题 引言:QVector的重要性与简介 QVector的常用接口 QVector和std::Vector 迭代器:遍历QVector 中的元素(Iterators: Traversing Ele ...

  8. VS中使用Qt方法详解

    在 Qt Creator 中可以使用 MSVC 编译工具对 Qt 项目进行编译.若有人比较习惯于使用 Visual Studio,或某些项目必须使用 Visual Studio,也可以在 Visual ...

  9. Qt QSet 详解:从底层原理到高级用法

    目录标题 引言:QSet的重要性与简介 QSet 的常用接口 迭代器:遍历Qset 中的元素(Iterators: Traversing Elements in Qset ) 高级用法:QSet 中的 ...

最新文章

  1. 2021年ACM Fellow名单出炉!唐杰、刘铁岩、谢涛等当选!
  2. Linux实战教学笔记16:磁盘原理
  3. 用户画像-撸一部分代码啊
  4. h5页面的写法_8. 开始构建我们的 index.hbs 页面
  5. wxWidgets:wxSystemOptions类用法
  6. 学习究竟是为了什么?
  7. C# 调用 Microsoft.VisualBasic.Collection
  8. 最佳适配算法和最差适配算法_影响开放社区的最佳(和最差)方法
  9. 余世维台湾经典讲座--管理者的EQ(一)
  10. cocos-creator 脚本逻辑-2
  11. 主板开启网络唤醒(Wake on lan)
  12. 目前服务器操作系统版本,Windows操作系统的版本选择
  13. 不出门远程控制公司电脑,这7个工具让你不用来回跑。
  14. 计算机教室不安风扇,多媒体教室设备常见故障及解决办法
  15. office精英俱乐部_开放组织读书俱乐部:收回精英制
  16. asp.net配置文件connectionStrings加密和解密
  17. 结对项目——最长英语单词链
  18. 英文版系统远程桌面无法连接到远程计算机,windows server 2016远程桌面进去,英文系统修改语言...
  19. Pandas与SQL比较
  20. 百度地图离线开发demo(热力图)

热门文章

  1. 百万粉女网红突袭国内手机公司:我来拿你们的源代码了!
  2. 武装突袭3fps服务器不稳定,《武装突袭3》深不见底:史上最硬核、最复杂的FPS游戏...
  3. word2016开机后首次打开非常慢_自从用了这些设置方法,电脑开机瞬间提速了50%,辛亏早知道...
  4. Win11任务栏太宽了怎么变窄?
  5. Fn键的功能笔记本fn键在哪?
  6. 数据挖掘实战分享:财政收入影响因素分析及预测(一)
  7. vue watch 修改滚动条_vue实现滚动监听,点击瞄点平滑滚动,控制内嵌滚动条滚动...
  8. 一文读懂~国内外区块链发展现状、趋势和政策
  9. 如何在Excel表格中输入分数?学会这几招轻松搞定!
  10. 李嘉诚的“自负指数”与盖茨的“自私基因”看成功需要什么?