Qt 如何实现的 Meta Object

Qt 如何实现的 Meta Object

  741人阅读  评论(0)  收藏  举报
qt signal callback object class table

(文章转贴自guiliblearning.blogspot.com) 

    前面提到了 Qt 实现的一个关键性技术,signal/slot。由于 Qt 使用了一个 moc 预处理,因此我们肯定会想知道 Qt 如何通过 moc 实现前面所说对于程序员的优点的呢?我们先通过一个简单的例子看看如何使用 QObject 这一些概念实现最基本的调用过程。然后我们探讨 Qt 实现这种扩展的方式是什么,后面因为要与 Gtkmm 的实现互相比较,而 Gtkmm 使用的是 libsigc++ 实现的对象通信机制,而且不使用预编译,因此可以发现很多有意思的东西。 下面这个例子声明了一个简单的 QObject 的子类,counter.hpp 文件

view plain copy to clipboard print ?
  1. #ifndef COUNTER_HPP  
  2. #define COUNTER_HPP  
  3. #include   
  4. #include   
  5.   
  6. class Counter : public QObject {  
  7. Q_OBJECT  
  8. int value ;  
  9. signals:  
  10. void valueChanged( int ) ;  
  11. public slots:  
  12. void setValue( int ) ;  
  13. public:  
  14. Counter( int = 0 ) ;  
  15.   
  16. friend std::ostream& operator<< ( std::ostream&, const Counter& ) ;  
  17. } ;  
  18. #endif  
我们可以看见我们需要通过继承 QObject 类,并且在 private 段声明 Q_OBJECT,signal 和 slots 其实对应的可以理解为 signal 是一个 callback 函数的链表(因为可以一个 signal 激发多个 slots),而 slots 就是常意的函数,因此也分 public/protected/private,可以继承、可以重载。下面是以上 Counter 类的 实现,counter.cpp 文件
view plain copy to clipboard print ?
  1. #include   
  2. #include "counter.hpp"  
  3.   
  4. void  
  5. Counter::setValue( int val )  
  6. {  
  7. value = val ;  
  8. // emit the signal  
  9. emit valueChanged( value ) ;  
  10. }  
  11.   
  12. Counter::Counter( int val ) : value( val ) {}  
  13.   
  14. std::ostream&  
  15. operator<< ( std::ostream& os, const Counter& c )  
  16. {  
  17. return os << c.value ;  
  18. }  
然后下面是调用的主程序 main.cpp 文件,
view plain copy to clipboard print ?
  1. #include   
  2. #include   
  3. #include "counter.hpp"  
  4.   
  5. using namespace std ;  
  6.   
  7. int  
  8. main( int argc, char *argv[] )  
  9. {  
  10. Counter a, b( 10 ) ;  
  11.   
  12. cout << "We have two counters, " << a  
  13.      << " and " << b << endl ;  
  14.   
  15. QObject::connect( &a, SIGNAL( valueChanged(int) ),  
  16.                   &b, SLOT( setValue(int) ) ) ;  
  17.   
  18. a.setValue( 12 ) ;  
  19. cout << "We have two counters, " << a  
  20.      << " and " << b << endl ;  
  21.   
  22. b.setValue( 5 ) ;  
  23. cout << "We have two counters, " << a  
  24.      << " and " << b << endl ;  
  25.   
  26. return 0 ;  
  27. }  
我们需要首先在该目录中使用 qmake -project 生成一个 .pro 文件,该文件含有工程细节,然后使用 qmake 产生 Makefile,最后 make 就可以产生可执行文件了。我们看到在主程序中调用 QObject::connect 将一个 signal 和一个 slot 连接,这导致我们触发 a.setValue() 的时候 b 的相关函数也被调用了。 那么我们继续看看 make 之后出现了什么。除了目标代码和执行文件以外,还有一个 moc_counter.cpp,这是使用 moc 产生的一个中间文件,稍微研究该文件,我们就不难发现  Qt 实现这一过程的要点: 继承 QObject 是为了使用 QObject 里面定义的信号方面的函数,如 QObject::connect() 和 QObject::disconnect()、判定继承 QObject::inherits()、支持国际化 QObject::tr() 和 QObject::trUtf8()、属性 QObject::setProperty() 和 QObject::property(),而 Q_OBJECT 宏(定义在 [include]/qt4/QtCore/qobjectdefs.h)是为了在原来这个类中重新插入一个 const static 的 QMetaObject,这个将覆盖父类对应的 QMetaObject,该 QMetaObject 里面含有 实现比 C++ 的 RTTI 更丰富功能以及 qobject_cast 等等的一个对象,因为该对象创建后不需要修改,整个 class 公用,所以是 const static 的,那个 moc_*.cpp 里面实际上就含有该 MetaObject 的初始化代码,编译的时候和自己 实现的 .o 一起编译,最后连接的时候加入到可执行文件里面。 这个 MetaObject 的初始化 code 是
view plain copy to clipboard print ?
  1. const QMetaObject Counter::staticMetaObject = {  
  2. { &QObject::staticMetaObject, qt_meta_stringdata_Counter,  
  3.   qt_meta_data_Counter, 0 }  
  4. };  
