信号槽连接目录
- 信号槽连接
- 1. 信号的连接
- 2 槽的调用
信号槽的连接,其实内部本质还是一个回调函数,主要是维护了信号发送Object的元对象里一个连接的列表。调用connect
函数时,将槽的一系列信息,封装成一个Connection
,在发送信号时,通过这个列表,去回调槽函数。
1. 信号的连接
下面列举一种信号的连接方式,来大致讲解一下信号的连接过程。
//Connect a signal to a pointer to qobject member function // QtPrivate::FunctionPointer::Object返回发送信号的对象类型 template static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer::Object *receiver, Func2 slot, Qt::ConnectionType type = Qt::AutoConnection) { typedef QtPrivate::FunctionPointer SignalType; typedef QtPrivate::FunctionPointer SlotType; Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro::Value, "No Q_OBJECT in the class with the signal"); //compilation error if the arguments does not match. // 检查信号和槽参数是否一致 Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount), "The slot requires more arguments than the signal provides.");// 检查信号和槽参数是否兼容 Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments::value), "Signal and slot arguments are not compatible.");// 检查信号和槽的返回值是否兼容Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible::value), "Return type of the slot is not compatible with the return type of the signal."); const int *types = nullptr;// SignalType -> QtPrivate::FunctionPointer// QtPrivate::ConnectionTypes::types() 返回信号参数的值对应的元类型id列表 if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection) types = QtPrivate::ConnectionTypes::types(); return connectImpl(sender, reinterpret_cast(&signal), receiver, reinterpret_cast(&slot), new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left::Value, typename SignalType::ReturnType>(slot), type, types, &SignalType::Object::staticMetaObject); }
上面主要都是一些基本的信号连接的判断,主要是:
- 信号和槽的参数数量
- 信号和槽的参数是否兼容
- 信号和槽的返回值是否兼容
然后获取信号参数所对应的元类型Id,再就到了一个信号连接的具体内部实现中
QMetaObject::Connection QObject::connectImpl(const QObject *sender, void **signal, const QObject *receiver, void **slot, QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type, const int *types, const QMetaObject *senderMetaObject){ if (!signal) { qWarning("QObject::connect: invalid nullptr parameter"); if (slotObj) slotObj->destroyIfLastRef(); return QMetaObject::Connection(); } int signal_index = -1; void *args[] = { &signal_index, signal };// 根据调用来判断是否存在信号,如果当前类没有就去父类中寻找// 直到找到信号或者是最基层的类// 找到信号的index和信号的对象 for (; senderMetaObject && signal_index superClass()) { senderMetaObject->static_metacall(QMetaObject::IndexOfMethod, 0, args); if (signal_index >= 0 && signal_index signalCount) break; } if (!senderMetaObject) { qWarning("QObject::connect: signal not found in %s", sender->metaObject()->className()); slotObj->destroyIfLastRef(); return QMetaObject::Connection(nullptr); }// 信号下标 signal_index += QMetaObjectPrivate::signalOffset(senderMetaObject); return QObjectPrivate::connectImpl(sender, signal_index, receiver, slot, slotObj, type, types, senderMetaObject);}
同样,我们对这个函数进行分析,第一个片段是对信号发送者是否为空指针的一个判断
if (!signal) { qWarning("QObject::connect: invalid nullptr parameter"); if (slotObj) slotObj->destroyIfLastRef(); return QMetaObject::Connection();}
第二个片段是去找到信号发送者(sender)的元对象类型(Meta Object)以及信号在对象信号中的位置。如果当前对象没有该信号,就去其父类对象去找。直到找到为止。
for (; senderMetaObject && signal_index superClass()) { senderMetaObject->static_metacall(QMetaObject::IndexOfMethod, 0, args); if (signal_index >= 0 && signal_index signalCount) break;}
然后就是进一步调用其内部实现:
QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int signal_index, const QObject *receiver, void **slot, QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type, const int *types, const QMetaObject *senderMetaObject){// 发送对象、接收对象、槽函数对象、信号发送的元对象都不为空 2023-3-11 if (!sender || !receiver || !slotObj || !senderMetaObject) {// 任意一个为空,报错且清理空间,并返回 const char *senderString = sender ? sender->metaObject()->className() : senderMetaObject ? senderMetaObject->className() : "Unknown"; const char *receiverString = receiver ? receiver->metaObject()->className() : "Unknown"; qWarning("QObject::connect(%s, %s): invalid nullptr parameter", senderString, receiverString); if (slotObj) slotObj->destroyIfLastRef(); return QMetaObject::Connection(); }// 去掉const的发送和接受对象 QObject *s = const_cast(sender); QObject *r = const_cast(receiver);// 顺序锁,按照顺序依次去对mutex去上锁// 这里依次对发送和接收者的信号去上锁 QOrderedMutexLocker locker(signalSlotLock(sender), signalSlotLock(receiver)); if (type & Qt::UniqueConnection && slot && QObjectPrivate::get(s)->connections.loadRelaxed()) {// ObjectPrivate::get(s) 获取s对应的d指针// connections 维护了所有的信号槽连接 QObjectPrivate::ConnectionData *connections = QObjectPrivate::get(s)->connections.loadRelaxed(); if (connections->signalVectorCount() > signal_index) {// 获取信号的连接 const QObjectPrivate::Connection *c2 = connections->signalVector.loadRelaxed()->at(signal_index).first.loadRelaxed();// 循环遍历 while (c2) {// 如果已经存在信号和槽的连接,且为uniqueConnection,则返回 if (c2->receiver.loadRelaxed() == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) { slotObj->destroyIfLastRef(); return QMetaObject::Connection(); } c2 = c2->nextConnectionList.loadRelaxed(); } }// 将type与UniqueConnection进行异或,去掉UniqueConnection type = static_cast(type ^ Qt::UniqueConnection); }// 创建一个新的连接 std::unique_ptr c{new QObjectPrivate::Connection}; c->sender = s; c->signal_index = signal_index; QThreadData *td = r->d_func()->threadData; td->ref(); c->receiverThreadData.storeRelaxed(td); c->receiver.storeRelaxed(r); c->slotObj = slotObj; c->connectionType = type; c->isSlotObject = true; if (types) { c->argumentTypes.storeRelaxed(types); c->ownArgumentTypes = false; }// 将新创建的连接加到连接列表中 QObjectPrivate::get(s)->addConnection(signal_index, c.get()); QMetaObject::Connection ret(c.release()); locker.unlock(); QMetaMethod method = QMetaObjectPrivate::signal(senderMetaObject, signal_index); Q_ASSERT(method.isValid()); s->connectNotify(method); return ret;}
同样第一个部分也是对一些个空值的判断
// 发送对象、接收对象、槽函数对象、信号发送的元对象都不为空 2023-3-11 if (!sender || !receiver || !slotObj || !senderMetaObject) {// 任意一个为空,报错且清理空间,并返回 const char *senderString = sender ? sender->metaObject()->className() : senderMetaObject ? senderMetaObject->className() : "Unknown"; const char *receiverString = receiver ? receiver->metaObject()->className() : "Unknown"; qWarning("QObject::connect(%s, %s): invalid nullptr parameter", senderString, receiverString); if (slotObj) slotObj->destroyIfLastRef(); return QMetaObject::Connection(); }
然后就是一个if判断,主要是对Qt::UniqueConnection
连接的一些处理,获取当前对象的信号连接列表,并判断当前要连接的信号和槽,之前有没有被连接过,如果有过连接,就直接返回。
if (type & Qt::UniqueConnection && slot && QObjectPrivate::get(s)->connections.loadRelaxed()) {// ObjectPrivate::get(s) 获取s对应的d指针// connections 维护了所有的信号槽连接 QObjectPrivate::ConnectionData *connections = QObjectPrivate::get(s)->connections.loadRelaxed(); if (connections->signalVectorCount() > signal_index) {// 获取信号的连接 const QObjectPrivate::Connection *c2 = connections->signalVector.loadRelaxed()->at(signal_index).first.loadRelaxed();// 循环遍历 while (c2) {// 如果已经存在信号和槽的连接,且为uniqueConnection,则返回 if (c2->receiver.loadRelaxed() == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) { slotObj->destroyIfLastRef(); return QMetaObject::Connection(); } c2 = c2->nextConnectionList.loadRelaxed(); } }// 将type与UniqueConnection进行异或,去掉UniqueConnection type = static_cast(type ^ Qt::UniqueConnection); }
最后才是创建一个Connection
并将连接的信息以及信号的参数设置进去,然后保存到对象的信号连接容器里。
// 创建一个新的连接 std::unique_ptr c{new QObjectPrivate::Connection}; c->sender = s; c->signal_index = signal_index; QThreadData *td = r->d_func()->threadData; td->ref(); c->receiverThreadData.storeRelaxed(td); c->receiver.storeRelaxed(r); c->slotObj = slotObj; c->connectionType = type; c->isSlotObject = true; if (types) { c->argumentTypes.storeRelaxed(types); c->ownArgumentTypes = false; }// 将新创建的连接加到连接列表中 QObjectPrivate::get(s)->addConnection(signal_index, c.get()); QMetaObject::Connection ret(c.release()); locker.unlock(); QMetaMethod method = QMetaObjectPrivate::signal(senderMetaObject, signal_index); Q_ASSERT(method.isValid()); s->connectNotify(method); return ret;
2 槽的调用
定义一个信号,使用moc
生成moc文件之后,我们可以看到信号函数的定义如下:
// SIGNAL 0void MainWindow::sgnTestFor(){ QMetaObject::activate(this, &staticMetaObject, 0, nullptr);}
我们发射一个信号的时候,我们会这样写:
emit sgnTestFor();
我们可以看关于emit
的定义:
其实emit
关键字什么都没有做,只是标识了一下当前发射了信号。所以本质上,发射一个信号实际上就是直接调用了这个信号的函数,也就是调用了QMetaObject
中的activate
函数。
函数如下:
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(sender, signal_index, argv); else doActivate(sender, signal_index, argv);}
上面的qt_signal_spy_callback_set
暂时不清楚是什么玩意,所以我们不管,直接看具体的doActive
函数
template void doActivate(QObject *sender, int signal_index, void **argv){// 首先获取QObject的private对象 QObjectPrivate *sp = QObjectPrivate::get(sender);// 判断信号是否阻塞 if (sp->blockSig) return; Q_TRACE_SCOPE(QMetaObject_activate, sender, signal_index); if (sp->isDeclarativeSignalConnected(signal_index) && QAbstractDeclarativeData::signalEmitted) { Q_TRACE_SCOPE(QMetaObject_activate_declarative_signal, sender, signal_index); QAbstractDeclarativeData::signalEmitted(sp->declarativeData, sender, signal_index, argv); } const QSignalSpyCallbackSet *signal_spy_set = callbacks_enabled ? qt_signal_spy_callback_set.loadAcquire() : nullptr; void *empty_argv[] = { nullptr }; if (!argv) argv = empty_argv; if (!sp->maybeSignalConnected(signal_index)) { // The possible declarative connection is done, and nothing else is connected if (callbacks_enabled && signal_spy_set->signal_begin_callback != nullptr) signal_spy_set->signal_begin_callback(sender, signal_index, argv); if (callbacks_enabled && signal_spy_set->signal_end_callback != nullptr) signal_spy_set->signal_end_callback(sender, signal_index); return; } if (callbacks_enabled && signal_spy_set->signal_begin_callback != nullptr) signal_spy_set->signal_begin_callback(sender, signal_index, argv); bool senderDeleted = false; { Q_ASSERT(sp->connections.loadAcquire()); QObjectPrivate::ConnectionDataPointer connections(sp->connections.loadRelaxed()); QObjectPrivate::SignalVector *signalVector = connections->signalVector.loadRelaxed();// 信号连接列表,因为一个信号可能连接了多个槽 const QObjectPrivate::ConnectionList *list; if (signal_index count()) list = &signalVector->at(signal_index); else list = &signalVector->at(-1);// 判断当前线程是不是信号发送者的线程 Qt::HANDLE currentThreadId = QThread::currentThreadId(); bool inSenderThread = currentThreadId == QObjectPrivate::get(sender)->threadData.loadRelaxed()->threadId.loadRelaxed();// // We need to check against the highest connection id to ensure that signals added // during the signal emission are not emitted in this emission. uint highestConnectionId = connections->currentConnectionId.loadRelaxed();// 此处也就代表着,一个信号连接的多个槽函数,或者多个连接,会以连接的顺序被触发 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; 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(); }// 判断连接方式是否是队列连接,是队列连接就要丢入事件循环队列中处理 // determine if this connection should be sent immediately or // put into the event queue if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread) || (c->connectionType == Qt::QueuedConnection)) { queued_activate(sender, signal_index, c, argv); continue;#if QT_CONFIG(thread) } else if (c->connectionType == Qt::BlockingQueuedConnection) { // 如果发送对象和接受对象在一个线程,使用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 }// 下面是普通连接,// 如果不在一个线程,并且使用直连,那么接收者就为空 QObjectPrivate::Sender senderData(receiverInSameThread ? receiver : nullptr, sender, signal_index);// 如果是槽函数对象 if (c->isSlotObject) { c->slotObj->ref(); struct Deleter { void operator()(QtPrivate::QSlotObjectBase *slot) const { if (slot) slot->destroyIfLastRef(); } }; const std::unique_ptr obj{c->slotObj}; { Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.get()); obj->call(receiver, argv); } } else if (c->callFunction && c->method_offset 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); } 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是循环遍历信号所连接的槽/信号 } while ((c = c->nextConnectionList.loadRelaxed()) != nullptr && c->id 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); }}
前面的一些基本的判断,我们就忽略,直接找到重要的地方,循环遍历信号所连接的部分。
当信号槽为队列连接,我们需要将信号丢到事件循环里,待事件循环将该信号发送出去。
if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread) || (c->connectionType == Qt::QueuedConnection)) { queued_activate(sender, signal_index, c, argv); continue;#if QT_CONFIG(thread)}
当信号槽为阻塞队列连接(
BlockingQueuedConnection
)时,首先,我们需要判断发送和接收者是不是在一个线程,因为如果连接类型为BlockingQueuedConnection
,发送者和接收者在一个线程,会导致死锁。else if (c->connectionType == Qt::BlockingQueuedConnection) { // 如果发送对象和接受对象在一个线程,使用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}
其他类型的连接如下:
信号的连接是一个槽函数对象
QSlotObject
,就直接调用call
函数if (c->isSlotObject) { c->slotObj->ref(); struct Deleter { void operator()(QtPrivate::QSlotObjectBase *slot) const { if (slot) slot->destroyIfLastRef(); } }; const std::unique_ptr obj{c->slotObj}; { Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.get()); obj->call(receiver, argv); } }
如果是其他类型,就通过
QMetaObject::InvokeMetaMethod
来调用else if (c->callFunction && c->method_offset 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); } 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);}
并且遍历整个列表,将所有相关的连接都调用一遍。
然后我们看QueuedConnection
的连接函数:
代码里,揭示了一点,就是如果我们使用信号槽连接的方式,而信号的参数不是一个元类型或者没用qRegisterMetaType
来注册类型,那么队列连接是不行的,槽函数是不会触发的。
static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connection *c, void **argv){// 存储元类型参数(meta-type argument) const int *argumentTypes = c->argumentTypes.loadRelaxed(); if (!argumentTypes) {// 获取对应的信号 QMetaMethod m = QMetaObjectPrivate::signal(sender->metaObject(), signal);// 获取信号的参数,并检查是否所有参数均为元类型(meta-type) argumentTypes = queuedConnectionTypes(m.parameterTypes()); if (!argumentTypes) // cannot queue arguments argumentTypes = &DIRECT_CONNECTION_ONLY; if (!c->argumentTypes.testAndSetOrdered(nullptr, argumentTypes)) { if (argumentTypes != &DIRECT_CONNECTION_ONLY) delete [] argumentTypes; argumentTypes = c->argumentTypes.loadRelaxed(); } }// 参数不符合要求,返回 if (argumentTypes == &DIRECT_CONNECTION_ONLY) // cannot activate return; int nargs = 1; // include return type while (argumentTypes[nargs-1]) ++nargs; QBasicMutexLocker locker(signalSlotLock(c->receiver.loadRelaxed())); if (!c->receiver.loadRelaxed()) { // the connection has been disconnected before we got the lock return; } if (c->isSlotObject) c->slotObj->ref(); locker.unlock();// 然后通过post一个QMetaCallEvent事件到事件循环队列中去 QMetaCallEvent *ev = c->isSlotObject ? new QMetaCallEvent(c->slotObj, sender, signal, nargs) : new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal, nargs); void **args = ev->args(); int *types = ev->types(); types[0] = 0; // return type args[0] = nullptr; // return value if (nargs > 1) { for (int n = 1; n < nargs; ++n) types[n] = argumentTypes[n-1]; for (int n = 1; n isSlotObject) c->slotObj->destroyIfLastRef(); if (!c->receiver.loadRelaxed()) { // the connection has been disconnected while we were unlocked locker.unlock(); delete ev; return; } QCoreApplication::postEvent(c->receiver.loadRelaxed(), ev);}
代码中我们可以看到,这里是通过post一个QMetaCallEvent
的事件到事件循环中,然后由事件循环去触发槽函数的调用。
好了,对于信号和槽的分析,我们暂时就先分析到这,如果有问题是我上面没有说明的,可以在评论区给我评论,我看到了,看懂了,我就会更新这篇博客的。
谢谢观看 ?