QT源码剖析-QT对象通信机制信号槽的绑定具体实现
本文详细介绍QT核心机制之一 信号和槽。
我们在此根据Qt源代码一步一步探究其信号槽的实现过程。
核心知识点:
模板元编程技术、 Qt moc预编译机制、 QObject类。
目录
1. QObject类介绍:
2: 相关助手类介绍:
2.1 类型(函数指针)萃取模板元函数。
2.2 函数调用器FunctorCall元函数
3. 信号槽需要Qt moc预编译器的支持
4. 信号槽绑定入口: QObject类的静态模板方法(connect方法)
4.1 从入口开始谈起,先看看QObject类的connect 静态模板方法对于信号和槽绑定的声明及其实现。
5. 信号发送(调用 emit)
6. 总结
1. QObject类介绍:
QObject类是整个Qt框架对象的核心模型,此类提供了大量Qt特性。包括对象的无缝通信机制信号槽、反射、动态属性、定时、对象树生命周期管理等。
2: 相关助手类介绍:
信号槽的本质是两个对象的函数地址映射,下面对信号槽实现过程中的一些助手类进行介绍。
2.1 类型(函数指针)萃取模板元函数。
注:下文中会把类型萃取模板元函数称之为类型萃取器。
FunctionPointer萃取器声明部分(Qt 信号槽绑定过程中会大量使用此萃取器来提取信号槽中的相关类型)
FunctionPointer偏特化版本之一:非CV的成员函数指针类型偏特化版本。
文中仅贴出此偏特化版本。为保证篇幅不至于太长,其他偏特化版本不做介绍基本大同小异。
从图中可以看出,FunctionPointer非CV类的成员函数指针类型偏特化版本主要做以下事情。
a. 提取类成员函数指针类型所属的类类型。b. 提取类成员函数指针类型函数形参参数包类型(形参列表打包到List模板元函数中)。
c. 提取类成员函数指针返回值类型。d. 重定义类成员函数指针类型。
e. 提取类成员函数指针类型的形参数量和是否为类的成员函数枚举值。
f. 提供一个此函数指针类型的分发调用静态方法。
2.2 函数调用器FunctorCall元函数
看看2.1 节萃取器会发现类型萃取包装的call分发函数会转而调用此元函数。(包装主要原因是解信号形参数量参数包)
此元函数的作用是调用指定对象o的成员函数f, 并解包信号函数形参数量参数包。
先记得这两个助手类,信号槽绑定与信号发射过程会大量使用到。
3. 信号槽需要Qt moc预编译器的支持
此处不对Qt moc预编译机制做详细介绍,只是谈谈它做了哪些事。 Qt moc详细实现原理单独博文在介绍。
首先我们看看moc预编译器对包含Q_OBJECT宏的类做了哪些事件。
举个栗子 定义一个TestObject类:
图中源码代码中Q_OBJECT宏必须添加,Qt moc预编译器根据此标识在编译前来生成moc_xx.cpp 实现文件。
编译后产生如下文件:
moc_testobject.cpp
void TestObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{if (_c == QMetaObject::InvokeMetaMethod) {auto *_t = static_cast<TestObject *>(_o);Q_UNUSED(_t)switch (_id) {case 0: _t->sig_void(); break;case 1: _t->sig_int((*reinterpret_cast< int(*)>(_a[1]))); break;case 2: _t->sig_char((*reinterpret_cast< char(*)>(_a[1]))); break;case 3: _t->sig_qstring((*reinterpret_cast< QString(*)>(_a[1]))); break;case 4: _t->sig_int_char((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< char(*)>(_a[2]))); break;case 5: _t->slot1(); break;default: ;}} else if (_c == QMetaObject::IndexOfMethod) {int *result = reinterpret_cast<int *>(_a[0]);{using _t = void (TestObject::*)();if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_void)) {*result = 0;return;}}{using _t = void (TestObject::*)(int );if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_int)) {*result = 1;return;}}{using _t = void (TestObject::*)(char );if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_char)) {*result = 2;return;}}{using _t = void (TestObject::*)(QString );if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_qstring)) {*result = 3;return;}}{using _t = void (TestObject::*)(int , char );if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_int_char)) {*result = 4;return;}}}
}QT_INIT_METAOBJECT const QMetaObject TestObject::staticMetaObject = { {QMetaObject::SuperData::link<QObject::staticMetaObject>(),qt_meta_stringdata_TestObject.data,qt_meta_data_TestObject,qt_static_metacall,nullptr,nullptr
} };const QMetaObject *TestObject::metaObject() const
{return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}void *TestObject::qt_metacast(const char *_clname)
{if (!_clname) return nullptr;if (!strcmp(_clname, qt_meta_stringdata_TestObject.stringdata0))return static_cast<void*>(this);return QObject::qt_metacast(_clname);
}int TestObject::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) {if (_id < 6)qt_static_metacall(this, _c, _id, _a);_id -= 6;} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {if (_id < 6)*reinterpret_cast<int*>(_a[0]) = -1;_id -= 6;}return _id;
}// SIGNAL 0
void TestObject::sig_void()
{QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}// SIGNAL 1
void TestObject::sig_int(int _t1)
{void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };QMetaObject::activate(this, &staticMetaObject, 1, _a);
}// SIGNAL 2
void TestObject::sig_char(char _t1)
{void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };QMetaObject::activate(this, &staticMetaObject, 2, _a);
}// SIGNAL 3
void TestObject::sig_qstring(QString _t1)
{void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };QMetaObject::activate(this, &staticMetaObject, 3, _a);
}// SIGNAL 4
void TestObject::sig_int_char(int _t1, char _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, 4, _a);
}
看到这个文件我想会打消这样一个疑问:为什么声明了信号函数自己没有实现编译器确没有报错?
这是因为Qt moc预编译器在编译器介入之前已经帮你做好了这件事。
moc_testobject.cpp 还定义了一些函数如qt_static_metacall、metaobject、qt_metacast、qt_metacall 为什么定义这些函数?
我们接着来看TestObject类声明中的Q_OBJECT宏内容:
: O_OBJECT 宏声明了与moc_testobject.cpp中完全匹配的函数声明部分。
可以看出moc预编译器会在编译时期检查类的Q_OBJECT宏自动生成实现文件。
这套机制为QT实现反射与信号槽提供了支持。
4. 信号槽绑定入口: QObject类的静态模板方法(connect方法)
4.1 从入口开始谈起,先看看QObject类的connect 静态模板方法对于信号和槽绑定的声明及其实现。
我们使用下面示例demo来开始进入Qt 信号槽的绑定的过程。
绑定信号过程与调用信号代码:
首先connect方法作为信号与槽的绑定入口函数,形参与特定类型无关。符合通用基础库的一般设计与使用流程。
函数形参解析:
connect方法模板类型Func1、Func2由实参(信号函数地址与槽函数地址)自动推导。
parameter1: FuntionPoniter 萃取器获取Func1成员函数指针所在的类类型对象指针。(信号所在的对象)
parameter2:Func1信号成员函数指针
parameter3: FuntionPoniter 萃取器获取Func2成员函数指针所在的类类型对象指针。(槽函数所在的对象)
parameter4: Func2槽成员函数指针。
parameter5: 信号槽连接类型. (5种类型的信号槽连接区别可以自行查看文档,此处主要介绍信号槽绑定实现过程)
函数实现解析:
该函数主要是做了一些类型校验性工作,对信号成员函数指针与槽成员函数指针的形参数量、形参类型、返回值类型是否符合一致性要求以及类型是否匹配做检查。
槽函数地址交给QSlotObject对象, 具体的连接丢给connectImpl去实现。
至于检查规则有兴趣可以查看CheckCompatibleArguments与AreArgumentsCompatible模板元函数。
在调用connectImpl方法时传递的第四个实参为QSQlotObject对象,此对象包装了槽函数地址。QSQlotObject函数包装器的实现:
Connect函数在调用connectImpl方法对QSQlotObject模板类实例化时可以推导出 QSQlotObject模板参数类型。
Func对应槽成员函数指针类型, Args对应List类型( 函数形参类型列表,形参类型链表),R对应信号返回值类型。
QSlotObject 函数包装器实现比较简单,将自己的impl静态方法地址交由父类来管理。 本身保存槽成员函数指针。
1.3.2 connectImpl 信号槽绑定实现函数。
函数解析:
先对函数形参做个简要介绍, 相信看了图中函数的声明也基本能明白了。
分别为信号发送对象、指向信号函数地址指针、 响应对象、指向槽函数地址的指针、槽函数包装器对象、连接类型、类型数组、发送者元对象。
此函数主要作用是根据信号所在类的元对象找出信号索引位置(向上查找)。
我们可以继续深挖看看Qt是怎么去查找信号索引的。
senderMetaObject->static_metacall(QMetaObject::IndexOfMethod, 0, args);
args实参构造了一个int(待返回的信号索引 OUT)和信号函数地址(IN)的数组进行传入。
static_metacall实现为:
QMetaObject的唯一成员属性d的声明为:
到这里我们就需要知道的在什么地方怎样构造的QMetaObject的对象。
TestObject类声明中的Q_OBJECT宏展开后的第一条语句展为静态常量变量QMetaObject的声明(翻阅上文Q_OBJECT宏声明),
我们看看在moc_testobject.cpp中是怎么实例化这个对象的。
所以最终senderMetaObject调用static_metacall其实也就是对TestObject的qt_static_metacall的调用。
qt_static_metacall的定义在moc_testobject.cpp文件中:
可以回头看看qt_static_metacall函数的实现过程。
这里函数所做的事情基本就是根据传入进来的信号函数地址与本类中的所有信号函数地址进行匹配来获取需要绑定信号的索引了。
qt是默认按照信号函数声明的顺序从0开始给信号函数进行编号的。
最后得到的索引还需累加父类中所有信号的总数。
好了,信号索引的查找就介绍到这里。 我们继续来看看QObject的connectImpl函数对QObjectPrivate::connectImp的调用。
说到Qt中XXPrivate的对象需要说明的是Qt对类基本使用Pimpl惯用法。
Pimpl惯用法解释:
如果你曾经与过多的编译次数斗争过,你会对Pimpl
(Pointer to implementation)惯用法很熟悉。
凭借这样一种技巧,你可以将一个类数据成员替换成一个指向包含具体实现的类或结构体的指针,
并将放在主类(primary class)的数据成员们移动到实现类去(implementation class), 而这些数据成员的访问将通过指针间接访问.(Effective Modern C++ item22 条款的解释)
个人认为这样做还有一个优点是主类对象和成员属性类对象内存分离,当主类发生拷贝构造时只是一个成员implementation class的地址拷贝这样拷贝构造的成本是极低的当然要注意浅拷贝问题(会不会高于移动构造效率呢?(#^.^#))。
这里QObjectPrivate就是对应QObject(主类)的implementation class。
这里继续探究QObjectPrivate::connectImp函数的实现:
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)
{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 null parameter", senderString, receiverString);if (slotObj)slotObj->destroyIfLastRef();return QMetaObject::Connection();}QObject *s = const_cast<QObject *>(sender);QObject *r = const_cast<QObject *>(receiver);QOrderedMutexLocker locker(signalSlotLock(sender),signalSlotLock(receiver));if (type & Qt::UniqueConnection && slot && QObjectPrivate::get(s)->connections.loadRelaxed()) {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) {if (c2->receiver.loadRelaxed() == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) {slotObj->destroyIfLastRef();return QMetaObject::Connection();}c2 = c2->nextConnectionList.loadRelaxed();}}type = static_cast<Qt::ConnectionType>(type ^ Qt::UniqueConnection);}std::unique_ptr<QObjectPrivate::Connection> 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;
}
函数实现关键代码段解析:
此函数基本就是信号和槽绑定过程中最底层函数了,函数最终返回的是一个QMetaObject::Connection连接对象。
函数形参到这里就不介绍,基本与上一层级的调用一致。
函数开始处对参数有效性做检查。函数实现过程中是加锁了的, 可以得到信号槽的绑定是线程安全的。
函数中构造了QObjectPrivate::Connection独占共享指针(注意区分与QMetaObject::Connection不同)记录了信号地址与槽地址以及所在对象指针、连接类型、信号函数地址索引等信息。
接着获取信号函数所在对象的Private对象(implementation class)对QObjectPrivate的addConnection函数进行调用。
addConnection函数形参为信号索引和QObjectPrivate::Connection指针。
addConnection函数实现为:
void QObjectPrivate::addConnection(int signal, Connection *c)
{Q_ASSERT(c->sender == q_ptr);ensureConnectionData();ConnectionData *cd = connections.loadRelaxed();cd->resizeSignalVector(signal + 1);ConnectionList &connectionList = cd->connectionsForSignal(signal);if (connectionList.last.loadRelaxed()) {Q_ASSERT(connectionList.last.loadRelaxed()->receiver.loadRelaxed());connectionList.last.loadRelaxed()->nextConnectionList.storeRelaxed(c);} else {connectionList.first.storeRelaxed(c);}c->id = ++cd->currentConnectionId;c->prevConnectionList = connectionList.last.loadRelaxed();connectionList.last.storeRelaxed(c);QObjectPrivate *rd = QObjectPrivate::get(c->receiver.loadRelaxed());rd->ensureConnectionData();c->prev = &(rd->connections.loadRelaxed()->senders);c->next = *c->prev;*c->prev = c;if (c->next)c->next->prev = &c->next;
}
此函数即为信号槽绑定关系的缓存部分了,介绍此函数前有必要贴下QObjectPrivate对于信号槽绑定的缓存数据结构进行介绍下:
由上图可知QObjectPrivate内部定义了多个用于存储信号槽绑定结构的内嵌类,类关系结构如上图所示。
上面QObjectPrivate::connectImp 函数中即构造了图中Connect对象并对相关属性进行了赋值最后传入到addConnect
SignVector: 信号向量 继承ConnectionOrSignalVector
Connection: 连接向量 继承ConnectionOrSignalVector
由它们的共同父类 ConnectionOrSignalVector 连接或信号向量可知他们都是分别存储信号与连接的容器。
也得知一个对象对于一个信号的多次绑定以及多个信号的绑定都是基于链表来存储的。
对照着此类关系图这里我们在一步步分段解析addConnection函数 。
1. 由于前面内容偏多,首先再次确认下当前QObjectPrivate对象属于发送者对象(QObject)的implementation class 对象。
2. 函数中ensureConnectionData函数功能为创建或者获取QObjectPrivate connections成员属性。(见上类UML图 QObjectPrivate类属性)
3. cd->resizeSignalVector(signal + 1) 构造或重置(扩容)SignalVertor对象。(此SignalVertor对象会按照8的倍数分配ConnectionList需要的存储空间加自己本身大小)
构造分配SignalVertor对象源码为:
void resizeSignalVector(uint size) {SignalVector *vector = this->signalVector.loadRelaxed();if (vector && vector->allocated > size)return;size = (size + 7) & ~7;SignalVector *newVector = reinterpret_cast<SignalVector *>(malloc(sizeof(SignalVector) + (size + 1) * sizeof(ConnectionList)));int start = -1;if (vector) {memcpy(newVector, vector, sizeof(SignalVector) + (vector->allocated + 1) * sizeof(ConnectionList));start = vector->count();}for (int i = start; i < int(size); ++i)newVector->at(i) = ConnectionList();newVector->next = nullptr;newVector->allocated = size;signalVector.storeRelaxed(newVector);if (vector) {vector->nextInOrphanList = orphaned.loadRelaxed();orphaned.storeRelaxed(ConnectionOrSignalVector::fromSignalVector(vector));}}
4. ConnectionList &connectionList = cd->connectionsForSignal(signal) 获取当前信号索引所在的ConnectionList对象。
我们在上文中知道每个信号发送对象的sign_index 都是唯一的包括父类,所以在QObjectPirvate对象connects成员属性signalVertor容器中都存在一份永久对应的ConnectionList对象。
5.
ConnectionList &connectionList = cd->connectionsForSignal(signal);if (connectionList.last.loadRelaxed()) {Q_ASSERT(connectionList.last.loadRelaxed()->receiver.loadRelaxed());connectionList.last.loadRelaxed()->nextConnectionList.storeRelaxed(c);} else {connectionList.first.storeRelaxed(c);}
获取当前信号索引所对应的ConnectionList对象并把实参c(QObjectPrivate::Connection)对象赋值给ConnectionList对象的first或者last的nextConnectionList属性(至于给谁就看该信号是不是第一次绑定了 ConnectList 的first给到第一次信号槽绑定形成的Connection对象, 后续last会给到最后一次绑定的Connection对象, Connection内部形成一个双向Connection链表)
6.
c->id = ++cd->currentConnectionId;c->prevConnectionList = connectionList.last.loadRelaxed();connectionList.last.storeRelaxed(c);
对当前连接对象c的连接id进行累加计数,并把上次连接到该信号的Connection绑定到此连接对象prevConnectionList中,形成一个链表。
结合5、6我们知道当前连接对象需要绑定到上一个连接对象Connection prevConnectionList上, 当前连接对象的prevConnectionList 需绑定到上一个连接对象形成一个双向链表。
7.
QObjectPrivate *rd = QObjectPrivate::get(c->receiver.loadRelaxed());rd->ensureConnectionData();c->prev = &(rd->connections.loadRelaxed()->senders);c->next = *c->prev;*c->prev = c;if (c->next)c->next->prev = &c->next;
此处为addConnection函数结尾部分,获取信号接收对象的QObjectPrivate对象。
文字解释确实比较绕,还是上一个最终信号槽绑定连接对象在内存中的布局图。
QObjectPrivate成员属性ConnectionData signalVector* 如图中所示: 指向resizeSignalVector创建的内存块。(connectionList 内存块默认是8的倍数,图中并不准确)
每个信号按照索引序号分配好ConnectList固定内存块。
图中示例了第0个信号被绑定了3次。 图中ConnectList内存块只存在first 和last Connection。 中间last ... 表示中间态。新绑定槽函数都会更新last。
所以first指向第一个Connection对象,绑定第二个槽函数时, last指向第二个Connection对象,图中虚线表示。 当绑定第3个槽函数时,last继续更新指向第三个Connection对象。
Connection对象链表样式如图中下半部分显示。
Connect方法最终方法一个QObject::Connect对象。 QObject::Connect包含了QObjectPrivate::Connection指针。
5. 信号发送(调用)
示例代码中: emit to->sig_int(100); 其实emit写不写无所有,本身就是一个空宏。 只不过方面于字面上的理解而已。
对于信号的调用则是直接进入由Qt moc帮我们生成的实现文件中:
// SIGNAL 1
void TestObject::sig_int(int _t1)
{void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
当发送sig_int信号时则进如上述代码段,首先构造_a void* 数组实参,取所有sig_int 参数地址放入到_a数组,数据0为位置始终用nullptr填充(至于原因我们后面再看)
接着继续调用QMetaObject::activate函数,形成分别为发送信号的对象地址、发送信号对象的QMetaObject对象地址、信号索引、信号参数数组。
我们继续观察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<true>(sender, signal_index, argv);elsedoActivate<false>(sender, signal_index, argv);
}
此函数唯一做的事情是获取信号最终索引(包含父类的信号偏移)
紧接着交由doActivate去做。
doActivate实现代码:
template <bool callbacks_enabled>
void doActivate(QObject *sender, int signal_index, void **argv)
{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 connectedif (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 < signalVector->count())list = &signalVector->at(signal_index);elselist = &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 interfereQMutexLocker lock(signalSlotLock(receiver));receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();}// determine if this connection should be sent immediately or// put into the event queueif ((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) {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<QtPrivate::QSlotObjectBase, Deleter> obj{c->slotObj};{Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.get());obj->call(receiver, argv);}} 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);} 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);}
}
函数关键代码段解析:
此函数比较长,主要功能相信大家也能猜出来了
1. 根据信号索引检查该信号是否存在连接。
if (!sp->maybeSignalConnected(signal_index)) {// The possible declarative connection is done, and nothing else is connectedif (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;}
看看 maybeSignalConnected实现代码。
bool QObjectPrivate::maybeSignalConnected(uint signalIndex) const
{ConnectionData *cd = connections.loadRelaxed();if (!cd)return false;SignalVector *signalVector = cd->signalVector.loadRelaxed();if (!signalVector)return false;if (signalVector->at(-1).first.loadAcquire())return true;if (signalIndex < uint(cd->signalVectorCount())) {const QObjectPrivate::Connection *c = signalVector->at(signalIndex).first.loadAcquire();return c != nullptr;}return false;
}
此函数实现过程就可以看出检查过程实际就是判断SignalVertor容器中指定sign_index位置的ConnectionList first是否为nullptr。
2. 取出指定信号索引处的connectionList对象
QObjectPrivate::ConnectionDataPointer connections(sp->connections.loadRelaxed());QObjectPrivate::SignalVector *signalVector = connections->signalVector.loadRelaxed();const QObjectPrivate::ConnectionList *list;if (signal_index < signalVector->count())list = &signalVector->at(signal_index);elselist = &signalVector->at(-1);
3. 循环调用绑定在此信号上的槽函数
4. 示例sample 真正的调用槽函数处
obj即为槽函数包装器器 (QSlotObjectBase) 这些数据都可以在Connection连接对象中找到,绑定过程中有存储。
这里在看看对QSlotObjectBase的调用,传入了接收对象和信号发射时传入的参数。
我们知道这个QSlotObjectBase对象实在QObject::Connect构造的,m_impl对应的实际时子类QSlotObject的静态函数impl函数。
上文有贴过QSlotObject模板类的实现代码。
到了这里回看第一节的助手类发现最终只是由元函数进行了下转发调用 ,最终调用到指定接收对象的指定成员方法。
6. 总结
6.1 本文只对sample代码走向进行了梳理,信号连接类型为AutoConnection且信号槽没有跨线程调用。 对于信号类型的使用和跨线程调用可以结合上文源码流程看看Qt针对于其他分支的处理。
6.2 相信看完本文,也会发现QT信号槽的绑定其实并不是特别复杂。 只是加入了模板元编程、MOC预编译机制、以及内部大量的引用计数和原子对象增加了阅读源码的复杂性。
6.3 看完Qt 信号槽的绑定过程后,是否可以不写Q_OBJECT宏也能让QObject子类来支持信号槽绑定?
6.4 是否可以实现一个对指定对象的信号拦截,来实现无侵入式的代码编写呢 ?
6.5 理解完会发现信号槽的绑定也离不开Qt的反射机制,Qt大部分新的特性都是相互结合使用的。
QT源码剖析-QT对象通信机制信号槽的绑定具体实现相关推荐
- python源码剖析代码例子_Python源码剖析笔记5-模块机制
python中经常用到模块,比如import xxx,from xxx import yyy这样子,里面的机制也是需要好好探究一下的,这次主要从黑盒角度来探测模块机制,源码分析点到为止,详尽的源码分析 ...
- Python源码剖析笔记5-模块机制
本文简书地址: http://www.jianshu.com/p/14586ec50ab6 python中经常用到模块,比如import xxx,from xxx import yyy这样子,里面的机 ...
- Qt源码剖析-智能指针
目录 智能指针是什么? 为什么使用QT智能(smart)指针? Qt提供了哪些智能指针? QSharedPointer类模板(smart指针类)源码分析 1. QSharedPointer类模板对于模 ...
- Spark源码分析之BlockManager通信机制
BlockManagerMasterEndpoint主要用于向BlockManagerSlaveEndpoint发送消息,主要分析他们都接受哪些消息,接受到消息之后怎么处理? 一BlockManage ...
- Python源码剖析[19] —— 执行引擎之一般表达式(2)
Python源码剖析 --Python执行引擎之一般表达式(2) 本文作者: Robert Chen(search.pythoner@gmail.com ) 3.2 Simple.py 前面我 ...
- 基于Qt5.14.2和mingw的Qt源码学习(三) — 元对象系统简介及moc工具是如何保存类属性和方法的
基于Qt5.14.2和mingw的Qt源码学习(三) - 元对象系统简介及moc工具是如何保存类属性和方法的 一.什么是元对象系统 1.元对象系统目的 2.实现元对象系统的关键 3.元对象系统的其他一 ...
- Qt之布局设置setLayout详解-源码剖析(下)
一.简述 大家好,我是前行中的小猪,今天呢给大家继续上一篇Qt之布局设置setLayout详解(上)之后的内容,再给大家进行一下拓展. 1.1 setLayout源码剖析 上篇我们说到如何清空部件上的 ...
- QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数
版权声明 请尊重原创作品.转载请保持文章完整性,并以超链接形式注明原始作者"tingsking18"和主站点地址,方便其他朋友提问和指正. QT源码解析(一) QT创建窗口程序.消 ...
- Chrome源码剖析、上--多线程模型、进程通信、进程模型
Chrome源码剖析.上 原著:duguguiyu. 整理:July. 时间:二零一一年四月二日. 出处:http://blog.csdn.net/v_JULY_v. 说明:此Chrome源码剖析很大 ...
最新文章
- 示波器地线应用注意问题
- Exchange2010server证书申请及分配服务
- ReverseMe-120(base64解码表) 逆向寒假生涯(21/100)
- hdu 5093 二分匹配
- 【explain】MySQL联表查询中的驱动表
- Istio 1.7——进击的追风少年
- matlab常用函数辨析
- python subprocess阻塞
- php 调用cron jobs,在CentOS 6.4中使用CronJobs执行PHP不起作用?
- 为jquery.AutoComplete添加触发事件
- 【AudioVideo】MediaRecorder概述(21)
- 访问母版页控件、属性、方法及母版页中调用内容页的方法
- Java RandomAccessFile示例
- 如何利用EasyRecovery恢复c盘已删文档
- FPGA那些事(黑金动力社区)-笔记
- WizTree v4.03 最快的磁盘空间分析工具中文便携版
- java开发常用排名前16的Java工具类
- 自制AI图像搜索引擎[笔记]
- 陀螺产业区块链案例库 | 终本案件智能管理系统
- C语言 求最大值和最小值
热门文章
- csapp之lab:shell lab
- 百度智能云身份证识别API的使用
- 基于Android的学生学习打卡监督系统
- Pytorch中tensor.view().permute().contiguous()函数理解
- Linux的LVM磁盘卷轴深度学习
- java语言基础总结ppt_我的java基础知识总结ppt
- Tkinter编程应知应会(20)-主菜单
- GC参数解析 UseSerialGC、UseParNewGC、UseParallelGC、UseConcMarkSweepGC
- uni-app 车牌录入组件封装(支持新能源)
- 滤波算法 | 无迹卡尔曼滤波(UKF)算法及其MATLAB实现