其中 string 的部分就是 slot 等,其实是以字符串形式存储的,当使用 SLOT/SIGNAL 宏的时候,应该是通过查寻字符串(这两个 macro 其实把代码转换成字符串传递给对应的函数)。 在 [include]/qt4/QtCore/qobjectdefs.h 里面有 MetaObject 类的定义,很多 QObject 的函数都是依赖这里面的函数的,比如 QMetaObject::connect()、QMetaObject::className()、QMetaObject::superClass() 等。通过预处理,同时把 signals 替换为 protected 而把 * slots 的 slots 去掉。我们可以通过 g++ -E 获得预处理后的源文件,我们会发现 signal 对应的 protected 部分没有 实现代码,但是在生成的执行代码中却有,这部分难道是 template 产生的?

(文章转贴自guiliblearning.blogspot.com) 

 首先我们看看 Q_OBJECT 展开变成了什么,在 qobjectdefs.h 文件中有

view plain copy to clipboard print ?
  1. #define Q_OBJECT /  
  2. public: /  
  3.  Q_OBJECT_CHECK /  
  4.  static const QMetaObject staticMetaObject; /  
  5.  virtual const QMetaObject *metaObject() const; /  
  6.  virtual void *qt_metacast(const char *); /  
  7.  QT_TR_FUNCTIONS /  
  8.  virtual int qt_metacall(QMetaObject::Call, intvoid **); /  
  9. private:  

首先调用了 Q_OBJECT_CHECK (插入了一个 qt_check_for_QOBJECT_macro 的 template function),然后是全局常量 QMetaObject 对象,因此可以用 QClassname::staticMetaObject 直接访问,另外提供了两个接口函数 metaObject() 用于不同的 class 返回自己的 staticMetaObject、qt_metacast() 用于转换,我们在 moc 产生的文件里面可以找到这两个接口的实现,

view plain copy to clipboard print ?
  1. const QMetaObject *Counter::metaObject() const  
  2. {  
  3.  return &staticMetaObject;  
  4. }  
  5.   
  6. void *Counter::qt_metacast(const char *_clname)  
  7. {  
  8.  if (!_clname) return 0;  
  9.  if (!strcmp(_clname, qt_meta_stringdata_Counter))  
  10.    return static_cast<void*>(const_cast< Counter*>(this));  
  11.  return QObject::qt_metacast(_clname);  
  12. }  

后者很明显,如果需要转换的名字 _clname 是自己的类名,就把自己的指针通过转换成 void* 传回去,否则调用 QOject::qt_metacast(),其实就是看是不是 QObject 了,否则就返回 0 了。另外 QT_TR_FUNCTIONS 是对应的 i18n 的函数,我们后面再看。最后还有一个 qt_metacall 的接口,实现如下

view plain copy to clipboard print ?
  1. int Counter::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:  
  9.      valueChanged((*reinterpret_castint(*)>(_a[1])));  
  10.      break;  
  11.    case 1:  
  12.      setValue((*reinterpret_castint(*)>(_a[1])));  
  13.      break;  
  14.    default: ;  
  15.    }  
  16.    _id -= 2;  
  17.  }  
  18.  return _id;  
  19. }  

这个函数起到一个中间作用,可以间接的调用成员方法,我们来仔细看看 QMetaObject,同一个文件里面有该结构的定义,我们只看这一部分,

view plain copy to clipboard print ?
  1. struct Q_CORE_EXPORT QMetaObject  
  2. {  
  3.  const char *className() const;  
  4.  const QMetaObject *superClass() const;  
  5.   
  6.  QObject *cast(QObject *obj) const;  
  7.   
  8.  // ...  
  9.   
  10.  struct { // private data  
  11.    const QMetaObject *superdata;  
  12.    const char *stringdata;  
  13.    const uint *data;  
  14.    const void *extradata;  
  15.  } d;  
  16. } ;  

