Qt moc文件的作用

来源:互联网 发布:福州淘宝美工在哪里学 编辑:程序博客网 时间:2024/05/21 19:24


前面我们说过,Qt 不是使用的“标准的” C++ 语言,而是对其进行了一定程度的“扩展”。这里我们从Qt新增加的关键字就可以看出来:signals、slots 或者 emit。所以有人会觉得 Qt 的程序编译速度慢,这主要是因为在 Qt 将源代码交给标准 C++ 编译器,如 gcc 之前,需要事先将这些扩展的语法去除掉。完成这一操作的就是 moc。

moc 全称是 Meta-Object Compiler,也就是“元对象编译器”。Qt 程序在交由标准编译器编译之前,先要使用 moc 分析 C++ 源文件。如果它发现在一个头文件中包含了宏 Q_OBJECT,则会生成另外一个 C++ 源文件。这个源文件中包含了 Q_OBJECT 宏的实现代码。这个新的文件名字将会是原文件名前面加上 moc_ 构成。这个新的文件同样将进入编译系统,最终被链接到二进制代码中去。因此我们可以知道,这个新的文件不是“替换”掉旧的文件,而是与原文件一起参与编译。另外,我们还可以看出一点,moc 的执行是在预处理器之前。因为预处理器执行之后,Q_OBJECT 宏就不存在了。

既然每个源文件都需要 moc 去处理,那么我们在什么时候调用了它呢?实际上,如果你使用 qmake 的话,这一步调用会在生成的 makefile 中展现出来。从本质上来说,qmake 不过是一个 makefile 生成器,因此,最终执行还是通过 make 完成的。

为了查看 moc 生成的文件,我们使用一个很简单的 cpp 来测试:

test.cpp

  1. class Test : public QObject 
  2.  Q_OBJECT 
  3. public
  4.  explicit Test(QObject *parent = 0); 
  5. signals: 
  6. public slots: 
  7. }; 

这是一个空白的类,什么都没有实现。在经过编译之后,我们会在输出文件夹中找到 moc_test.cpp:

moc_test.cpp

  1. /**************************************************************************** 
  2. ** Meta object code from reading C++ file 'test.h' 
  3. ** 
  4. ** Created: Thu Jul 22 13:06:45 2010 
  5. **      by: The Qt Meta Object Compiler version 62 (Qt 4.6.3) 
  6. ** 
  7. ** WARNING! All changes made in this file will be lost! 
  8. *****************************************************************************/ 
  9.  
  10. #include "../test.h" 
  11. #if !defined(Q_MOC_OUTPUT_REVISION) 
  12. #error "The header file 'test.h' doesn't include <QObject>." 
  13. #elif Q_MOC_OUTPUT_REVISION != 62 
  14. #error "This file was generated using the moc from 4.6.3. It" 
  15. #error "cannot be used with the include files from this version of Qt." 
  16. #error "(The moc has changed too much.)" 
  17. #endif 
  18.  
  19. QT_BEGIN_MOC_NAMESPACE 
  20. static const uint qt_meta_data_Test[] = { 
  21.  
  22.  // content: 
  23.        4,       // revision 
  24.        0,       // classname 
  25.        0,    0, // classinfo 
  26.        0,    0, // methods 
  27.        0,    0, // properties 
  28.        0,    0, // enums/sets 
  29.        0,    0, // constructors 
  30.        0,       // flags 
  31.        0,       // signalCount 
  32.  
  33.        0        // eod 
  34. }; 
  35.  
  36. static const char qt_meta_stringdata_Test[] = { 
  37.     "Test\0" 
  38. }; 
  39.  
  40. const QMetaObject Test::staticMetaObject = { 
  41.     { &QObject::staticMetaObject, qt_meta_stringdata_Test, 
  42.       qt_meta_data_Test, 0 } 
  43. }; 
  44.  
  45. #ifdef Q_NO_DATA_RELOCATION 
  46. const QMetaObject &Test::getStaticMetaObject() { return staticMetaObject; } 
  47. #endif //Q_NO_DATA_RELOCATION 
  48.  
  49. const QMetaObject *Test::metaObject() const 
  50.     return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; 
  51.  
  52. void *Test::qt_metacast(const char *_clname) 
  53.     if (!_clname) return 0; 
  54.     if (!strcmp(_clname, qt_meta_stringdata_Test)) 
  55.         return static_cast<void*>(const_cast< Test*>(this)); 
  56.     return QObject::qt_metacast(_clname); 
  57.  
  58. int Test::qt_metacall(QMetaObject::Call _c, int _id, void **_a) 
  59.     _id = QObject::qt_metacall(_c, _id, _a); 
  60.     if (_id < 0) 
  61.         return _id; 
  62.     return _id; 
  63. QT_END_MOC_NAMESPACE 

