(文章转贴自guiliblearning.blogspot.com)
前面提到了 Qt 实现的一个关键性技术,signal/slot。由于 Qt 使用了一个 moc 预处理,因此我们肯定会想知道 Qt 如何通过 moc 实现前面所说对于程序员的优点的呢?我们先通过一个简单的例子看看如何使用 QObject 这一些概念实现最基本的调用过程。然后我们探讨 Qt 实现这种扩展的方式是什么,后面因为要与 Gtkmm 的实现互相比较,而 Gtkmm 使用的是 libsigc++ 实现的对象通信机制,而且不使用预编译,因此可以发现很多有意思的东西。 下面这个例子声明了一个简单的 QObject 的子类,counter.hpp 文件
view plain copy to clipboard print ?
- #ifndef COUNTER_HPP
- #define COUNTER_HPP
- #include
- #include
-
- class Counter : public QObject {
- Q_OBJECT
- int value ;
- signals:
- void valueChanged( int ) ;
- public slots:
- void setValue( int ) ;
- public:
- Counter( int = 0 ) ;
-
- friend std::ostream& operator<< ( std::ostream&, const Counter& ) ;
- } ;
- #endif
我们可以看见我们需要通过继承 QObject 类,并且在 private 段声明 Q_OBJECT,signal 和 slots 其实对应的可以理解为 signal 是一个 callback 函数的链表(因为可以一个 signal 激发多个 slots),而 slots 就是常意的函数,因此也分 public/protected/private,可以继承、可以重载。下面是以上 Counter 类的
实现,counter.cpp 文件
view plain copy to clipboard print ?
- #include
- #include "counter.hpp"
-
- void
- Counter::setValue( int val )
- {
- value = val ;
-
- emit valueChanged( value ) ;
- }
-
- Counter::Counter( int val ) : value( val ) {}
-
- std::ostream&
- operator<< ( std::ostream& os, const Counter& c )
- {
- return os << c.value ;
- }
然后下面是调用的主程序 main.cpp 文件,
view plain copy to clipboard print ?
- #include
- #include
- #include "counter.hpp"
-
- using namespace std ;
-
- int
- main( int argc, char *argv[] )
- {
- Counter a, b( 10 ) ;
-
- cout << "We have two counters, " << a
- << " and " << b << endl ;
-
- QObject::connect( &a, SIGNAL( valueChanged(int) ),
- &b, SLOT( setValue(int) ) ) ;
-
- a.setValue( 12 ) ;
- cout << "We have two counters, " << a
- << " and " << b << endl ;
-
- b.setValue( 5 ) ;
- cout << "We have two counters, " << a
- << " and " << b << endl ;
-
- return 0 ;
- }
我们需要首先在该目录中使用 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 ?
- const QMetaObject Counter::staticMetaObject = {
- { &QObject::staticMetaObject, qt_meta_stringdata_Counter,
- qt_meta_data_Counter, 0 }
- };
其中 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 ?
- #define Q_OBJECT /
- public: /
- Q_OBJECT_CHECK /
- static const QMetaObject staticMetaObject; /
- 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 (插入了一个 qt_check_for_QOBJECT_macro 的 template function),然后是全局常量 QMetaObject 对象,因此可以用 QClassname::staticMetaObject 直接访问,另外提供了两个接口函数 metaObject() 用于不同的 class 返回自己的 staticMetaObject、qt_metacast() 用于转换,我们在 moc 产生的文件里面可以找到这两个接口的实现,
view plain copy to clipboard print ?
- const QMetaObject *Counter::metaObject() const
- {
- return &staticMetaObject;
- }
-
- void *Counter::qt_metacast(const char *_clname)
- {
- if (!_clname) return 0;
- if (!strcmp(_clname, qt_meta_stringdata_Counter))
- return static_cast<void*>(const_cast< Counter*>(this));
- return QObject::qt_metacast(_clname);
- }
后者很明显,如果需要转换的名字 _clname 是自己的类名,就把自己的指针通过转换成 void* 传回去,否则调用 QOject::qt_metacast(),其实就是看是不是 QObject 了,否则就返回 0 了。另外 QT_TR_FUNCTIONS 是对应的 i18n 的函数,我们后面再看。最后还有一个 qt_metacall 的接口,实现如下
view plain copy to clipboard print ?
- int Counter::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
- {
- _id = QObject::qt_metacall(_c, _id, _a);
- if (_id < 0)
- return _id;
- if (_c == QMetaObject::InvokeMetaMethod) {
- switch (_id) {
- case 0:
- valueChanged((*reinterpret_cast< int(*)>(_a[1])));
- break;
- case 1:
- setValue((*reinterpret_cast< int(*)>(_a[1])));
- break;
- default: ;
- }
- _id -= 2;
- }
- return _id;
- }
这个函数起到一个中间作用,可以间接的调用成员方法,我们来仔细看看 QMetaObject,同一个文件里面有该结构的定义,我们只看这一部分,
view plain copy to clipboard print ?
- struct Q_CORE_EXPORT QMetaObject
- {
- const char *className() const;
- const QMetaObject *superClass() const;
-
- QObject *cast(QObject *obj) const;
-
-
-
- struct {
- const QMetaObject *superdata;
- const char *stringdata;
- const uint *data;
- const void *extradata;
- } d;
- } ;
注意 Couter 类 QMetaObject 的初始化,
view plain copy to clipboard print ?
- const QMetaObject Counter::staticMetaObject = {
- { &QObject::staticMetaObject, qt_meta_stringdata_Counter,
- qt_meta_data_Counter, 0 }
- } ;
下面我们着重看看几个与 signal/slot 相关的代码,首先就是 [qt]/src/corelib/kernel/qobject.cpp 文件中关于 QObject::connect() 函数的代码,
view plain copy to clipboard print ?
- bool QObject::connect(const QObject *sender, const char *signal,
- const QObject *receiver, const char *method,
- Qt::ConnectionType type)
- {
- {
- const void *cbdata[] = { sender, signal, receiver, method, &type };
- if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
- return true;
- }
-
-
-
- QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
- const_cast(sender)->connectNotify(signal - 1);
- return true;
- }
这里首先调用了 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 ?
- bool QInternal::activateCallbacks(Callback cb, void **parameters)
- {
- Q_ASSERT_X(cb >= 0, "QInternal::activateCallback()", "Callback id must be a valid id");
-
- QInternal_CallBackTable *cbt = global_callback_table();
- if (cbt && cb < cbt->callbacks.size()) {
- QList callbacks = cbt->callbacks[cb];
- bool ret = false;
- for (int i=0; i
- ret |= (callbacks.at(i))(parameters);
- return ret;
- }
- return false;
- }
这可以看出来调用该函数去检查一个 global_callback_table(),如果查到一个匹配的 signal/slot 就返回 true,否则返回 false,换言之 QObject::connect() 通过这个函数判定需不需要调用 QMetaObject::connect 创建新的连接。这个 callback table 本质是什么呢?同一个文件里面有
view plain copy to clipboard print ?
- struct QInternal_CallBackTable {
- QVector > callbacks;
- };
所以,这是一个链表的动态数组(Orz... 本来以为就是个链表),而 qInternalCallback 是一个在 [qt]/src/corelib/global/qnamespace.h 中定义的函数指针,
view plain copy to clipboard print ?
- typedef bool (*qInternalCallback)(void **);
现在我们可以猜测到在 QMetaObject::connect() 调用中我们会维护这个 callback table。在 [qt]/src/corelib/kernel/qmetaobject.cpp 我们有幸找到了如下代码:
view plain copy to clipboard print ?
- bool QMetaObject::connect(const QObject *sender, int signal_index,
- const QObject *receiver, int method_index, int type, int *types)
- {
- QObject *s = const_cast
- QObject *r = const_cast
-
- QOrderedMutexLocker locker(&s->d_func()->threadData->mutex,
- &r->d_func()->threadData->mutex);
-
- #if defined(Q_CC_HPACC) && defined(QT_ARCH_PARISC)
- QObjectPrivate::Connection c;
- c.receiver = r;
- c.method = method_index;
- c.connectionType = type;
- c.argumentTypes = types;
- #else
- QObjectPrivate::Connection c = { r, method_index, type, Q_BASIC_ATOMIC_INITIALIZER(types) };
- #endif
- s->d_func()->addConnection(signal_index, &c);
- r->d_func()->refSender(s, signal_index);
-
- if (signal_index < 0)
- sender->d_func()->connectedSignals = ~0u;
- else if (signal_index < 32)
- sender->d_func()->connectedSignals |= (1 << signal_index);
-
- return true;
- }
这段代码中使用了防止多线程操作引起问题的 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 ?
- #define Q_DECLARE_PRIVATE(Class) /
- inline Class##Private* d_func() { return reinterpret_cast(d_ptr); } /
- inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } /
- friend class Class##Private;
-
- #define Q_DECLARE_PRIVATE_D(Dptr, Class) /
- inline Class##Private* d_func() { return reinterpret_cast(Dptr); } /
- inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(Dptr); } /
- friend class Class##Private;
-
- #define Q_DECLARE_PUBLIC(Class) /
- inline Class* q_func() { return static_cast(q_ptr); } /
- inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } /
- friend class Class;
-
- #define Q_D(Class) Class##Private * const d = d_func()
- #define Q_Q(Class) Class * const q = q_func()
记得 QObject 的定义么?
view plain copy to clipboard print ?
- class Q_CORE_EXPORT QObject
- {
- Q_OBJECT
- Q_PROPERTY(QString objectName READ objectName WRITE setObjectName)
- Q_DECLARE_PRIVATE(QObject)
这样 QObject 通过 d_func() 返回 d_ptr 这个指针,这是怎么一回事呢?我们知道这个宏在 QObject 内部定义了一个 QObjectPrivate 的类,并且留下了一个指针
view plain copy to clipboard print ?
- protected:
- QObjectData *d_ptr;
在 QObject 构造的时候创建了这个对象,并在析构的时候释放,由于是 friend class,可以对 QObject 无限制访问,
view plain copy to clipboard print ?
- QObject::QObject(QObject *parent) : d_ptr(new QObjectPrivate)
- QObject::~QObject()
- {
- Q_D(QObject);
-
- emit destroyed(this);
-
- delete d;
- d_ptr = 0;
- }
可见真正管理 signal/slots 的是 QObjectPrivate 类,下面是它的主要成员函数,
view plain copy to clipboard print ?
-
- QObjectPrivate::QObjectPrivate(int version) ;
- QObjectPrivate::~QObjectPrivate() ;
- int *QObjectPrivate::setDeleteWatch(QObjectPrivate *d, int *w) ;
- void QObjectPrivate::resetDeleteWatch(QObjectPrivate *d, int *oldWatch, int deleteWatch) ;
- void QObjectPrivate::sendPendingChildInsertedEvents() ;
- void QObjectPrivate::removePendingChildInsertedEvents(QObject *child) ;
- bool QObjectPrivate::isSender(const QObject *receiver, const char *signal) const ;
- QObjectList QObjectPrivate::receiverList(const char *signal) const ;
- QObjectList QObjectPrivate::senderList() ;
-
- void QObjectPrivate::addConnection(int signal, Connection *c) ;
- void QObjectPrivate::removeReceiver(int signal, QObject *receiver) ;
- void QObjectPrivate::cleanConnectionLists() ;
-
- void QObjectPrivate::refSender(QObject *sender, int signal) ;
- void QObjectPrivate::derefSender(QObject *sender, int signal) ;
- void QObjectPrivate::removeSender(QObject *sender, int signal) ;
- QObjectPrivate::Sender *QObjectPrivate::setCurrentSender(QObject *receiver, Sender *sender) ;
- void QObjectPrivate::resetCurrentSender(QObject *receiver, Sender *currentSender, Sender *previousSender) ;
- void QObjectPrivate::clearGuards(QObject *object) ;
那么我们如何存储数据的呢?在 [qt]/src/corelib/kernel/qobject_p.h 里,有该类的声明,
view plain copy to clipboard print ?
- class Q_CORE_EXPORT QObjectPrivate : public QObjectData {
-
- public:
- QList pendingChildInsertedEvents;
- struct Sender {
- QObject *sender;
- int signal;
- int ref;
- };
- Sender *currentSender;
- QList > eventFilters;
-
- struct Connection {
- QObject *receiver;
- int method;
- uint connectionType : 3;
- QBasicAtomicPointer<int> argumentTypes;
- };
- typedef QList ConnectionList;
- QObjectConnectionListVector *connectionLists;
-
- QList senders;
- } ;
至此,我们已经找到了 Qt 实现 signal/slot 机制的所有需要知道的东西。
记得下面 moc 生成的代码
view plain copy to clipboard print ?
- void Counter::valueChanged(int _t1)
- {
- void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
- QMetaObject::activate(this, &staticMetaObject, 0, _a);
- }
这里重要的就是 QMetaObject::activate() 函数,其实它就是依次激活 senders 里面的函数,
view plain copy to clipboard print ?
- void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv) ;
- void QMetaObject::activate(QObject *sender, int signal_index, void **argv) ;
- void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index, void **argv) ;
- 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 ?
- void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv)
- {
- if (sender->d_func()->blockSig)
- return;
-
- for (int signal = from_signal_index;
- (signal >= from_signal_index && signal <= to_signal_index) || (signal == -2);
- (signal == to_signal_index ? signal = -2 : ++signal))
- {
- int count = connectionLists->at(signal).count();
- for (int i = 0; i < count; ++i) {
-
- receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
-
- }
-
- }
-
- --connectionLists->inUse;
-
- }
我们来回顾一下,使用 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。