信号&槽连接过程分析
通过Q_OBJECT 宏在编译过程中产生的Moc文件我们已经分析过了。那么QObject是如何通过Moc建立信号&槽之间的绑定的呢?
1 友元
大家可以看到QMetaObject是QObject的友元,那么就意味着可以访问生成的Moc文件中的友元的成员函数和数据成员,QObject就是这样来获取信号&槽在Moc文件中的相对索引、参数类型、参数名、函数名等信号或槽的调用与绑定所需要的关键信息。
二 信号&槽连接过程
信号&槽连接关键调用流程如图:
首先要说清QMetaObjectPrivate数据的由来,然后才能根据调用的序号进行一步一步的说明,QMetaObjectPrivate从何处来?
const QMetaObject CTestMoc::staticMetaObject = {
{ &QObject::staticMetaObject, qt_meta_stringdata_CTestMoc.data,
qt_meta_data_CTestMoc, qt_static_metacall, 0, 0}
};
在初始化这个结构体时,大家注意到第三个参数qt_meta_data_CTestMoc对应的是const uint *data 这个类型,而QMetaObjectPrivate就是由这个数据转换而来,前14个uint保存了这个信号&槽的索引位置、个数、信号个数等信息。而connect这个函数获取信号的索引以及接收方槽的相对索引皆是通过QMetaObjectPrivate这个对象获取。
下面,根据调用序号进行说明。
1 QObject的connect函数。
这个函数是建立两个对象的信号或槽连接调用的函数,由于传递的对象是QObject类型指针、信号或槽的参数是通过调用宏返回的字符串,信号或槽的索引也是通过QMetaObjectPrivate获取,所以,QObject对象与子类的耦合度很低。
2 QMetaObjectPrivate::decodeMethodSignature 获取信号名和格式化信号参数
这个函数主要作用是返回信号或槽名称和信号或槽参数组成的一个”数组”
(1) 信号或槽函数名获取
信号或槽函数名是取SIGNAL或SLOT宏返回的字符串 ‘(’ 字符前所有字符组成的字符串。
(2) 信号或槽参数类型处理 QArgumentTypeArray
参数类型处理的原理是根据逗号分隔匹配的是括号内的字符串,然后根据类型的字符串初始化QArgumentType类型对象,后保存在QArgumentTypeArray中,QArgumentTypeArray这个对象通过一个指针指向一个malloc分配的数组,而数组类型就是QArgumentType。
3 QMetaObjectPrivate::indexOfSignalRelative 获取相对索引
在Moc文件的索引表中已经初始化好了信号&槽的索引、参数、类型等信息,在获取相对索引过程中,检查QMetaObjectPrivate::decodeMethodSignature函数整理的信号或槽信息的入参是否正确就是匹配与Moc文件中信号或槽信息是否一致。
4 QMetaObjectPrivate::signalOffset 计算绝对索引
在构建staticMetaObject 静态成员时,参数QObject::staticMetaObject是QObject的静态元对象,而绝对索引就是元对象的superdata信号数量加上子类的信号的相对索引。
QMetaObjectPrivate::signalOffset函数计算索引的关键代码如下:
for (m = m->d.superdata; m; m = m->d.superdata)
offset += priv(m->d.data)->signalCount;
5 QMetaObjectPrivate::decodeMethodSignature 获取槽函数名和格式化槽参数
处理方式同第二步函数调用。
6 QMetaObjectPrivate::indexOfSlotRelative 获取槽函数相对索引
获取索引原理同获取信号索引原理一致
7 QMetaObject::Connection 建立连接
这个函数是QMetaObject::Connection调用QMetaObjectPrivate::connect实现的。
建立连接主要的两个过程是创建连接的关键,第一个是连接结构体赋值,第二个是连接结构体插入链表处理,其中链表处理尤为关键。
(1) 创建结构体
QScopedPointer
c->sender = s; 发送信号对象
c->signal_index = signal_index; 信号绝对索引
c->receiver = r; 接收对象
c->method_relative = method_index; 槽索相对引
c->method_offset = method_offset; 槽绝对索引的偏移量,与信号绝对索引计算方式相同
c->connectionType = type;
c->isSlotObject = false;
c->argumentTypes.store(types);
c->nextConnectionList = 0;
c->callFunction = callFunction; 回调函数staticMetaObject中的qt_static_metacall
这个就是结构体的赋值,从中可以看出QObject对象是根据索引来调用处理信号和槽的调用。
(2) 插入链表
ConnectionList &connectionList = (*connectionLists)[signal];
首先,在发送对象的连接列表里尾部插入QObjectPrivate::Connection对象
if (connectionList.last) {
connectionList.last->nextConnectionList = c;
} else {
connectionList.first = c;
}
connectionList.last = c;
cleanConnectionLists();
其次,在接受对象首部插入QObjectPrivate::Connection对象
c->prev = &(QObjectPrivate::get(c->receiver)->senders);
c->next = *c->prev;
*c->prev = c;
if (c->next)
c->next->prev = &c->next;