Qt MetaObject System详解
Posted lfsblackoverflow
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt MetaObject System详解相关的知识,希望对你有一定的参考价值。
网上的资源比较乱,该文章整理自地址:http://www.xuebuyuan.com/735789.html
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信息。
1 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,基本信息
1 struct Q_CORE_EXPORT QMetaObject 2 { 3 const char *className() const; 4 const QMetaObject *superClass() const;
2,classinfo:提供额外的类信息。其实就是一些名值对。用户可以在类的声明中以
1 Q_CLASSINFO(name, value)
方式添加
1 int classInfoOffset() const; 2 int classInfoCount() const; 3 int indexOfClassInfo(const char *name) const; 4 QMetaClassInfo classInfo(int index) const;
3、contructor:提供该类的构造方法信息
1 int constructorCount() const; 2 int indexOfConstructor(const char *constructor) const; 3 QMetaMethod constructor(int index) const;
4、enum:描述该类声明体中所包含的枚举类型信息
1 int enumeratorOffset() const; 2 int enumeratorCount() const; 3 int indexOfEnumerator(const char *name) const; 4 QMetaEnum enumerator(int index) const;
5、method:描述类中所包含方法信息:包括property,signal,slot等,包括祖先类,如何组织暂时不确定。
1 int methodOffset() const; 2 int methodCount() const; 3 int indexOfMethod(const char *method) const; 4 int indexOfSignal(const char *signal) const; 5 int indexOfSlot(const char *slot) const; 6 QMetaMethod method(int index) const;
6、property:类型的属性信息
1 int propertyOffset() const; 2 int propertyCount() const; 3 int indexOfProperty(const char *name) const; 4 QMetaProperty property(int index) const; ////返回类中设置了USERflag的属性,(难道只能有一个这样的属性?)
注意:对于类里面定义的函数,构造函数,枚举,只有加上一些宏才表示你希望为方法提供meta信息。比如 Q_ENUMS用来注册宏,
Q_INVACABLE用来注册方法(包括构造函数)。Qt这么设计的原因应该是避免meta信息的臃肿。
下文来源: http://biancheng.dnbcw.info/linux/253557.html
如果一个类的声明中包含Q_OBJECT宏,那么qmake将为这个类生成meta信息,这个信息在前一篇中所提到的moc文件中。这一篇通过解析这个一个示例moc文件来阐述这些meta信息的存储方式和格式;本篇先说明了一下QMetaObject的数据结构,然后呈现了一个简单的类TestObject类及其生成的moc文件,最后对这个moc文件个内容进行了详细解释。
QMetaObject的数据定义:
QMetaObject包含唯一的数据成员如下(见头文件qobjectdefs.h)
1 struct Q_CORE_EXPORT QMetaObject 2 { 3 struct { // private data 4 const QMetaObject *superdata; //父类QMetaObject实例的指针 5 const char *stringdata; //一段字符串内存块,包含MetaObject信息之字符串信息 6 const uint *data; //一段二级制内存块,包含MetaObject信息之二进制信息 7 const void *extradata; //额外字段,暂未使用 8 } d; 9 };
QMetaObjectPrivate的数据定义:
QMetaObjectPrivate是QMetaObject的私有实现类,其数据定义部分如下(见头文件qmetaobject_p.h)。该数据结构全是int类型,一些是直接的int型信息,比如classInfoCount、
methodCount等,还有一些是用于在QMetaObject的stringdata和data内存块中定位信息的索引值。下文结合这两个内存块的结构再分析个字段的含义。
1 struct QMetaObjectPrivate 2 { 3 int revision; 4 int className; 5 int classInfoCount, classInfoData; 6 int methodCount, methodData; 7 int propertyCount, propertyData; 8 int enumeratorCount, enumeratorData; 9 int constructorCount, constructorData; //since revision 2 10 int flags; //since revision 3 11 int signalCount; //since revision 4 12 // revision 5 introduces changes in normalized signatures, no new members 13 // revision 6 added qt_static_metacall as a member of each Q_OBJECT and inside QMetaObject itself 14 };
下文利用一个示例QObject子类及其moc文件,来分析QMetaObject的信息结构。
示例类TestObject:
TestObject类继承自QObject,定义了两个Property:propertyA,propertyB;两个classinfo:Author,Version;一个枚举:TestEnum。
1 #include <QObject> 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 <QObject>." 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_cast< const QString(*)>(_a[1]))); break; 75 case 3: onEventB((*reinterpret_cast< int(*)>(_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的接口的实现正是基于这两块数据。下面就对这两个数据进行分块说明。
1 static const uint qt_meta_data_TestObject[] = { 2 3 数据块一: 4 // content: 5 4, // revision 6 0, // classname 7 8 2, 14, // classinfo 9 10 4, 18, // methods 11 12 2, 38, // properties 13 1, 44, // enums/sets 14 0, 0, // constructors 15 0, // flags 16 2, // signalCount 17 18 这块数据可以被看做meta信息的头部,正好和QMetaObjectPrivate数据结构相对应,在QMetaObject的实现中,正是将这块数据映射为QMetaObjectPrivate进行使用的。 19 20 第一行数据“4”:版本号; 21 22 第二行数据“0”:类型名,该值是qt_meta_stringdata_TestObject的索引,qt_meta_stringdata_TestObject[0]这个字符串不正是类型名“TestObject”吗。 23 24 第三行数据“2,14”,第一个表明有2个classinfo被定义,第二个是说具体的 classinfo信息在qt_meta_data_TestObject中的索引,qt_meta_data_TestObject[14]的位置两个 classinfo名值对的定义; 25 26 第四行数据“4,18”,指明method的信息,模式同上; 27 28 第五行数据“2,38”,指明property的信息,模式同上; 29 第六行数据“1,14”,指明enum的信息,模式同上。 30 31 数据块二: 32 // classinfo: key, value 33 22, 11, 34 44, 29, 35 36 classinfo信息块。第一行“22,11”,22表明 qt_meta_stringdata_TestObject[22]处定义的字符串是classinfo的key,11表明 qt_meta_stringdata_TestObject[11]处的字符串就是value。第二行“44,29”定义第二个classinfo。 37 38 数据块三: 39 // signals: signature, parameters, type, tag, flags 40 53, 52, 52, 52, 0x05, 41 63, 52, 52, 52, 0x05, 42 43 signal信息块。第一行“53, 52, 52, 52, 0x05”定义第一个signal clicked()。qt_meta_stringdata_TestObject[53]是signal名称字符串。parameters 52, type 52, tag 52, flags如何解释暂未知。 44 45 数据块四: 46 // slots: signature, parameters, type, tag, flags 47 73, 52, 52, 52, 0x0a, 48 91, 52, 52, 52, 0x0a, 49 50 slots信息,模式类似signal。 51 52 数据块五: 53 // properties: name, type, flags 54 113, 105, 0x0a095007, 55 123, 105, 0x0a095007, 56 57 property性信息,模式类signal和slots,105如何和type对应暂未知。 58 59 数据块六: 60 // enums: name, flags, count, data 61 133, 0x0, 2, 48, 62 // enum data: key, value 63 142, uint(TestObject::EnumValueA), 64 153, uint(TestObject::EnumValueB), 65 66 enum信息,第一行定义的是枚举名,flag,值的数目,data48不知是什么。 67 68 几行定义的是各枚举项的名称和值。名称同上都是qt_meta_stringdata_TestObject的索引值。 69 70 0 // eod 71 }; 72 73 static const char qt_meta_stringdata_TestObject[] = { 74 75 这块数据就是meta信息所需的字符串。是一个字符串的序列。 76 "TestObject\0Long Huihu\0Author\0" 77 "TestObjectV1.0\0Version\0\0clicked()\0" 78 "pressed()\0onEventA(QString)\0onEventB(int)\0" 79 "QString\0propertyA\0propertyB\0TestEnum\0" 80 "EnumValueA\0EnumValueB\0" 81 };
本篇从Qt MetaObject源代码解读相关接口的实现,这些接口都定义于qmetaobject.cpp中。
QMetaObject::className()
1 inline const char *QMetaObject::className() const 2 { return d.stringdata; }
d.stringdata就是那块字符串数据,包含若干c字符串(以‘\0‘)结尾。如果把d.stringdata当做一个c字符串指针的话,就是这个字符串序列的第一个字符串,正是类名。
QMetaObject::superClass()
1 inline const QMetaObject *QMetaObject::superClass() const 2 { return d.superdata; }
QMetaObject::classInfoCount()
1 int QMetaObject::classInfoCount() const 2 { 3 int n = priv(d.data)->classInfoCount; 4 const QMetaObject *m = d.superdata; 5 while (m) { 6 n += priv(m->d.data)->classInfoCount; 7 m = m->d.superdata; 8 } 9 return n; 10 }
从代码可以看出,返回该类的所有classinfo数目,包括所有基类的。
函数priv是一个简单inline函数:
1 static inline const QMetaObjectPrivate *priv(const uint* data) 2 { return reinterpret_cast<const QMetaObjectPrivate*>(data); }
d.data指向的是那块二进制信息,priv将d.data解释为QMetaObjectPrivate。
QMetaObjectPrivate是QMetaObject的私有实现类,其数据定义部分如下(见头文件qmetaobject_p.h)。和前一篇的示例moc文件内容一对应,其含义一目了然。
1 struct QMetaObjectPrivate 2 { 3 int revision; 4 int className; 5 int classInfoCount, classInfoData; 6 int methodCount, methodData; 7 int propertyCount, propertyData; 8 int enumeratorCount, enumeratorData; 9 int constructorCount, constructorData; //since revision 2 10 int flags; //since revision 3 11 int signalCount; //since revision 12 }
QMetaObject:: classInfoOffset ()
1 int QMetaObject::classInfoOffset() const 2 { 3 int offset = 0; 4 const QMetaObject *m = d.superdata; 5 while (m) { 6 offset += priv(m->d.data)->classInfoCount; 7 m = m->d.superdata; 8 } 9 return offset; 10 }
该类的含义是返回这个类所定义的classinfo的起始索引值,相当于它的祖先类所定义的classinfo的数量
QMetaObject:: classInfo (int index)
1 QMetaClassInfo QMetaObject::classInfo(int index) const 2 { 3 int i = index; 4 i -= classInfoOffset(); 5 if (i < 0 && d.superdata) 6 return d.superdata->classInfo(index); 7 8 QMetaClassInfo result; 9 if (i >= 0 && i < priv(d.data)->classInfoCount) { 10 result.mobj = this; 11 result.handle = priv(d.data)->classInfoData + 2*i; 12 } 13 return result; 14 }
1 class Q_CORE_EXPORT QMetaClassInfo 2 { 3 public: 4 inline QMetaClassInfo() : mobj(0),handle(0) {} 5 const char *name() const; 6 const char *value() const; 7 inline const QMetaObject *enclosingMetaObject() const { return mobj; } 8 private: 9 const QMetaObject *mobj; 10 uint handle; 11 friend struct QMetaObject; 12 };
这个代码的流程比较简单。priv(d.data)->classInfoData是classinfo的信息在d.data中的偏移;每条classinfo信息占2个UINT的大小,因此“priv(d.data)->classInfoData +
2*i”这个表达式的值就是第i个classinfo的信息在d.data中的偏移。
QMetaObject:: indexOfClassInfo ()
1 int QMetaObject::indexOfClassInfo(const char *name) const 2 { 3 int i = -1; 4 const QMetaObject *m = this; 5 while (m && i < 0) { 6 for (i = priv(m->d.data)->classInfoCount-1; i >= 0; --i) 7 if (strcmp(name, m->d.stringdata 8 + m->d.data[priv(m->d.data)->classInfoData + 2*i]) == 0) { 9 i += m->class以上是关于Qt MetaObject System详解的主要内容,如果未能解决你的问题,请参考以下文章
Qt文档阅读笔记-staticMetaObject解析与实例
Qt文档阅读笔记-staticMetaObject解析与实例
Qt--- 一些常见问题处理(无法解析得外部符号,UI头文件找不到等)
Python Qt GUI设计:QTableViewQListViewQListWidetQTableWidgetQTreeWidget和QTreeWidgetltem表格和树类(提升篇—1)(代码片