Showing posts with label signals and slots. Show all posts
Showing posts with label signals and slots. Show all posts

Friday, April 24, 2009

Qt 的线程和线程通信

传统的多线程程序都是通过 POSIX 的线程库 pthread 实现的,而 Qt 为用户提供的线程就是对 pthread 的再次封装,这个封装的类就是 QThread 直接继承 QObject 而来。

class Q_CORE_EXPORT QThread : public QObject
{
public:
    static Qt::HANDLE currentThreadId();
    static QThread *currentThread();
    static int idealThreadCount();
    static void yieldCurrentThread();

    explicit QThread(QObject *parent = 0);
    ~QThread();

    enum Priority {
        IdlePriority,

        LowestPriority,
        LowPriority,
        NormalPriority,
        HighPriority,
        HighestPriority,

        TimeCriticalPriority,

        InheritPriority
    };

    void setPriority(Priority priority);
    Priority priority() const;

    bool isFinished() const;
    bool isRunning() const;

    void setStackSize(uint stackSize);
    uint stackSize() const;

    void exit(int retcode = 0);

public Q_SLOTS:
    void start(Priority = InheritPriority);
    void terminate();
    void quit();

public:
    // default argument causes thread to block indefinately
    bool wait(unsigned long time = ULONG_MAX);

Q_SIGNALS:
    void started();
    void finished();
    void terminated();

protected:
    virtual void run();
    int exec();

    static void setTerminationEnabled(bool enabled = true);

    static void sleep(unsigned long);
    static void msleep(unsigned long);
    static void usleep(unsigned long);

#ifdef QT3_SUPPORT
public:
    inline QT3_SUPPORT bool finished() const { return isFinished(); }
    inline QT3_SUPPORT bool running() const { return isRunning(); }
#endif

protected:
    QThread(QThreadPrivate &dd, QObject *parent = 0);

private:
    Q_OBJECT
    Q_DECLARE_PRIVATE(QThread)

    static void initialize();
    static void cleanup();

    friend class QCoreApplication;
    friend class QThreadData;
};
编程人员主要通过继承 QThread 类,并自己重新实现 QThread::run(),我们来看该类在哪里留下的“钩子”,这个原始的 run() 做了什么呢?
void QThread::run()
{
    (void) exec();
}

int QThread::exec()
{
    Q_D(QThread);
    d->mutex.lock();
    d->data->quitNow = false;
    QEventLoop eventLoop;
    d->mutex.unlock();
    int returnCode = eventLoop.exec();
    return returnCode;
}
可见默认情况就是进入一个事件循环。

所谓的 event loop 是每一个 thread 都需要进行的任务,这个过程将处理外部环境发给自己的 event,Qt 将各种 event 进行了包装,用 QEvent 类进行了封装,不同的事件就是 QEvent 的子类,很多类都具有处理 event 的能力,比如 QWidget,下面是 QWidget 实现的事件,

protected:
    // Event handlers
    bool event(QEvent *);
    virtual void mousePressEvent(QMouseEvent *);
    virtual void mouseReleaseEvent(QMouseEvent *);
    virtual void mouseDoubleClickEvent(QMouseEvent *);
    virtual void mouseMoveEvent(QMouseEvent *);
#ifndef QT_NO_WHEELEVENT
    virtual void wheelEvent(QWheelEvent *);
#endif
    virtual void keyPressEvent(QKeyEvent *);
    virtual void keyReleaseEvent(QKeyEvent *);
    virtual void focusInEvent(QFocusEvent *);
    virtual void focusOutEvent(QFocusEvent *);
    virtual void enterEvent(QEvent *);
    virtual void leaveEvent(QEvent *);
    virtual void paintEvent(QPaintEvent *);
    virtual void moveEvent(QMoveEvent *);
    virtual void resizeEvent(QResizeEvent *);
    virtual void closeEvent(QCloseEvent *);
#ifndef QT_NO_CONTEXTMENU
    virtual void contextMenuEvent(QContextMenuEvent *);
#endif
#ifndef QT_NO_TABLETEVENT
    virtual void tabletEvent(QTabletEvent *);
#endif
#ifndef QT_NO_ACTION
    virtual void actionEvent(QActionEvent *);
#endif

