QT实现解析(1)

在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的公有继承所以可以进行类型转换。

你可能感兴趣的:(C++,qt)