注意 Couter 类 QMetaObject 的初始化,

view plain copy to clipboard print ?
  1. const QMetaObject Counter::staticMetaObject = {  
  2.  { &QObject::staticMetaObject, qt_meta_stringdata_Counter,  
  3.   qt_meta_data_Counter, 0 }  
  4. } ;  

下面我们着重看看几个与 signal/slot 相关的代码,首先就是 [qt]/src/corelib/kernel/qobject.cpp 文件中关于 QObject::connect() 函数的代码,

view plain copy to clipboard print ?
  1. bool QObject::connect(const QObject *sender, const char *signal,  
  2.                   const QObject *receiver, const char *method,  
  3.                   Qt::ConnectionType type)  
  4. {  
  5.  {  
  6.    const void *cbdata[] = { sender, signal, receiver, method, &type };  
  7.    if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))  
  8.      return true;  
  9.  }  
  10.   
  11.  // checking sender, receiver, compatability of signal and slot  
  12.   
  13.  QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);  
  14.  const_cast(sender)->connectNotify(signal - 1);  
  15.  return true;  
  16. }  

这里首先调用了 QInternal 这个 namespace 里面 activateCallbacks 这个函数,然后根据 QMetaObject 信息检查了 sender、receiver 以及对应 signal/slots 的匹配性,此时已经把 signal/slot 字符串转换成为了对应的 index,然后调用 QMetaObject::connect() 完成连接,最后的 QObject::connectNotify() 以及另外的 QObject::disconnectNotify() 其实是一个 signal。QInternal::activaeCallback() 在 [qt]/src/corelib/global/qglobal.cpp 中定义,

view plain copy to clipboard print ?
  1. bool QInternal::activateCallbacks(Callback cb, void **parameters)  
  2. {  
  3.  Q_ASSERT_X(cb >= 0, "QInternal::activateCallback()""Callback id must be a valid id");  
  4.   
  5.  QInternal_CallBackTable *cbt = global_callback_table();  
  6.  if (cbt && cb < cbt->callbacks.size()) {  
  7.    QList callbacks = cbt->callbacks[cb];  
  8.    bool ret = false;  
  9.    for (int i=0; i
  10.      ret |= (callbacks.at(i))(parameters);  
  11.    return ret;  
  12.  }  
  13.  return false;  
  14. }  

这可以看出来调用该函数去检查一个 global_callback_table(),如果查到一个匹配的 signal/slot 就返回 true,否则返回 false,换言之 QObject::connect() 通过这个函数判定需不需要调用 QMetaObject::connect 创建新的连接。这个 callback table 本质是什么呢?同一个文件里面有

view plain copy to clipboard print ?
  1. struct QInternal_CallBackTable {  
  2. QVector > callbacks;  
  3. };  

所以,这是一个链表的动态数组(Orz... 本来以为就是个链表),而 qInternalCallback 是一个在 [qt]/src/corelib/global/qnamespace.h 中定义的函数指针,

view plain copy to clipboard print ?
  1. typedef bool (*qInternalCallback)(void **);  

现在我们可以猜测到在 QMetaObject::connect() 调用中我们会维护这个 callback table。在 [qt]/src/corelib/kernel/qmetaobject.cpp 我们有幸找到了如下代码:

view plain copy to clipboard print ?
  1. bool QMetaObject::connect(const QObject *sender, int signal_index,  
  2.                       const QObject *receiver, int method_index, int type, int *types)  
  3. {  
  4.  QObject *s = const_cast
  5.  QObject *r = const_cast
  6.   
  7.  QOrderedMutexLocker locker(&s->d_func()->threadData->mutex,  
  8.                             &r->d_func()->threadData->mutex);  
  9.   
  10. #if defined(Q_CC_HPACC) && defined(QT_ARCH_PARISC)  
  11.  QObjectPrivate::Connection c;  
  12.  c.receiver = r;  
  13.  c.method = method_index;  
  14.  c.connectionType = type;  
  15.  c.argumentTypes = types;  
  16. #else  
  17.  QObjectPrivate::Connection c = { r, method_index, type, Q_BASIC_ATOMIC_INITIALIZER(types) };  
  18. #endif  
  19.  s->d_func()->addConnection(signal_index, &c);  
  20.  r->d_func()->refSender(s, signal_index);  
  21.   
  22.  if (signal_index < 0)  
  23.    sender->d_func()->connectedSignals = ~0u;  
  24.  else if (signal_index < 32)  
  25.    sender->d_func()->connectedSignals |= (1 << signal_index);  
  26.   
  27.  return true;  
  28. }  

这段代码中使用了防止多线程操作引起问题的 mutex,sender 和 receiver 双方都有一个结构来保证这个通讯机制,sender 是通过 addConnection,receiver 通过 refSender(),这里并没有我们猜测的 global callback table。现在我们有两个问题,一个是 sender 通过 addConnection() 和 receiver 通过 refSneder() 记录了一些什么,用什么数据结构储存,另一个是 global callback table 是做什么用的。

我们先来看看 global callback table,不难发现,该 table 是 QInternal 类(没有成员,提供了一个接口)的方法维护的,主要有 QInternal::registerCallback()、QInternal::unregisterCallback()、QInternal::activateCallbacks()、QInternal::callFunction(),意思我想都很清楚,可是 grep 了一圈,似乎只在某些调试部分看到了调用该函数的地方,莫非这是用来调试的代码?

我们来看看 QObject->d_func() 返回的是什么。

view plain copy to clipboard print ?
  1. #define Q_DECLARE_PRIVATE(Class) /  
  2.   inline Class##Private* d_func() { return reinterpret_cast(d_ptr); } /  
  3.   inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } /  
  4.   friend class Class##Private;  
  5.   
  6. #define Q_DECLARE_PRIVATE_D(Dptr, Class) /  
  7.   inline Class##Private* d_func() { return reinterpret_cast(Dptr); } /  
  8.   inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(Dptr); } /  
  9.   friend class Class##Private;  
  10.   
  11. #define Q_DECLARE_PUBLIC(Class)                                    /  
  12.   inline Class* q_func() { return static_cast(q_ptr); } /  
  13.   inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } /  
  14.   friend class Class;  
  15.   
  16. #define Q_D(Class) Class##Private * const d = d_func()  
  17. #define Q_Q(Class) Class * const q = q_func()  