#ifndef QT_NO_DRAGANDDROP
    virtual void dragEnterEvent(QDragEnterEvent *);
    virtual void dragMoveEvent(QDragMoveEvent *);
    virtual void dragLeaveEvent(QDragLeaveEvent *);
    virtual void dropEvent(QDropEvent *);
#endif

    virtual void showEvent(QShowEvent *);
    virtual void hideEvent(QHideEvent *);

#if defined(Q_WS_MAC)
    virtual bool macEvent(EventHandlerCallRef, EventRef);
#endif
#if defined(Q_WS_WIN)
    virtual bool winEvent(MSG *message, long *result);
#endif
#if defined(Q_WS_X11)
    virtual bool x11Event(XEvent *);
#endif
#if defined(Q_WS_QWS)
    virtual bool qwsEvent(QWSEvent *);
#endif

    // Misc. protected functions
    virtual void changeEvent(QEvent *);
这里看起来很复杂,主要两种,一个是 QWidget::event() 本身,另一个是各种虚函数 *Event 用于处理不同的 event,其实是一种 event handler,我们通过继承 QWidget 实现不同的 event handler 就可以获得不同的 component 了,比如 QAbstractButton 需要重写 paintEvent,使得该区域没有被按下的时候凸起,而按下后变成凹下的样子,另外,在 QWidget::event() 的实现中我们看见如下实现,
switch (event->type()) {
case QEvent::MouseMove:
  mouseMoveEvent((QMouseEvent*)event);
  break;
// ...
}
这就是下“钩子”的地方,Qt 通过调用 event() 方法,响应向一个 QObject 发送一个需要响应的事件(调用者利用 QApplication::sendEvent() 方法发送),event() 处理后返回 true,否则返回 false,event() 函数进一步利用 virtual function *Event() 函数分类处理该事件,因此要改变一个 QWidget 响应某事件就需要 override 这些事件的 event handler。而为了让用户更好的处理这些事件而不干预内部的实现,在 event handler 里面会 emit signal,比如
void QAbstractButton::mousePressEvent(QMouseEvent *e)
{
    Q_D(QAbstractButton);
    if (e->button() != Qt::LeftButton) {
        e->ignore();
        return;
    }
    if (hitButton(e->pos())) {
        setDown(true);
        repaint(); //flush paint event before invoking potentially expensive operation
        QApplication::flush();
        d->emitPressed();
        e->accept();
    } else {
        e->ignore();
    }
}
这样,用户就可以利用这个 signal 而不是事件本身来通知别的 component 了。如果是需要通过 QApplication::sendEvent() 那么必须重新 implement 对应的 event handler,也就是必须继承该 class,这是非常繁琐的(每个控件都是库里面的衍生体 -,-b)。

我们接着看 QThread 如何调用 run(),这部分到不是在 [qt]/src/corelib/qthread.cpp 里定义的,是在 qthread_unix.cpp 里面,这相当于是跟实现的 OS 相关的代码,因此分成了几个文件,QThread::start() 调用了 run(),

int code =
        pthread_create(&d->thread_id, &attr, QThreadPrivate::start, this);
void *QThreadPrivate::start(void *arg)
{
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    pthread_cleanup_push(QThreadPrivate::finish, arg);

    QThread *thr = reinterpret_cast<QThread *>(arg);
    QThreadData *data = QThreadData::get2(thr);

    pthread_once(¤t_thread_data_once, create_current_thread_data_key);
    pthread_setspecific(current_thread_data_key, data);

    data->ref();
    data->quitNow = false;

    // ### TODO: allow the user to create a custom event dispatcher
    if (QCoreApplication::instance())
        createEventDispatcher(data);

    emit thr->started();
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    pthread_testcancel();
    thr->run();

    pthread_cleanup_pop(1);
    return 0;
}
这个程序首先把 QThreadPrivate::finish() 压栈(等线程结束后清理),设置好线程特性,然后建立 event dispatcher,触发 QThread::started() 信号,然后就调用 QThread::run() 了。最后等待该部分结束后,就会通过前面压栈的 finish() 完成清理工作。