可以看到,moc_test.cpp 里面为 Test 类增加了很多函数。然而,我们并没有实际写出这些函数,它是怎么加入类的呢?别忘了,我们还有 Q_OBJECT 这个宏呢!在 qobjectdefs.h 里面,找到 Q_OBJECT 宏的定义:

  1. #define Q_OBJECT \ 
  2. public: \ 
  3.     Q_OBJECT_CHECK \ 
  4.     static const QMetaObject staticMetaObject; \ 
  5.     Q_OBJECT_GETSTATICMETAOBJECT \ 
  6.     virtual const QMetaObject *metaObject() const; \ 
  7.     virtual void *qt_metacast(const char *); \ 
  8.     QT_TR_FUNCTIONS \ 
  9.     virtual int qt_metacall(QMetaObject::Call, intvoid **); \ 
  10. private

这下了解了:正是对 Q_OBJECT 宏的展开,使我们的 Test 类拥有了这些多出来的属性和函数。注意,QT_TR_FUNCTIONS 这个宏也是在这里定义的。也就是说,如果你要使用 tr() 国际化,就必须使用 Q_OBJECT 宏,否则是没有 tr() 函数的。这期间最重要的就是 virtual const QMetaObject *metaObject() const; 函数。这个函数返回 QMetaObject 元对象类的实例,通过它,你就获得了 Qt 类的反射的能力:获取本对象的类型之类,而这一切,都不需要 C++ 编译器的 RTTI 支持。Qt 也提供了一个类似 C++ 的 dynamic_cast() 的函数 qobject_case(),而这一函数的实现也不需要 RTTI。另外,一个没有定义 Q_OBJECT 宏的类与它最接近的父类是同一类型的。也就是说,如果 A 继承了 QObject 并且定义了 Q_OBJECT,B 继承了 A 但没有定义 Q_OBJECT,C 继承了 B,则 C 的 QMetaObject::className() 函数将返回 A,而不是本身的名字。因此,为了避免这一问题,所有继承了 QObject 的类都应该定义 Q_OBJECT 宏,不管你是不是使用信号槽。


以上内容转载自:http://devbean.blog.51cto.com/448512/355100


首先看一下简单含有的signal, slot代码

view plain
  1. class myClass : public QObject  
  2. {  
  3.     Q_OBJECT  
  4. public:  
  5.     myClass();  
  6.     ~myClass();  
  7.     void trigger();  
  8.     void trigger2();  
  9. signals:  
  10.         void signalFunc(double);  
  11.         int signalFunc2(charint);  
  12. protected slots:  
  13.         void slotFunc(double);  
  14.         int slotFunc2(char);  
  15. };  
  16. // 只是实例代码,用来生成moc_myClass  
  17. #include"myClass.h"  
  18. #include<iostream>  
  19. using std::cout;  
  20. using std::endl;  
  21. myClass::myClass()  
  22. {  
  23.     connect(this, SIGNAL(signalFunc),  
  24.             this, SLOT(slotFunc));  
  25.     connect(this, SIGNAL(signalFunc2),  
  26.             this, SLOT(slotFunc2));  
  27. }  
  28. myClass::~myClass()  
  29. {  
  30. }  
  31. void myClass::slotFunc(double d)  
  32. {  
  33.     cout << "slotFunc" << endl;  
  34. }  
  35. int myClass::slotFunc2(char c)  
  36. {  
  37.     cout << "slotFunc2" << endl;  
  38.     return c;  
  39. }  
  40. void myClass::trigger()  
  41. {  
  42. }  
  43. void myClass::trigger2()  
  44. {  
  45. }   

编译生成moc_myClass.cpp

view plain
  1. static const uint qt_meta_data_myClass[] = {  
  2.  // content:  
  3.        4,       // revision  
  4.        0,       // classname  
  5.        0,    0, // classinfo  
  6.        4,   14, // methods  
  7.        0,    0, // properties  
  8.        0,    0, // enums/sets  
  9.        0,    0, // constructors  
  10.        0,       // flags  
  11.        2,       // signalCount  
  12.  // signals: signature, parameters, type, tag, flags  
  13.        9,    8,    8,    8, 0x05,  
  14.       34,   32,   28,    8, 0x05,  
  15.  // slots: signature, parameters, type, tag, flags  
  16.       56,    8,    8,    8, 0x09,  
  17.       73,    8,   28,    8, 0x09,  
  18.        0        // eod  
  19. };  
 

其中methods部分,4代表这个对象含有4个signal + slot, 14是基础数字,在moc代码里面也是硬编码,数一下content的个数,刚好是14,这个14其实就是个偏移量,偏移到signal的第一行

signal和slot的flag提供了一些属性,这个会在后面的QObject::connect讲到。

这里需要提出来一点是signal和slot的第一个数值,9, 34, 56, 73这几个,这个马上会讲到先提出来,留个印象


这里前14个数值是对应的QMetaObjectPrivate的值

view plain
  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.     ...  
  13.     ...   
  14. }  
 



接下来是

view plain
  1. static const char qt_meta_stringdata_myClass[] = {  
  2.     "myClass/0/0signalFunc(double)/0int/0,/0"  
  3.     "signalFunc2(char,int)/0slotFunc(double)/0"  
  4.     "slotFunc2(char)/0"  
  5. };  
 

