在QT程序设计当中,最重要的类别是QObject(定义于SRC\CORELIB\KERNEL\qobject.h),几乎所有的类别都是从这个类别继承来的。而这个类别也如MFC下面的CObject一样,承载了整个QT程序的运行,各个类别通过QObject组成一棵对象树,以便于所有没有被程序员处理的消息都能够得到默认的处理。
类同于MFC喜欢将很多东西用宏包装起来,QT也利用了类似的手段。在QObject类别当中,第一个宏便是Q_OBJECT。顺着代码搜索下去,这个宏的定义可以在编译的时候展开为如下所示。这个宏必须在私有区域添加,使得类别支持信号和槽函数。(Q_OBJECT的定义在SRC\CORELIB\KERNEL\qobjectdefs.h当中)
#define Q_OBJECT \ public: \ Q_OBJECT_CHECK \ static const QMetaObject staticMetaObject; \ virtual const QMetaObject *metaObject() const; \ virtual void *qt_metacast(const char *); \ virtual int qt_metacall(QMetaObject::Call, int, void **); \ private: \ static const QMetaObjectExtraData staticMetaObjectExtraData; \ static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
上面是整个Q_OBJECT的定义,在同一个文件当中搜索就可以看到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; } template <typename T> inline int qYouForgotTheQ_OBJECT_Macro(T, T) { return 0; } template <typename T1, typename T2> inline void qYouForgotTheQ_OBJECT_Macro(T1, T2) {}
利用偏特化实现定义,如果_q_argument和this是同一个类型,或者可以转型构造函数,那么就返回0,否则因为给i赋值为空,编译器会报警。后面的i=i是为了防止编译器报警,声明i却没有使用的警告。
其中三个虚拟函数都是由编译器实现,这个函数的实现可以在生成的.exe文件的地方看到有一个前面增加了moc_并且和你的类别同名的C++文件。上面三个函数都可以在这个文件里面看到。
其中d_ptr是一个智能指针,原型是QObjectData,根据QMetaObject是否为空返回QObject的QMetaObject或者返回自身的static QMetaObject。
const QMetaObject *FindDialog::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; }
接下来是qt_metacast函数则根据传入的参数名称进行比对,如果和我们定义的qt_meta_stringdata_FindDialog字符串的前面的字节相等则进行类型转换,否则传入到父类当中进行处理。其中qt_meta_stringdata_FindDialog字符串会在下面给出。
void *FindDialog::qt_metacast(const char *_clname) { if (!_clname) return 0; if (!strcmp(_clname, qt_meta_stringdata_FindDialog)) return static_cast<void*>(const_cast< FindDialog*>(this)); return QDialog::qt_metacast(_clname); }
接下来是qt_static_metacall,在这里消息进入到整个消息处理的神经末梢,调用相应的函数进行消息或者槽函数处理。
void FindDialog::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { FindDialog *_t = static_cast<FindDialog *>(_o); switch (_id) { case 0: _t->findNext((*reinterpret_cast< const QString(*)>(_a[1])),(*reinterpret_cast< Qt::CaseSensitivity(*)>(_a[2]))); break; case 1: _t->findPrevious((*reinterpret_cast< const QString(*)>(_a[1])),(*reinterpret_cast< Qt::CaseSensitivity(*)>(_a[2]))); break; case 2: _t->findClicked(); break; case 3: _t->enableFindButton((*reinterpret_cast< const QString(*)>(_a[1]))); break; default: ; } } }
因为一个类别当中的成员函数不占用内存地址,所以这里实际上是给QMetaObject内部的结构体d赋值,也就是这样就利用superdata将所有的类别信息给链接起来了。Stringdata包含类别所需要的一些信息,这些信息是以字符串形式给出的。Data用于索引上面所说的字符串,索引中的值可以根据struct QMetaObjectPrivate结构体做出解释。Extradata包含调用函数。
const QMetaObject FindDialog::staticMetaObject = { { &QDialog::staticMetaObject, qt_meta_stringdata_FindDialog, qt_meta_data_FindDialog, &staticMetaObjectExtraData } }; static const char qt_meta_stringdata_FindDialog[] = { "FindDialog\0\0str,cs\0" "findNext(QString,Qt::CaseSensitivity)\0" "findPrevious(QString,Qt::CaseSensitivity)\0" "findClicked()\0text\0enableFindButton(QString)\0" }; static const uint qt_meta_data_FindDialog[] = { 6, // revision 0, // classname 0, 0, // classinfo 4, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 2, // signalCount // signals: signature, parameters, type, tag, flags 19, 12, 11, 11, 0x05, 57, 12, 11, 11, 0x05, // slots: signature, parameters, type, tag, flags 99, 11, 11, 11, 0x08, 118, 113, 11, 11, 0x08, 0 // eod }; const QMetaObjectExtraData FindDialog::staticMetaObjectExtraData = { 0, qt_static_metacall };
qt_metacall函数进行消息分发,结合上面的消息处理函数可以看到,参数_id实际上是消息和槽函数的序号,由于上面是4个,所以这里减掉4,这个数目只和定义在signals和slots后面的函数数目无关而与是否connected相关。函数首先提交给父类处理,加入父类处理之后还存在相应的消息需要处理或者父类根本没处理,那么就由子类进行处理。另外这个函数实际上也参与Q_PROPERTY宏的实现,也就是说利用Q_PROPERTY宏实现的函数都由这个函数进行分发。
int FindDialog::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QDialog::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { if (_id < 4) qt_static_metacall(this, _c, _id, _a); _id -= 4; } return _id; }
Q_PROPERTY会在上面所说的索引和字符串当中添加相应的索引值和字符串,接下来是Q_DECLARE_PRIVATE。
template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; } template <typename Wrapper> static inline typename Wrapper::pointer qGetPtrHelper(const Wrapper &p) { return p.data(); } #define Q_DECLARE_PRIVATE(Class) \ inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \ inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \ friend class Class##Private;由于在QObject当中的定义不是const类型,所以根据偏特化选择第一个函数,不过由于 QObjectPrivate 是QObjectData的公有继承所以可以进行类型转换。