因此尽管 QThread 这个对象在主线程中创建,但是实际上 QThread::run() 是在新的线程里面运行的,这里涉及到很重要的问题就是 reentrant 和 thread-safe。一个函数/类称为 reentrant 如果可以同时被若干线程调用,一般来说就是说他不会改变内部所依赖的数据,有的函数通过一个 static 成员计算结果,而该成员在计算中会被反复修改,因此一个线程调用该函数后,另一个线程就不可以继续调用。所谓的 thread-safe 就是说,某函数/类尽管需要访问 shared data,但是通过串行化访问,保证在不同线程调用时使得每个操作都是安全的。为了保证这点,Qt 提供了我们 QMutex、QReadWriteLock、QSemaphore 和 QWaitCondition 这些用于保证访问的 thread-safety 和同步我们的线程。

所谓 mutex(mutual exclusive)就是保证访问是 serialized 的,创建一个 QMutex 对象,所有合法的请求应该都遵守首先使用 QMutexLocker 将该 mutex 锁定,然后才能访问数据的原则,这时因为 QMutex 只能被 lock 一次,后续请求的线程将被挂起直到前面锁定的线程解除。可是 QMutex 无法区分读、写的不同,因为往往读操作可以多线程并行,而写操作必须串行化,这可以用 QReadWriteLock。另外 QMetaphore 是对一批资源的控制,比如 Qt 提供的例程,

#include <QtCore>

#include <stdio.h>
#include <stdlib.h>

const int DataSize = 100000;
const int BufferSize = 8192;
char buffer[BufferSize];

QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes;

class Producer : public QThread
{
public:
  void run();
};

void Producer::run()
{
  qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
  for (int i = 0; i < DataSize; ++i) {
    freeBytes.acquire();
    buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
    usedBytes.release();
  }
}

class Consumer : public QThread
{
public:
  void run();
};

void Consumer::run()
{
  for (int i = 0; i < DataSize; ++i) {
    usedBytes.acquire();
    fprintf(stderr, "%c", buffer[i % BufferSize]);
    freeBytes.release();
  }
  fprintf(stderr, "\n");
}

int main(int argc, char *argv[])
{
  QCoreApplication app(argc, argv);
  Producer producer;
  Consumer consumer;
  producer.start();
  consumer.start();
  producer.wait();
  consumer.wait();
  return 0;
}
有人可能会抱怨,那个 fprintf() 会不会不是 atomic 的呢?
The POSIX standard requires that by default the stream operations are atomic. I.e., issuing two stream operations for the same stream in two threads at the same time will cause the operations to be executed as if they were issued sequentially. The buffer operations performed while reading or writing are protected from other uses of the same stream. To do this each stream has an internal lock object which has to be (implicitly) acquired before any work can be done.
But there are situations where this is not enough and there are also situations where this is not wanted. The implicit locking is not enough if the program requires more than one stream function call to happen atomically. One example would be if an output line a program wants to generate is created by several function calls. The functions by themselves would ensure only atomicity of their own operation, but not atomicity over all the function calls. For this it is necessary to perform the stream locking in the application code.
而 QWaitCondition 主要用于多个线程的同步,比如创建一个该对象,几个线程调用 QWaitCondition::wait( &mutex ),另外的通过 QWaitCondition::wakeAll() 或者 QWaitCondition::wakeOne() 唤醒。

最后我们谈一下 signal/slot 机制和多线程的关系。尽管 QObject 保证是 reentrant 的(QWidget 一般不是的),但是我们编程的时候要注意,一个 QObject 的的子对象(通过 QObject::QObject( QObject *parent) 构造)必须在同一个线程里面产生;另外,对事件驱动的如 QTimer 和 network 一些类,启动的代码必须在同一个线程里(这意味着如果在一个QThread 子类里面创建一个 QTimer,其实是不能在 QThread::run() 里面启动该 timer,因为创建 QThread 对象是在另外一个线程里面,只有 QThread::run() 的那部分在新的线程里面执行);在一个线程里面创建的对象必须在线程被释放 QThread 前释放。

