前言
- 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的实现
- 获取信号对应的槽函数链表
- 获取当前的发送信号的线程ID,并判断是否在发送者线程
- 遍历该信号的所有连接
- 如果不在发送者线程,对接收者进行加锁
- 如果当前时默认连接,并且接收者和发送信号不在同一线程或者指定为Qt::QueuedConnection连接,通过queued_activate封装成事件调用postevnt给接收者
- 当前连接方式为Qt::BlockingQueuedConnection
- 同一个线程抛出死锁警告
- 构造的QMetaCallEvent通过postEvent形式给接收者
- 并且通过QSemaphore阻塞发送者线程,等待槽函数执行完毕将
- 接收者执行槽函数完毕后再执行semaphore.release()来释放信号使其(调用发送信号的对象继续执行)
- 处理所有的直连(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的源码解读,但原理逻辑流程上大同小异