Qt信号槽连接方式源码解读

前言

  • Qt的五(四)种连接方式,在上一篇已经讲明,本篇主要分析在源码上是如何实现这几种连接方式的。
  • 本次源码为Qt 5.15.2
  • 搞懂务必认真阅读最后添加注释后的代码

connect时会做什么?

已知connect是可以实现一个信号连接多个槽的,并且Qt会为每一个信号创建一个槽链表。
所以其内部当发现(触发)connect时,就会把connect的“接收者和槽”加入到对应的信号的槽链表上。存储的就是接收者及其槽的索引(函数地址)。当然每一个接收对象也会记录与之连接信号,以便销毁时会通知信号将其断开。(后续会专门补这块的源码分析)

获取信号对应的槽

通常我们在跳转到一个信号的定义时,都会看到类似这种的代码

// SIGNAL 0
void TestMoc::testMocSignal(int _t1, int _t2)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))), const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

这里要重点关注QMetaObject::activate的第三个参数0,会根据这个索引值,把发送者(this)、信号函数的参数,传给activate函数,

void TestMoc::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast<TestMoc *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->testMocSignal((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< int(*)>(_a[2]))); break;
        case 1: _t->testMocSignal2((*reinterpret_cast< int(*)>(_a[1]))); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        //·····
    }
}

QMetaObject::activate 最终是执行doActivate执行槽函数

void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
                           void **argv)
{
    int signal_index = local_signal_index + QMetaObjectPrivate::signalOffset(m);

    if (Q_UNLIKELY(qt_signal_spy_callback_set.loadRelaxed()))
        doActivate<true>(sender, signal_index, argv);
    else
        doActivate<false>(sender, signal_index, argv);
}

doActivate的实现

  1. 获取信号对应的槽函数链表
  2. 获取当前的发送信号的线程ID,并判断是否在发送者线程
  3. 遍历该信号的所有连接
    1. 如果不在发送者线程,对接收者进行加锁
    2. 如果当前时默认连接,并且接收者和发送信号不在同一线程或者指定为Qt::QueuedConnection连接,通过queued_activate封装成事件调用postevnt给接收者
    3. 当前连接方式为Qt::BlockingQueuedConnection
      1. 同一个线程抛出死锁警告
      2. 构造的QMetaCallEvent通过postEvent形式给接收者
      3. 并且通过QSemaphore阻塞发送者线程,等待槽函数执行完毕将
      4. 接收者执行槽函数完毕后再执行semaphore.release()来释放信号使其(调用发送信号的对象继续执行)
    4. 处理所有的直连(Qt::DirectConnection)
      1. 通过 call方法调用槽Qt5的函数指针
      2. 通过callFunction即moc_XXXX.cpp里的qt_static_metacall
      3. 通过metacall, 也就是moc_XXXX.cpp里的qt_metacall再调用
