什么是元对象? In computer science, a metaobject or meta-object is any entity that manipulates, creates, describes, or implements other objects. The object that the metaobject is about is called the base object. Some information that a metaobject might store is the base object's type, interface, class, methods, attributes, variables, functions, control structures, etc. 在计算机科学中,元对象是这样一个东西:它可以操纵,创建,描述,或执行其他对象。元对象描述的对象称为基对象。元对象可能存这样的信息:基础对象的类型,接口,类,方法,属性,变量,函数,控制结构等。 QT中的元对象系统 QT中的元对象系统基于以下三种东西: 1. QObject 提到这个类,相信大家都不陌生。几乎所有在QT开发的类都继承于此类。 QObject这个类为其他需要用到元对象系统的类提供了一个基类。 2.Q_OBJECT 放在类声明中的Q_OBJECT宏是用来为这个类开启元对象特性的,例如动态的属性(dynamic properties),信号(signals)以及槽(slots)。 可这个Q_OBJECT宏到底是什么呢?如下图所示,大家可以看到它是一些函数和一个静态的类成员。 Q_OBJECT宏内容如下: #define Q_OBJECT \ public: \ Q_OBJECT_CHECK \ static const QMetaObject staticMetaObject; \ Q_OBJECT_GETSTATICMETAOBJECT \ virtual const QMetaObject *metaObject() const; \ virtual void *qt_metacast(const char *); \ QT_TR_FUNCTIONS \ virtual int qt_metacall(QMetaObject::Call, int, void **); \ private: 而Q_OBJECT_CHECK宏的内容是: #define Q_OBJECT_CHECK \ template <typename T> inline void qt_check_for_QOBJECT_macro(const T &_q_argument) const \ { int i = qYouForgotTheQ_OBJECT_Macro(this, &_q_argument); i = i; } 这里是检查类声明中是否遗漏Q_OBJECT宏,如果遗漏则在编译时给出错误提示。 而QT_TR_FUNCTIONS宏的内容是: # define QT_TR_FUNCTIONS \ static inline QString tr(const char *s, const char *c = 0) \ { return staticMetaObject.tr(s, c); } \ static inline QString trUtf8(const char *s, const char *c = 0) \ { return staticMetaObject.trUtf8(s, c); } \ static inline QString tr(const char *s, const char *c, int n) \ { return staticMetaObject.tr(s, c, n); } \ static inline QString trUtf8(const char *s, const char *c, int n) \ { return staticMetaObject.trUtf8(s, c, n); } 一些字符串操作的算法,这几个方法实际上是隐藏了父类QObject中这个几个方法。我们在类定义中调用的tr或是trUtf8方法都是调用这里的方法。 3 MOC what does MOC do? MOC编译器为QObject子类提供了一些实现元对象特性所需的一些代码。就比如说信号,大家只是在类声明的时候声明了所需的信号,就像下面这样: 样当我们把鼠标放到Q_SINGNALS时可以看到下面 这个宏感觉有点奇怪,它要替换的是它自己。实际上它只是一个protected的关键字。 说到这里,大家应该会想到,哦,moc编译器会用protected来替换它。但moc编译器做的远过于此,它会用protected来替换它,但同时还会为这个信号声明方法体。 当moc工具读取一个c++源文件时,如果它发现类的声明中有Q_OBJECT时,它会产生另外一个c++源文件,这个文件中就包含了这些类的元对象代码。这个新产生的文件可以以#include形式包含到这个类的源文件中,但更常用的做法是和这个类的定义一起编译连接。 以QT Creator为例,以下代码MainWindow类是我创建工程时选择Mobile Qt Application时自动创建的一个类,其中我自己增加了3个信号3槽,代码如下所示: #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QString> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); public slots: void slotTest1(int value); void slotTest2(const QString value); void slotTest3(bool value); signals: void signalTest1(int value); void signalTest2(const QString value); void signalTest3(bool value); public: void test(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H #include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::slotTest1(int value) { } void MainWindow::slotTest2(const QString value) { } void MainWindow::slotTest3(bool value) { } void MainWindow::test() { connect(this, SIGNAL(signalTest1(int)), this, SLOT(slotTest1(int))); } 这个类很简单,但当moc读到mainwindow.h时发现Q_OBJECT时,它会在在这个工程目录下的moc目录下创建moc_mainwindow.cpp。让我们来看看这里面都有些什么: /**************************************************************************** ** Meta object code from reading C++ file 'mainwindow.h' ** ** Created: Thu Sep 2 16:23:24 2010 ** by: The Qt Meta Object Compiler version 62 (Qt 4.6.3) ** ** WARNING! All changes made in this file will be lost! *****************************************************************************/ #include "../mainwindow.h" #if !defined(Q_MOC_OUTPUT_REVISION) #error "The header file 'mainwindow.h' doesn't include <QObject>." #elif Q_MOC_OUTPUT_REVISION != 62 #error "This file was generated using the moc from 4.6.3. 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_MainWindow[] = { // content: 4, // revision 0, // classname 0, 0, // classinfo 6, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 3, // signalCount // signals: signature, parameters, type, tag, flags 18, 12, 11, 11, 0x05, 35, 12, 11, 11, 0x05, 56, 12, 11, 11, 0x05, // slots: signature, parameters, type, tag, flags 74, 12, 11, 11, 0x0a, 89, 12, 11, 11, 0x0a, 108, 12, 11, 11, 0x0a, 0 // eod }; static const char qt_meta_stringdata_MainWindow[] = { "MainWindow\0\0value\0signalTest1(int)\0" "signalTest2(QString)\0signalTest3(bool)\0" "slotTest1(int)\0slotTest2(QString)\0" "slotTest3(bool)\0" }; const QMetaObject MainWindow::staticMetaObject = { { &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow, qt_meta_data_MainWindow, 0 } }; #ifdef Q_NO_DATA_RELOCATION const QMetaObject &MainWindow::getStaticMetaObject() { return staticMetaObject; } #endif //Q_NO_DATA_RELOCATION const QMetaObject *MainWindow::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; } void *MainWindow::qt_metacast(const char *_clname) { if (!_clname) return 0; if (!strcmp(_clname, qt_meta_stringdata_MainWindow)) return static_cast<void*>(const_cast< MainWindow*>(this)); return QMainWindow::qt_metacast(_clname); } int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QMainWindow::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { switch (_id) { case 0: signalTest1((*reinterpret_cast< int(*)>(_a[1]))); break; case 1: signalTest2((*reinterpret_cast< const QString(*)>(_a[1]))); break; case 2: signalTest3((*reinterpret_cast< bool(*)>(_a[1]))); break; case 3: slotTest1((*reinterpret_cast< int(*)>(_a[1]))); break; case 4: slotTest2((*reinterpret_cast< const QString(*)>(_a[1]))); break; case 5: slotTest3((*reinterpret_cast< bool(*)>(_a[1]))); break; default: ; } _id -= 6; } return _id; } // SIGNAL 0 void MainWindow::signalTest1(int _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 0, _a); } // SIGNAL 1 void MainWindow::signalTest2(const QString _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 1, _a); } // SIGNAL 2 void MainWindow::signalTest3(bool _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 2, _a); } QT_END_MOC_NAMESPACE 看起来还真不少,没关系,让我们来一点一点分析。说到这里我先说一点废话,最近大家都在学习QT,但我发现大家掌握的都不够深入。如果你仅仅是学习一些类的使用时不够的,这对你技术的提高很不好,这是一个习惯,而且是个好习惯,如果你养成了这种习惯,你会发现你学东西会很快,技术当然也是突飞猛进。也会有人会说我也学了一些深层次的东西,就比如说我也学会了信号槽原理啦,我知道怎么用了。作为技术人员,如果你把你的技术高度定位在会用,你会发现你就在那个高度了。闲话少说,我们回到刚才那个话题上来。moc还真做的不少呢,不过很少有人意识到这点。让我们来看看它都做了些什么。 先看看的一些预处理,wow,不少的if error: #if !defined(Q_MOC_OUTPUT_REVISION) #error "The header file 'mainwindow.h' doesn't include <QObject>." #elif Q_MOC_OUTPUT_REVISION != 62 #error "This file was generated using the moc from 4.6.3. 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 在qglobal.h我们可以找到如下: # define QT_BEGIN_MOC_NAMESPACE QT_USE_NAMESPACE # define QT_USE_NAMESPACE using namespace :: QT_NAMESPACE; 所以QT_BEGIN_MOC_NAMESPACE就是using namespace :: QT_NAMESPACE;现在看起来是不是很熟悉啊,哈哈。 如下说完这个我们也一并把它的配对的一项QT_END_MOC_NAMESPACE也看一下: 在qglobal.h我们可以找到如下: # define QT_END_MOC_NAMESPACE 所以它就是一个空的,什么也没有,仅仅是为了配对,看起来比较好看吧。 接着是一个静态全局常量qt_meta_data_MainWindow: static const uint qt_meta_data_MainWindow[] = { // content: 4, // revision 0, // classname 0, 0, // classinfo 6, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 3, // signalCount // signals: signature, parameters, type, tag, flags 18, 12, 11, 11, 0x05, 35, 12, 11, 11, 0x05, 56, 12, 11, 11, 0x05, // slots: signature, parameters, type, tag, flags 74, 12, 11, 11, 0x0a, 89, 12, 11, 11, 0x0a, 108, 12, 11, 11, 0x0a, 0 // eod }; 你写的每个类,只要是从QObject继承,并且在类的声明中包含Q_OBJECT宏,moc都会为了定义一个(qt_meta_data_+类名)格式的静态变量。 继续,wow,又一个静态变量: static const char qt_meta_stringdata_MainWindow[] = { "MainWindow\0\0value\0signalTest1(int)\0" "signalTest2(QString)\0signalTest3(bool)\0" "slotTest1(int)\0slotTest2(QString)\0" "slotTest3(bool)\0" }; 很容易看出来这里放置的是类名和所有的信号槽。这个东西很重要,就是有了这个才可以通过索引到相应的信号和槽的,至于信号槽更深入的东西,信号槽是如何实现的,我们将在下面的内容中讲到。 继续,wow,还没有完呢,又一个全局静态变量: const QMetaObject MainWindow::staticMetaObject = { { &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow, qt_meta_data_MainWindow, 0 } }; 这个就是MainWindow的静态元对象了,看的出来是个结构体,包含了三项: 1. 它的父类的静态元对象。 2. 它的元字符串数据。 3. 它的元数据。 继续看,这下开始到方法了: #ifdef Q_NO_DATA_RELOCATION const QMetaObject &MainWindow::getStaticMetaObject() { return staticMetaObject; } #endif //Q_NO_DATA_RELOCATION 这个方法很简单,就是返回它自己的静态元对象。 const QMetaObject *MainWindow::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; } 很简单,如果它最顶层父类QObject类的d_ptr->metaObject不为空则返回最顶层父类QObject类的d_ptr->metaObject,否则返回staticMetaObject的指针。 继续, void *MainWindow::qt_metacast(const char *_clname) { if (!_clname) return 0; if (!strcmp(_clname, qt_meta_stringdata_MainWindow)) return static_cast<void*>(const_cast< MainWindow*>(this)); return QMainWindow::qt_metacast(_clname); } 看的出来这个方法就是控制类型转换的了,通过比较类的元字符串数据,如果相等则当前的this指针转换为空指针返回,否则继续向上找,如果最顶上的类的元字符串数据还是不相同则返回空指针。 继续, int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QMainWindow::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { switch (_id) { case 0: signalTest1((*reinterpret_cast< int(*)>(_a[1]))); break; case 1: signalTest2((*reinterpret_cast< const QString(*)>(_a[1]))); break; case 2: signalTest3((*reinterpret_cast< bool(*)>(_a[1]))); break; case 3: slotTest1((*reinterpret_cast< int(*)>(_a[1]))); break; case 4: slotTest2((*reinterpret_cast< const QString(*)>(_a[1]))); break; case 5: slotTest3((*reinterpret_cast< bool(*)>(_a[1]))); break; default: ; } _id -= 6; } return _id; } 现在到了QT里的元调用了,这时是 QT信号槽原理实现很重要的一步,这里_id就是信号槽对应的索引,可每个信号或是槽对应哪个索引呢,看看它的元字符串数据qt_meta_stringdata_MainWindow,我想你应该知道了。 继续,接着就是信号的实现了: // SIGNAL 0 void MainWindow::signalTest1(int _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 0, _a); } // SIGNAL 1 void MainWindow::signalTest2(const QString _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 1, _a); } // SIGNAL 2 void MainWindow::signalTest3(bool _t1) { void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 2, _a); } 这里就是moc为所有信号生成的方法了。 |