记得 QObject 的定义么?

view plain copy to clipboard print ?
  1. class Q_CORE_EXPORT QObject  
  2. {  
  3.   Q_OBJECT  
  4.   Q_PROPERTY(QString objectName READ objectName WRITE setObjectName)  
  5.   Q_DECLARE_PRIVATE(QObject)  

这样 QObject 通过 d_func() 返回 d_ptr 这个指针,这是怎么一回事呢?我们知道这个宏在 QObject 内部定义了一个 QObjectPrivate 的类,并且留下了一个指针

view plain copy to clipboard print ?
  1. protected:  
  2.    QObjectData *d_ptr;  

在 QObject 构造的时候创建了这个对象,并在析构的时候释放,由于是 friend class,可以对 QObject 无限制访问,

view plain copy to clipboard print ?
  1. QObject::QObject(QObject *parent) : d_ptr(new QObjectPrivate)  
  2. QObject::~QObject()  
  3. {  
  4.    Q_D(QObject);  
  5.    // clear all signal receivers  
  6.    emit destroyed(this);  
  7.    // clear all signal senders  
  8.    delete d;  
  9.    d_ptr = 0;  
  10. }  

可见真正管理 signal/slots 的是 QObjectPrivate 类,下面是它的主要成员函数,

view plain copy to clipboard print ?
  1. //constructor and destructor  
  2. QObjectPrivate::QObjectPrivate(int version) ;  
  3. QObjectPrivate::~QObjectPrivate() ;  
  4. int *QObjectPrivate::setDeleteWatch(QObjectPrivate *d, int *w) ;  
  5. void QObjectPrivate::resetDeleteWatch(QObjectPrivate *d, int *oldWatch, int deleteWatch) ;  
  6. void QObjectPrivate::sendPendingChildInsertedEvents() ;  
  7. void QObjectPrivate::removePendingChildInsertedEvents(QObject *child) ;  
  8. bool QObjectPrivate::isSender(const QObject *receiver, const char *signal) const ;  
  9. QObjectList QObjectPrivate::receiverList(const char *signal) const ;  
  10. QObjectList QObjectPrivate::senderList() ;  
  11. // connection list manipulation  
  12. void QObjectPrivate::addConnection(int signal, Connection *c) ;  
  13. void QObjectPrivate::removeReceiver(int signal, QObject *receiver) ;  
  14. void QObjectPrivate::cleanConnectionLists() ;  
  15. // sender list manipulation  
  16. void QObjectPrivate::refSender(QObject *sender, int signal) ;  
  17. void QObjectPrivate::derefSender(QObject *sender, int signal) ;  
  18. void QObjectPrivate::removeSender(QObject *sender, int signal) ;  
  19. QObjectPrivate::Sender *QObjectPrivate::setCurrentSender(QObject *receiver, Sender *sender) ;  
  20. void QObjectPrivate::resetCurrentSender(QObject *receiver, Sender *currentSender, Sender *previousSender) ;  
  21. void QObjectPrivate::clearGuards(QObject *object) ;  

那么我们如何存储数据的呢?在 [qt]/src/corelib/kernel/qobject_p.h 里,有该类的声明,

view plain copy to clipboard print ?
  1. class Q_CORE_EXPORT QObjectPrivate : public QObjectData {  
  2.  // ...  
  3. public:  
  4.  QList pendingChildInsertedEvents;  
  5.  struct Sender {  
  6.    QObject *sender;  
  7.    int signal;  
  8.    int ref;  
  9.  };  
  10.  Sender *currentSender;  
  11.  QList > eventFilters;  
  12.   
  13.  struct Connection {  
  14.    QObject *receiver;  
  15.    int method;  
  16.    uint connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking  
  17.    QBasicAtomicPointer<int> argumentTypes;  
  18.  };  
  19.  typedef QList ConnectionList;  
  20.  QObjectConnectionListVector *connectionLists;  
  21.   
  22.  QList senders;  
  23. } ;  

至此,我们已经找到了 Qt 实现 signal/slot 机制的所有需要知道的东西。

记得下面 moc 生成的代码

view plain copy to clipboard print ?
  1. void Counter::valueChanged(int _t1)  
  2. {  
  3.    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };  
  4.    QMetaObject::activate(this, &staticMetaObject, 0, _a);  
  5. }  

