如果一个类的声明中包含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。view plaincopy to clipboardprint?#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文件:view plaincopy to clipboardprint?#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_castvoid*>(const_cast(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 return _id; if (_c == QMetaObject::InvokeMetaMethod) { switch (_id) { case 0: clicked(); break; case 1: pressed(); break; case 2: onEventA((*reinterpret_castconst QString(*)>(_a[1]))); break; case 3: onEventB((*reinterpret_castint(*)>(_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(_v) = getPropertyA(); break; case 1: *reinterpret_cast(_v) = getPropertyB(); break; } _id -= 2; } else if (_c == QMetaObject::WriteProperty) { void *_v = _a[0]; switch (_id) { case 0: getPropertyA(*reinterpret_cast(_v)); break; case 1: getPropertyB(*reinterpret_cast(_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”定义第一个signalclicked()。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文件中以静态数据的形式被定义,其排列有点类似可执行文件中静态数据信息的排布。