一个 QObject 创建后存在于一个线程中,我们可以用 QObject::moveToThread() 把该对象“置于”另外的线程,但是该 QObject 不能有 parent。这时候从一个线程调用另外一个线程的方法可以认为不是 thread safe 的,因为可能该对象正在处理事件,而比如调用析构函数就会产生严重问题(因此最好调用 QObject::deleteLater() 函数),这也就是我们需要注意信号发送的方式的问题,QObject::connect() 允许我们使用所谓 queued connection。低层一点的 QCoreApplication::sendEvent() 其实也应该在这种情况被替换为 QCoreApplication::postEvent()。这样避免在本线程重复调用某个 QObject 的代码,产生 thread safety 的问题。

我们将在下面研究 QApplication 和 QCoreApplication 类,分析 sendEvent()、postEvent() 以及 Qt 如何实现的 queued connection 的。

Saturday, April 18, 2009

再看 Qt 的 QMetaObject

首先我们看看 Q_OBJECT 展开变成了什么,在 qobjectdefs.h 文件中有
#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 产生的文件里面可以找到这两个接口的实现,
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 的接口,实现如下
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,同一个文件里面有该结构的定义,我们只看这一部分,
struct Q_CORE_EXPORT QMetaObject
{
 const char *className() const;
 const QMetaObject *superClass() const;

 QObject *cast(QObject *obj) const;

 // ...