这里重要的就是 QMetaObject::activate() 函数,其实它就是依次激活 senders 里面的函数,

view plain copy to clipboard print ?
  1. void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv) ;  
  2. void QMetaObject::activate(QObject *sender, int signal_index, void **argv) ;  
  3. void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index, void **argv) ;  
  4. void QMetaObject::activate(QObject *sender, const QMetaObject *m, int from_local_signal_index, int to_local_signal_index, void **argv) ;  

这里调用的是第三个,他们最终都是用第一个实现的,下面是实现的基本代码,

view plain copy to clipboard print ?
  1. void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv)  
  2. {  
  3.    if (sender->d_func()->blockSig)  
  4.        return;  
  5.    // ...  
  6.    for (int signal = from_signal_index;  
  7.         (signal >= from_signal_index && signal <= to_signal_index) || (signal == -2);  
  8.         (signal == to_signal_index ? signal = -2 : ++signal))  
  9.    {  
  10.        int count = connectionLists->at(signal).count();  
  11.        for (int i = 0; i < count; ++i) {  
  12.            // signal type and etc  
  13.            receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);  
  14.            // ...  
  15.        }  
  16.    //...  
  17.    }  
  18.    // ...  
  19.    --connectionLists->inUse;  
  20.    // ...  
  21. }  

我们来回顾一下,使用 Qt 实现 signal/slots 机制是通过继承 QObject,并在类声明时加入 Q_OBJECT,该宏嵌入一个 QMetaObject 作为整个类实现 Qt 的 RTTI 机制的基础。继承 QObject 的同时使得该类含有一个 d_ptr 指向 QObject 共有的一个友元类 QObjectPrivate,每一个对象都会创建一个 QObjectPrivate 的对象来管理自己 signal/slot。对用户而言,通过声明 signal 其实是实现一个 protected function,它由 moc 生成程序,而 slot 本身就是一般的函数。signal 和 slot 通过调用 QObject::connect()/disconnect() 连接/切断连接,该函数验证连接的合理性(通过 QMetaObject 里面存放的字符串信息,如类名、父类、signal 和 slot 名称和参数),然后调用 QMetaObject::connect() 该方法会使用 mutex 保证操作的线程稳定性,访问两个对象的 d_ptr 指向的 QObjectPrivate 对象,对 sender 添加 connectionLists(一个链表),对 receiver 添加 senders(一个链表)。调用 signal 使用的 emit 其实什么都不是,可以直接调用该 signal 的函数也行,这会调用 QMetaObject::activate() 调用连接上的 slots。

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