信号到槽是QT的标识性特征之一.
本文打算用简明扼要的源码分析,来告诉读者信号与槽是怎么实现的.
不然要分析的太多,又复杂,会让人蒙掉----他说的到底是啥?
本文讲述的是一个类MySender向另一个MyReceiver发信号,并响应的故事.
先来熟悉一下宏吧,混个脸熟:
# define slots
# define signals protected
# define emit //可忽略
# define SLOT(a) qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
可以看出,这些宏并没有什么特别,但是QT会用moc对slots和signals这两个做特殊处理.可以思考一下,标准的C++并没有信号和槽这个东西的,QT增加了这个东西,他(QT)必然会住类里面添加一些信息.
关键点: 要使用信号与槽功能,必须要继承QObject并在类里面加Q_OBJECT宏.
看看我的MySender类,继承自QObject并在类的第一行里加了Q_OBJECT宏.
class MySender : public QObject
{
Q_OBJECT
public:
MySender(){}
~MySender(){}
public:
void UiMakeValueChanged()
{
emit valueChanged(100);
}
signals:
void valueChanged(int newValue);
};
另外,我在MySender里面定义了一个信号函数void valueChanged(int newValue)
正如你看到的,我并没有实现这个函数,那么谁来实现的呢,答案是QT的moc处理
QT moc处理后,会自动生成一个文件moc_mysender.cpp里面实现了void valueChanged(int newValue),我们看看吧
// SIGNAL 0
void MySender::valueChanged(int _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
这个实现先把参数放到一个void*数组里面,然后就调用了QMetaObject::activate.
先说到这里,后面再来分析.
再看看我们的信号接收者的实现
class MyReceiver : public QObject
{
Q_OBJECT
public:
MyReceiver(){}
~MyReceiver(){}
protected slots:
void setValue(int value)
{
qDebug() << value << "\r\n";
}
};
实现也比较简单,就是定义了一个槽,然后在里面打印一下参数value.
QT的moc也帮忙实现了一个文件叫moc_myreceiver.cpp.
好了,有了信号,有了槽,还需要做一件事情:connect,把信号和槽连接起来.
看代码:
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MySender s;
MyReceiver r;
QObject::connect(&s, SIGNAL( valueChanged(int) ),&r, SLOT( setValue(int)) );
s.UiMakeValueChanged();
return a.exec();
}
QObject::connect就作用就是把信号和槽连接起来,四个参数也非常形象:
bool QObject::connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type)
sender:发信号的家伙
signal:发送的信号
receiver:接收信号的家伙,
method:接收信号的处理函数(就是slot)
type:不表,今天只讲最简单的,用默认值AutoConnection,后续有机会讲这个东东.
connect后, 调用s.UiMakeValueChanged就可以发出信号啦,当然,没有意外的话,接收者的slot就会响应信号
void UiMakeValueChanged()
{
emit valueChanged(100); //emit可省略,没啥用,只是好看
}
让我们来运行一下:
运行成功,100被打印出来了.
那我们现在开始提出问题:
1.connect到底干了什么,他是产生了一个连接(connection)吗?
2.如果产生了一个连接,这个连接存在内存哪里的?
3.当发出(emit)一个信号后,是如何调到接收者的slot的?
源码之前,了无秘密,让我们去看看源码吧,如果你还不会调试源码,请参考:https://netpt.net/forum.php?mod=viewthread&tid=53
我们直接进到QObject::connect这个函数里去看:
四个参数
sender:是指向MySender
signal:是字符串"2valueChanged(int)"
receiver是指向MyReceiver
method:也就是slot是字符串"1setValue(int)"
signal和slot前面的2和1是什么意思呢?还记得宏定义吗?
# define SLOT(a) qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
原来signal和slot都是字符串,QT在前面加了一个"数字"用来识别区分signal和slot.
这段代码主要是将signal字符串转成signal_index,为何要这样呢?
我猜想, 存字符串不利于查找,也更浪费内存,所以转成索引,更加方便.
const QMetaObject *smeta = sender->metaObject();
因为我们的类是继承自QObject,所以就有QObject的特性,QMetaObject有类的信息.
代码我没有截屏完,也没必要全部截屏,紧接着,同理,receiver也千辛万苦地把字符串method转换成了整数method_index_relative
然后,开始执行函数QMetaObjectPrivate::connect
可以看出,QMetaObjectPrivate::connect之前的主要工作就是把字符串转成index,无论是signal,还是method.
QMetaObjectPrivate::connect里面有核心信息:
1.connect到底干了什么,他是产生了一个连接(connection)吗?
是的,从QObjectPrivate::Connection *c = new QObjectPrivate::Connection;可以看出.
2.如果产生了一个连接,这个连接存在内存哪里的?
存在了两个地方,发送者对象实例s和接收者对象实例r都存了.
现在我们来解决另一个问题,当发送者产生信号后,怎么就调用到接收者函数了,
这里只讲最简单的情况,也就是在同一个线程的情况,跨线程会比较复杂,以后有机会再说.
代码执行顺序为:
s.UiMakeValueChanged();->emit valueChanged(100);->void MySender::valueChanged(int _t1)
void MySender::valueChanged(int _t1)
{
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
可以看出, 会执行到QMetaObject::activate
三个参数:
this肯定是指的是MySender
staticMetaObject也是MySender的staticMetaObject
a是把参数放到一个数组里面去.
QMetaObject::activate的定义如下:
void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
void **argv)
这里面做的事情是把存在sender里面的connection找出来,然后再调用
由于signal和slot是多对多的关系,一个signal可以对应多个slot,一个slot也可以对应多个signal
所以list中的connection个数>=1, 每一个connection都会被处理的.
最后,我们来看下调用堆栈
3.当发出(emit)一个信号后,是如何调到接收者的slot的?
a).发出调用valueChanged(100)
b)QMetaObject::activate(this, &staticMetaObject, 0, _a); //this为MySender, &staticMetaObject属于MySender, _a[0] = 0, _a[1] = 100
c).从sender中找到第2)点存起来的connection
d).用connection->callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);进行回调
e).调用到moc生成的代码void MyReceiver::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
f).调用到我们自己写的代码:void MyReceiver::setValue(int value)
我能发表下感想吗???
在同一线程中,signal&slot就是回调!呃,好吧,不然呢?
代码见:https://gitee.com/flash008/qt_signal_slot/tree/master/000100/
参考:
https://www.cnblogs.com/Vancamel/p/11217966.html
https://www.cnblogs.com/sherlock-lin/p/13960936.html
https://blog.51cto.com/9291927/2070398