void doActivate(QObject *sender, int signal_index, void **argv)
{
    QObjectPrivate *sp = QObjectPrivate::get(sender);
    //·····省略
    bool senderDeleted = false;
    {
    Q_ASSERT(sp->connections.loadAcquire());
    
    QObjectPrivate::ConnectionDataPointer connections(sp->connections.loadRelaxed());
    QObjectPrivate::SignalVector *signalVector = connections->signalVector.loadRelaxed();
    //1.获取信号对应的槽函数链表
    const QObjectPrivate::ConnectionList *list;
    if (signal_index < signalVector->count())
        list = &signalVector->at(signal_index);
    else
        list = &signalVector->at(-1);
    //2.获取当前的发送信号的线程ID,并判断是否在发送者线程
    Qt::HANDLE currentThreadId = QThread::currentThreadId();
    bool inSenderThread = currentThreadId == QObjectPrivate::get(sender)->threadData.loadRelaxed()->threadId.loadRelaxed();
    //3.遍历该信号的所有连接
    /*参考代码
    QObjectPrivate::Connection *c = list->first.loadRelaxed();
    if (!c)
        continue;
    do {
		//...
    }  while ((c = c->nextConnectionList.loadRelaxed()) != nullptr && c->id <= highestConnectionId);
    */
    //源代码
    do {
        QObjectPrivate::Connection *c = list->first.loadRelaxed();
        if (!c)
            continue;
        do {
            QObject * const receiver = c->receiver.loadRelaxed();
            if (!receiver)
                continue;
            QThreadData *td = c->receiverThreadData.loadRelaxed();
            if (!td)
                continue;
            //3.1如果不在发送者线程,对接收者进行加锁
            bool receiverInSameThread;
            if (inSenderThread) {
                receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();
            } else {
                // need to lock before reading the threadId, because moveToThread() could interfere
                QMutexLocker lock(signalSlotLock(receiver));
                receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();
            }
            //3.2 如果当前时默认连接,并且接收者和发送**信号**不在同一线程或者指定为Qt::QueuedConnection连接
            // 通过queued_activate封装成事件事件postevnt给接收者
            if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                || (c->connectionType == Qt::QueuedConnection)) {
                queued_activate(sender, signal_index, c, argv);
                continue;
#if QT_CONFIG(thread)
            }
            //3.3 当前连接方式为Qt::BlockingQueuedConnection
            // 同一个线程抛出死锁警告
            // 并且通过QSemaphore 阻塞发送者线程,等待槽函数执行完毕将构造的QMetaCallEvent通过postEvent形式给接收者
            // 接收者执行槽函数完毕后再执行semaphore.release()来释放信号使其(调用发送信号的对象继续执行)
            else if (c->connectionType == Qt::BlockingQueuedConnection) {
                if (receiverInSameThread) {
                    qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: "
                    "Sender is %s(%p), receiver is %s(%p)",
                    sender->metaObject()->className(), sender,
                    receiver->metaObject()->className(), receiver);
                }
                QSemaphore semaphore;
                {
                    QBasicMutexLocker locker(signalSlotLock(sender));
                    if (!c->receiver.loadAcquire())
                        continue;
                    QMetaCallEvent *ev = c->isSlotObject ?
                        new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore) :
                        new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction,
                                           sender, signal_index, argv, &semaphore);
                    QCoreApplication::postEvent(receiver, ev);
                }
                semaphore.acquire();
                continue;
#endif
            }
            //3.4 处理所有的直连(Qt::DirectConnection)
            // Qt还可以进行信号转发,一个信号连接另一个信号的形式
            QObjectPrivate::Sender senderData(receiverInSameThread ? receiver : nullptr, sender, signal_index);
            //3.4.1 如果是槽,则直接通过call方法调用槽Qt5的函数指针
            if (c->isSlotObject) {
                c->slotObj->ref();

                struct Deleter {
                    void operator()(QtPrivate::QSlotObjectBase *slot) const {
                        if (slot) slot->destroyIfLastRef();
                    }
                };
                const std::unique_ptr<QtPrivate::QSlotObjectBase, Deleter> obj{c->slotObj};

                {
                    Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.get());
                    obj->call(receiver, argv);
                }
            } 
            //3.4.2 通过callFunction即moc_XXXX.cpp里的qt_static_metacall
            else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
                //we compare the vtable to make sure we are not in the destructor of the object.
                const int method_relative = c->method_relative;
                const auto callFunction = c->callFunction;
                const int methodIndex = (Q_HAS_TRACEPOINTS || callbacks_enabled) ? c->method() : 0;
                if (callbacks_enabled && signal_spy_set->slot_begin_callback != nullptr)
                    signal_spy_set->slot_begin_callback(receiver, methodIndex, argv);

                {
                    Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, methodIndex);
                    callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv);
                }

                if (callbacks_enabled && signal_spy_set->slot_end_callback != nullptr)
                    signal_spy_set->slot_end_callback(receiver, methodIndex);
            } 
            //3.4.3 通过metacall, 也就是moc_XXXX.cpp里的qt_metacall再调用
            else {
                const int method = c->method_relative + c->method_offset;

                if (callbacks_enabled && signal_spy_set->slot_begin_callback != nullptr) {
                    signal_spy_set->slot_begin_callback(receiver, method, argv);
                }

                {
                    Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, method);
                    QMetaObject::metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv);
                }

                if (callbacks_enabled && signal_spy_set->slot_end_callback != nullptr)
                    signal_spy_set->slot_end_callback(receiver, method);
            }
            
        } while ((c = c->nextConnectionList.loadRelaxed()) != nullptr && c->id <= highestConnectionId);

    } while (list != &signalVector->at(-1) &&
        //start over for all signals;
        ((list = &signalVector->at(-1)), true));

        if (connections->currentConnectionId.loadRelaxed() == 0)
            senderDeleted = true;
    }
    if (!senderDeleted) {
        sp->connections.loadRelaxed()->cleanOrphanedConnections(sender);

        if (callbacks_enabled && signal_spy_set->signal_end_callback != nullptr)
            signal_spy_set->signal_end_callback(sender, signal_index);
    }
}

最后

Qt 信号槽源码这块不同版本的是由差异的,本次是按照Qt5.15的源码解读,但原理逻辑流程上大同小异

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值