Qt信号槽原理(链接)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt信号槽原理(链接)相关的知识,希望对你有一定的参考价值。

参考技术A Sender

Sloter

Connection

那么Qt是怎么将这两个信号槽链接起来的呢?接下来看一看connect的源码:
在Qt中connect有很多个重载,大致可以分为2类:一种是qt4的Sender(),Slot()方式,另一种是qt5的模板方式进行链接

用这种方式链接会调用上图中的第一个connect函数,其内部的实现如下(太多了我们分段看看)

这一部分主要是在检测传入的信号是否属于sender,不是的话会告警并直接返回,这也是这种方式不好的地方,必须写正确,而且写错了如果不看告警的话是不知道的。
这里面两个主要的函数:

我们看看他们是如何实现的

通过Qt的元对象(QMetaObject)获取,到这里需要讲一讲QMetaObject是个什么东西,它是个类,内部有一个结构体d如下:

OK,继续回去看staticMetaObject这个成员的初始化,我们在这里先只关注stringdata和data这个两个参数,这里用到了,后面会继续有其他参数的意义,在这个地方stringdata被初始化成了qt_meta_stringdata_TestSender.data,data被初始化成了qt_meta_data_TestSender。它们是啥,还是在moc文件里

这两个一个保存着信号的名字,一个保存着信号的返回值,参数类型和参数个数。
到这里就可以通过这两个参数和链接时传入的signal字符串对比找到信号在sender中的的位置,当然因为继承的存在,所以这个位置不是真是的位置,还需要找到signalOffset这个函数找到在这个类中的位置。怎么找的呢?其实MetaObject中的data最终会被转换成这个

因为QObject有2个信号,所有这个地方获取到的索引位置为3

这一段跟上面差不多,用来检车receiver和槽函数是否对应

这个地方主要是检查信号槽的参数(比如信号的参数不能比槽的参数少等)
接下来就会进入另一个connect函数,他的实现

到这里就要看到信号槽是如何链接起来的了,每个QObject的对象都会维护着多个Connection的链接对象,这里面存储着信号的发送者、信号的接收者等一系列信息

至此信号差就链接起来了,最终会以Connection对象的方式存放在每个QObject对象的connectionlist里面,这种是Qt4老的链接方式

Qt5的新链接方式相比Qt4要简单很多,因为是基于模板实现的,因此可以直接找到函数的地址,最终也会放到链接列表里,来看看与Qt4的区别吧。
不知道是否还记得最开始connect函数的几种重载,这里用到的是下面红框里的方式(模板)
让我们先看一种

它会调用这个实例化的模板函数

看到了吧,这个函数有2个功能,一个是执行信号(因为信号可能会很信号链接,具体怎么执行到的后面会讲),另一个是获取信号函数在对象的偏移量(如何获取的呢,其实就是对比传入的信号函数的地址和实际信号的地址,这里a0就是要获取的偏移量,a1就是信号函数的地址)

这个地方需要注意的是connecImpl与connect不同的是吧receiver对象包装成了一个QSlotObject的对象,它是这样的

这个类接受一个QtPrivate::FunctionPointer<Func>的对象,这个是个啥?着其实是个模板类,用来识别槽函数的地址,在connect的时候也用到了这个,具体怎么实现的可以放出来,就是模板的推导比较麻烦,可以简单的理解成通过这个可以判断槽函数是否是receiver的成员函数

接着往下讲,不知道是否还记得之前的connectImpl函数,它获取到了信号的偏移量,接着又调用了一个connectImpl,它是这样的

看到这里有没有熟悉的感觉,跟Qt4的其实差不多,也是将信号槽存储到connection对象内,在添加到sender的connectionList里,不过这里存储的槽不是一个函数,而是一个QSlotObject对象的地址,看看Connection结构的内容可以发现使用Union实现的,存储的都是一个地址
到这里就讲完了Qt4和Qt5信号槽的链接部分,不知道是否讲的明白,moc文件里生成的东西基本就是Qt信号槽的核心,每一个都有其作用,都对应着QObjcet底层的数据,也是Qt元对象的核心。

Qt信号槽原理(调用)

参考技术A 接上一章链接部分,继续讲信号槽是如何调用的
首先看看信号是如何触发的,一般都是这么写:
emit sender.test_Signal(1);
这个emit是啥,其实啥也不是,就是一个空的define

它只是用来标记这是一个信号方便阅读,其实这个信号本身也是一个函数,只不过我们没有实现,这是语言的基础,定义了函数肯定是要实现的,那么它的实现在哪呢,答案还在moc文件内部,以之前的例子为例,它的实现是这样的

在这里将信号的参数包装成了void*的数组,然后调用元对象的activate函数,如下

这一部分主要是判断信号是否有链接槽函数

之前讲过Qt4的槽函数是存储在Connection的callFunction对象,如果这个不为空就会判断为Qt4的链接方式

这种就比较简单,直接调用callFunction即可,这个callFunction是什么呢,不知道是否还记得,之前链接的时候讲过,它就是moc文件里的qt_static_metacall,如果不记得了回去看看,这里在放出

是否还记得Qt5的槽放在哪呢,它放在一个QSlotObject对象里,Connection保存的是这个对象的地址,因此通过判断这个地址是否为空就能判断是否为Qt5的调用方式

还记得之前将的QSlotObject吗,它是QSlotObjectBase的子类,为了方便再贴出来看看

这里面调用的又是 FuncType(也就是FunctionPointer<Func>)的call函数,好记得它是什么吗,就是用来判断槽函数是否为receiver成员函数的那个模板类,再贴出来看看

到此就会调用到我们的槽函数,Qt5方式的调用也就结束了

Qt信号槽在链接的时候最后一个参数代表的是链接方式,包括5中

一般用的比较多的就是AutoConnection和QueuedConnection,如果是auto的方式,调用时会判断信号所在线程和槽所在线程是否是一个线程,如果不是就是queue的方式调用,具体是这样判断的

如果是QueuedConnection,会把当前的信号包装成一个QMeCallEvent的事件,进入到事件循环来调用槽函数

2、用了QueuedConnection是不是就代表是异步了呢?

那么事件循环是怎么调用到这个函数的呢?

以上是关于Qt信号槽原理(链接)的主要内容,如果未能解决你的问题,请参考以下文章

QT信号槽实现原理

qt信号和槽机制的问题

Qt编程中信号与槽机制可以用啥方法替换

Qt的信号槽机制可以保障线程安全吗

QT的信号槽机制

androidUI卡顿原理分析及Vsync信号机制