经过《Qt信号槽之—准备阶段》学习,我们知道信号是函数,里面调用了 QMetaObject::activate() 函数;
而对于接收端的槽函数或信号,是通过私有静态函数 qt_static_metacall() 调用元方法。
源头是有的,接收端也是有的,中间少了个桥,这个桥就是 Qt 元对象系统的技术内幕,我们需要阅读 Qt 核心源码才能知道。 后面三个小节就是把从源头到接收端的桥解析一下。
connect做了什么?
connect我们都知道是将信号和槽进行关联,具体这个桥梁如何搭建的,咱们下面进行分解。这里我们讲解Qt旧语法的信号槽,如下是一种咱们最常见的结构:
1.1 SIGNAL和SLOT宏
QMetaObject::Connection connect(const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection)
实例:
bool flg = connect(ui.pushButton, SIGNAL(clicked()), this, SLOT(slot_set_label_number()));
我们可以看到实例信号,和槽分别用两个宏进行处理,下面来看一下SIGNAL和SLOT宏分别代表什么:
# define SLOT(a) qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
把信号作为字符串前面加上了“2”,对于槽函数,前面加"1",因此,上面的实例等同下面这句
bool flg = connect(ui.pushButton, "2clicked()", this, "1slot_set_label_number()");
1.2 connect函数详解
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type)
{
if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
sender ? sender->metaObject()->className() : "(null)",
(signal && *signal) ? signal+1 : "(null)",
receiver ? receiver->metaObject()->className() : "(null)",
(method && *method) ? method+1 : "(null)");
return QMetaObject::Connection(0);
}
QByteArray tmp_signal_name;
//用来检测是否使用SINGAL关键字,如果没有则报错,返回一个空Connection
if (!check_signal_macro(sender, signal, "connect", "bind"))
return QMetaObject::Connection(0);
const QMetaObject *smeta = sender->metaObject();
const char *signal_arg = signal;
++signal; //skip code 去掉前面的数字
QArgumentTypeArray signalTypes;
Q_ASSERT(QMetaObjectPrivate::get(smeta)->revision >= 7);
QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
//计算相对序号
int signal_index = QMetaObjectPrivate::indexOfSignalRelative(
&smeta, signalName, signalTypes.size(), signalTypes.constData());
if (signal_index < 0) {
//相对序号为负数,则表示没找到,则规范信号名(去除空格)
// check for normalized signatures
tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
signal = tmp_signal_name.constData() + 1;
signalTypes.clear();
signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
smeta = sender->metaObject();
//再次查找序号
signal_index = QMetaObjectPrivate::indexOfSignalRelative(
&smeta, signalName, signalTypes.size(), signalTypes.constData());
}
if (signal_index < 0) {
//两次都没找到,打印报警
err_method_notfound(sender, signal_arg, "connect");
err_info_about_objects("connect", sender, receiver);
return QMetaObject::Connection(0);
}
//判断是否为克隆信号,如果是,则用原始序号
signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);
//绝对序号
signal_index += QMetaObjectPrivate::signalOffset(smeta);
QByteArray tmp_method_name;
int membcode = extract_code(method);
if (!check_method_code(membcode, receiver, method, "connect"))
return QMetaObject::Connection(0);
const char *method_arg = method;
++method; // skip code
QArgumentTypeArray methodTypes;
QByteArray methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
const QMetaObject *rmeta = receiver->metaObject();
int method_index_relative = -1;
Q_ASSERT(QMetaObjectPrivate::get(rmeta)->revision >= 7);
switch (membcode) {
case QSLOT_CODE:
//如果 method 是槽函数类型,使用 QMetaObjectPrivate::indexOfSlotRelative 函数计算槽函数相对序号 method_index_relative;
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
case QSIGNAL_CODE:
//如果 method 是信号类型,那么使用 QMetaObjectPrivate::indexOfSignalRelative 函数计算信号的相对序号 method_index_relative。
method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
}
if (method_index_relative < 0) {
// check for normalized methods
//格式化
tmp_method_name = QMetaObject::normalizedSignature(method);
method = tmp_method_name.constData();
methodTypes.clear();
methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
// rmeta may have been modified above
rmeta = receiver->metaObject();
switch (membcode) {
case QSLOT_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
case QSIGNAL_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
}
}
//两次检测都失败,返回空链接
if (method_index_relative < 0) {
err_method_notfound(receiver, method_arg, "connect");
err_info_about_objects("connect", sender, receiver);
return QMetaObject::Connection(0);
}
//检查信号函数参数个数、类型与接收函数的参数个数、类型是否 能兼容,如果不兼容返回空连接,如果兼容就继续往下走。
if (!QMetaObjectPrivate::checkConnectArgs(signalTypes.size(), signalTypes.constData(),
methodTypes.size(), methodTypes.constData())) {
qWarning("QObject::connect: Incompatible sender/receiver arguments"
"\n %s::%s --> %s::%s",
sender->metaObject()->className(), signal,
receiver->metaObject()->className(), method);
return QMetaObject::Connection(0);
}
int *types = 0;
//如果连接类型 type == Qt::QueuedConnection,那么使用 queuedConnectionTypes 函数计算入队关联需要的额外类型指针 types
if ((type == Qt::QueuedConnection)
&& !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) {
return QMetaObject::Connection(0);
}
#ifndef QT_NO_DEBUG
//QMetaObjectPrivate::signal 函数根据源头元对象smeta和信号相对序号 signal_index,得到信号元方法 smethod;
QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
//计算 method_index_relative + rmeta->methodOffset() ,也就是接收端的元方法绝对序号,然后通过 rmeta->method 函数得到接收端元方法 rmethod;
QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset());
//查源头元对象 smeta、元方法 smethod 、接收端元对象 rmeta、元方法 rmethod 是不是具有 QMetaMethod::Compatibility 特性。
check_and_warn_compat(smeta, smethod, rmeta, rmethod);
#endif
//)前面全都是参数判断和查询序号,最后才是关键的一步,进行实际的连接操作:
QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));
return handle;
}
可以看到QObject内的connect函数前半部分都是在做类型检测,到最后获取到了几个信息
- QObject sender 发送者
- signal_index 信号绝对序号
- QMetaObject smeta 发送者元对象
- receiver 接收者
- method_index_relative 接收者槽函数绝对序号
- QMetaObject rmeta 接收者元对象
- Qt::ConnectionType type 槽连接方式
- types:是多线程程序的入队关联需要的额外类型指针
QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,
int signal_index, const QMetaObject *smeta,
const QObject *receiver, int method_index,
const QMetaObject *rmeta, int type, int *types)
{
QObject *s = const_cast<QObject *>(sender);
QObject *r = const_cast<QObject *>(receiver);
int method_offset = rmeta ? rmeta->methodOffset() : 0;
Q_ASSERT(!rmeta || QMetaObjectPrivate::get(rmeta)->revision >= 6);
QObjectPrivate::StaticMetaCallFunction callFunction =
rmeta ? rmeta->d.static_metacall : 0;
//关联操作需要访问、修改源头和接收端的信号和槽信息,因此需要使用互斥锁,独占源头和接收端信号和槽信息的访问权,先锁定访问权,然后再进行操作。这对于多线程操 作尤为重要,不能多个线程同时操作一对源头和接收端,那样会造成信号和槽数据的混乱,如果情况严重会导致程序崩溃,因此启用互斥锁
QOrderedMutexLocker locker(signalSlotLock(sender),
signalSlotLock(receiver));
//关联类型 type 是 Qt::AutoConnection、Qt::DirectConnection、Qt::QueuedConnection 或Qt::BlockingQueuedConnection,这些类型的关联,可以将完全相同的源头、信号、接收端、槽函数关联多次,那样信号触发一次,槽函数会被调用 多次,重复关联是有效的。Qt::UniqueConnection 是唯一关联的标志,可以与其他四种关联标志叠加(OR 运算),那样就会执行上面一段代码,检查关联的唯一性,如果是重复关联就返回 0 ,如果没重复就进行唯一性关联。上面代码就是穷举比较源头信号关联列表 c2 里面有没有重复的关联。一般关联类型 type 都是 Qt::AutoConnection,所以可以不用管上面一段唯一性检查的代码。
if (type & Qt::UniqueConnection) {
QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;
if (connectionLists && connectionLists->count() > signal_index) {
const QObjectPrivate::Connection *c2 =
(*connectionLists)[signal_index].first;
int method_index_absolute = method_index + method_offset;
while (c2) {
if (!c2->isSlotObject && c2->receiver == receiver && c2->method() == method_index_absolute)
return 0;
c2 = c2->nextConnectionList;
}
}
type &= Qt::UniqueConnection - 1;
}
QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
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;//是否为 SlotObject,新式关联语法和 QML程序会用到这个奇怪的 SlotObject。
c->argumentTypes.store(types);//保存多线程入队关联需要的 types 指针
c->nextConnectionList = 0;//nextConnectionList 是下一个链表节点,暂时为 0,以后由 QObjectPrivate::addConnection 函数填充。
c->callFunction = callFunction;//重点:接收端的私有静态函数 qt_static_metacall(),这个接收端私有静态函数是可以根据相对序号调用元方法的。
QObjectPrivate::get(s)->addConnection(signal_index, c.data());
locker.unlock();
QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
if (smethod.isValid())
s->connectNotify(smethod);
return c.take();
}
QObjectPrivate::addConnection 函数根据信号绝对序号 signal_index 和连接数据 c.data() 添加新的连接。
感兴趣的读者可以跟踪 QObjectPrivate::addConnection 函数源码,这里不贴代码了,一张数据结构图说明:
从信号到槽函数
信号触发时,调用moc_xx.cpp中的信号函数
void Sig_SLOT::label_number_change(int _t1)
{
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
其中调用复杂版本的版本
activate(sender, QMetaObjectPrivate::signalOffset(m), local_signal_index, argv);
这个函数比较复杂,只讲一下比较关键的部分
如果sender与receiver在一个线程,则直接使用如下函数回调槽函数
callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);
如果与sender与receiver不在同一个线程,使用如下函数构造QMetaCallEvent事件,然后Post到receiver的事件循环中。
queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker)
{
....
QMetaCallEvent *ev = c->isSlotObject ?
new QMetaCallEvent(c->slotObj, sender, signal, nargs, types, args) :
new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal, nargs, types, args);
QCoreApplication::postEvent(c->receiver, ev);
}
注意:QMetaCallEvent 是一个包含信号及槽相关信息,通过QMetaCallEvent ,接收对象可以在其所在线程中正确执行槽函数。