 struct { // private data
   const QMetaObject *superdata;
   const char *stringdata;
   const uint *data;
   const void *extradata;
 } d;
} ;
注意 Couter 类 QMetaObject 的初始化,
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() 函数的代码,
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;
 }

 // checking sender, receiver, compatability of signal and slot

 QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
 const_cast<QObject*>(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 中定义,
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<qInternalCallback> callbacks = cbt->callbacks[cb];
   bool ret = false;
   for (int i=0; i<callbacks.size(); ++i)
     ret |= (callbacks.at(i))(parameters);
   return ret;
 }
 return false;
}
这可以看出来调用该函数去检查一个 global_callback_table(),如果查到一个匹配的 signal/slot 就返回 true,否则返回 false,换言之 QObject::connect() 通过这个函数判定需不需要调用 QMetaObject::connect 创建新的连接。这个 callback table 本质是什么呢?同一个文件里面有
struct QInternal_CallBackTable {
QVector<QList<qInternalCallback> > callbacks;
};
所以,这是一个链表的动态数组(Orz... 本来以为就是个链表),而 qInternalCallback 是一个在 [qt]/src/corelib/global/qnamespace.h 中定义的函数指针,
typedef bool (*qInternalCallback)(void **);
现在我们可以猜测到在 QMetaObject::connect() 调用中我们会维护这个 callback table。在 [qt]/src/corelib/kernel/qmetaobject.cpp 我们有幸找到了如下代码:
bool QMetaObject::connect(const QObject *sender, int signal_index,
                      const QObject *receiver, int method_index, int type, int *types)
{
 QObject *s = const_cast<QObject *<(sender);
 QObject *r = const_cast<QObject *<(receiver);

 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() 返回的是什么。

#define Q_DECLARE_PRIVATE(Class) \
  inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(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<Class##Private *>(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<Class *>(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 的定义么?
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 的类,并且留下了一个指针
protected:
   QObjectData *d_ptr;
在 QObject 构造的时候创建了这个对象,并在析构的时候释放,由于是 friend class,可以对 QObject 无限制访问,
QObject::QObject(QObject *parent) : d_ptr(new QObjectPrivate)
QObject::~QObject()
{
   Q_D(QObject);
   // clear all signal receivers
   emit destroyed(this);
   // clear all signal senders
   delete d;
   d_ptr = 0;
}
可见真正管理 signal/slots 的是 QObjectPrivate 类,下面是它的主要成员函数,
//constructor and destructor
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() ;
// connection list manipulation
void QObjectPrivate::addConnection(int signal, Connection *c) ;
void QObjectPrivate::removeReceiver(int signal, QObject *receiver) ;
void QObjectPrivate::cleanConnectionLists() ;
// sender list manipulation
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 里,有该类的声明,
class Q_CORE_EXPORT QObjectPrivate : public QObjectData {
 // ...
public:
 QList pendingChildInsertedEvents;
 struct Sender {
   QObject *sender;
   int signal;
   int ref;
 };
 Sender *currentSender;
 QList<QPointer<QObject> > eventFilters;

 struct Connection {
   QObject *receiver;
   int method;
   uint connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
   QBasicAtomicPointer<int> argumentTypes;
 };
 typedef QList<Connection> ConnectionList;
 QObjectConnectionListVector *connectionLists;

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

记得下面 moc 生成的代码

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 里面的函数,
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) ;
这里调用的是第三个,他们最终都是用第一个实现的,下面是实现的基本代码,
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) {
           // signal type and etc
           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。

Friday, April 17, 2009

Gtkmm 实现信号使用 libsigc++

这个 libsigc++ 只有一个终极目标,通过 C++ 的 template 机制实现 object 之间 signal/slot 功能。与 Qt 依赖 QMetaObject 不同在于,前者 hack C++ 的 template,而后者其实通过什么字符串等实现的,我们在这里仅仅实现一个简单的通信例子,介绍使用方法。我们将在后面的文章比较这两种实现需要的开销,细节。 整体上来看 libsigc++ 实现通信机制要简单很多,下面是 counter.hpp:
#ifndef COUNTER_HPP
#define COUNTER_HPP
#include <iostream>
#include <sigc++/sigc++.h>

class Counter {
int value ;
public:
sigc::signal<void, int> valueChanged ;
void setValue( int ) ;
Counter( int = 0 ) ;

friend std::ostream& operator<< ( std::ostream&, const Counter& ) ;
} ;

#endif
这里和 Qt 不同之处是需要通过 composite 一个 sigc::signal 的对象从而使得该类获得 signal/slot 处理能力,Qt 是依靠继承 QObject 获得(使用 Q_OBJECT 只是为了获得新的 QMetaObject)。下面是实现代码 counter.cpp:
#include <iostream>
#include "counter.hpp"

void
Counter::setValue( int val )
{
value = val ;
// emit the signal
valueChanged.emit( value ) ;
}

Counter::Counter( int val ) : value( val ) {}

std::ostream&
operator<< ( std::ostream& os, const Counter& c )
{
return os << c.value ;
}
这里通过 sigc::signal::emit 方法或者 sigc::signal::operator() 发送信号。最后是主文件 test_sigc.cpp:
#include <iostream>
#include <iomanip>
#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 ;

a.valueChanged.connect( sigc::mem_fun( b, &Counter::setValue ) ) ;

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 ;
}
这里调用了 sigc::signal::connect 方法连接到一个 slot上,该 slot 是使用 sigc::mem_fun 产生的一个调用指定对象成员函数的 functor。其实 libsigc++ 提供了远远不止这些功能,我们将在下面仔细介绍其他的功能。 编译 libsigc++ 程序可以用 pkg-config,如
$ g++ -o test_sigc test_sigc.o counter.o `pkg-config sigc++-2.0 --libs`
通过以上例子我们可以大致获得一个使用该库的印象,需要 signal 功能的类通过 composition 或者继承(感觉似乎不大好,也许应该 protected 继承?)加入 sigc::signal<> 对象,该模板第一个参数是 signal 返回值,后面写上的话是 signal 的参数,可见通过 template 的检查保证了 type safe。之后使用 sigc::signal::connect 连接到一个 slot 上,该 slot 可以用 sigc::mem_fun() 创建(使用对象的成员函数)或者 sigc::fun_ptr() 创建(使用一般函数地址)。在该库编写的时候合适的地方通过调用 sigc::signal::emit() (或者重载的 operator())发出 signal。 简而言之,C++ 的 virtual function 为对象在继承过程中实现多态提供了基础,是一个同类函数同名操作的函数钩子;signal/slot 机制是为了对象之间互相通信留下的函数钩子。只是前者通过 vtable 很容易实现,后者需要依赖 template 和 functor 等比较高级的方式实现。

Thursday, April 16, 2009

Qt 如何实现的 Meta Object

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

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 文件
#include <iostream>
#include "counter.hpp"

void
Counter::setValue( int val )
{
value = val ;
// emit the signal
emit valueChanged( value ) ;
}

Counter::Counter( int val ) : value( val ) {}

std::ostream&
operator<< ( std::ostream& os, const Counter& c )
{
return os << c.value ;
}
然后下面是调用的主程序 main.cpp 文件,
#include <iostream>
#include <iomanip>
#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 是
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 产生的?