从这个字符串里面,可以看到第一个值为这个类的类名(元对象可以不通过RTTI给出类名的原因就在这里)

在第一个/0后面会给出第一个signal的返回值类型,在这个例子中signalFunc没有返回值,所以没有任何内容,

在第二个/0后面会给出参数名,因为moc读取的是头文件,而我们在头文件中没定义参数名,所以为空

然后就是signal的名字和参数列表类型,这里需要注意的是,即使头文件给出了参数名,在这里也会被忽略掉,只提供类型

再下一个/0后面就是下一个函数的描述了,描述的方式跟前面是一样的。

刚刚提到的9, 34, 56, 73这几个数字,在这里是有用的,这几个数字,刚好是这个字符串对应的函数开头的部分。比如9,那我们在这个字符串中数9个字符,即signalFunc(double)这一段内容。


然后是源对象的数据定义:

view plain
  1. const QMetaObject myClass::staticMetaObject = {  
  2.     { &QObject::staticMetaObject, qt_meta_stringdata_myClass,  
  3.       qt_meta_data_myClass, 0 }  
  4. };  
  5. 可以看到源对象的数据定义为:  
  6.     struct { // private data  
  7.         const QMetaObject *superdata;  
  8.         const char *stringdata;  
  9.         const uint *data;  
  10.         const void *extradata;  
  11.     }  
 

这个名为staticMetaObject的对象是由Q_OBJECT引入的

第一个数据为父类名字, moc对于多继承的限制可能也在于此。

moc规定多继承的情况下,moc会假设第一个继承的类为QObject, 并且必须要保证在多继承中,只有唯一一个类是继承自QObject的。这样看上去,多余一个QObject继承的,第二个QObject根本没办法识别出来。

第二个数据就是上面的字符串数据

第三个也是上面的uint*数据。

这个源对象非常关键,后面的内容就靠他了。

view plain
  1. const QMetaObject &myClass::getStaticMetaObject() { return staticMetaObject; }  
  2. const QMetaObject *myClass::metaObject() const  
  3. {  
  4.     return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;  
  5. }  

这2个方法都是由Q_OBJECT引入的。目的是返回这个类的元对象

view plain
  1. void *myClass::qt_metacast(const char *_clname)  
  2. {  
  3.     if (!_clname) return 0;  
  4.     if (!strcmp(_clname, qt_meta_stringdata_myClass))  
  5.         return static_cast<void*>(const_cast< myClass*>(this));  
  6.     return QObject::qt_metacast(_clname);  
  7. }  

还是由Q_OBJECT引入的


当传入的字符串数据是当前这个类的话,就将this指针转换成void指针传给外界: 这个操作相当危险。

如果不是当前类的话,就调用父类的qt_metacast继续查询。

在这里,我的父类是QObject,所以自然就调用QObject::qt_metacase了


在看qt_metacall之前,先看下signal的定义。 额。。事实上signal不需要你自己定义,moc已经帮我们完成这点了。

具体内容如下:

view plain
  1. // SIGNAL 0  
  2. void myClass::signalFunc(double _t1)  
  3. {  
  4.     void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };  
  5.     QMetaObject::activate(this, &staticMetaObject, 0, _a);  
  6. }  
 

我们看到他将所有的参数都转型成void*指针保存到 void *a的数组中,然后调用了

QMetaObject::activate(this, &staticMetaObject, 0, _a);

看到传入的参数为this, 源对象和参数

这个函数实际上就是触发消息的函数,在这里不过多关注他,有机会再写


最终,int myClass::qt_metacall(QMetaObject::Call _c, int _id, void **_a)

会被调用,用来处理对应的signal的消息

代码如下

view plain
  1. int myClass::qt_metacall(QMetaObject::Call _c, int _id, void **_a)  
  2. {  
  3.     _id = QObject::qt_metacall(_c, _id, _a);  
  4.     if (_id < 0)  
  5.         return _id;  
  6.     if (_c == QMetaObject::InvokeMetaMethod) {  
  7.         switch (_id) {  
  8.         case 0: signalFunc((*reinterpret_castdouble(*)>(_a[1]))); break;  
  9.         case 1: { int _r = signalFunc2((*reinterpret_castchar(*)>(_a[1])),(*reinterpret_castint(*)>(_a[2])));  
  10.             if (_a[0]) *reinterpret_castint*>(_a[0]) = _r; }  break;  
  11.         case 2: slotFunc((*reinterpret_castdouble(*)>(_a[1]))); break;  
  12.         case 3: { int _r = slotFunc2((*reinterpret_castchar(*)>(_a[1])));  
  13.             if (_a[0]) *reinterpret_castint*>(_a[0]) = _r; }  break;  
  14.         default: ;  
  15.         }  
  16.         _id -= 4;  
  17.     }  
  18.     return _id;  
  19. }  
 

这里可以注意下,有返回值和没有返回值的处理方法~

moc所作的这些工作,都是为了元对象能更好的工作而做的准备

以上内容转载自:http://blog.csdn.net/liuysheng/article/details/6822718
0 0