(二):Qt信号槽连接及触发原理

2023-11-06

经过《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 ,接收对象可以在其所在线程中正确执行槽函数。

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

(二):Qt信号槽连接及触发原理 的相关文章

随机推荐

  • 2020-10-10

    闭包和装饰器 1 高阶函数 接收函数作为参数是高阶函数 将函数作为返回值返回的函数就是高阶函数 2 匿名函数 lambda函数 无名函数 语法 lambda 参数列表 表达式 filter 函数 过滤列表 第一个参数 函数 第二个参数 序列
  • Java设计模式(十四)—— 模板方法模式

    模板方法模式是指定义一个操作中算法的骨架 而将一些步骤延迟到子类中 模板方法使子类可以不改变一个算法的结构 即可重定义该算法的某些特定步骤 适合模板方法模式的情景如下 编制一个通用算法 将某些步骤的具体实现留给子类来实现 需要重构代码 将各
  • 对Linux svn保存的明文密码加密

    需求来源 随着GitHub GitLab的兴起 svn已经渐渐的没落了 从公司当初的源代码管理服务器 逐渐演变成公司的ftp服务器 最近需要部署gitlab的CI单元测试模块 而软件版本都在svn上有备份 我就希望从代码的提交 gt 到版本
  • Python统计文本数字,字母,单词量

    统计一百万位圆周率中数字0 9各自的数量 统计一本书中字母a z各自的数量 统计一本书共有多少个单词 含重复的单词 和单词量 不含重复的单词 import string class CountNums 求txt文本中数字或字母的数量 def
  • Jvm类加载机制详解---类加载器及双亲委托模型

    前面介绍了类加载的几个过程 实际中这些过程大部分都是由虚拟机本身去执行的 我们没有办法去改变或影响这些过程的执行 但是虚拟机团队将类加载阶段第一步中的 通过一个类的全限定名来获取描述该类的二进制字节流 这个动作放到虚拟机外部去实现 以便让应
  • 使用R语言绘制ovarian数据集中病例年龄分布的直方图

    使用R语言绘制ovarian数据集中病例年龄分布的直方图 直方图是一种常用的数据可视化工具 用于展示连续变量的分布情况 在R语言中 我们可以使用hist 函数轻松地创建直方图 本文将展示如何使用R语言绘制ovarian数据集中病例年龄的分布
  • String、StringBuffer和StringBuilder三者之间的区别

    最基本的区别就是String是一个字符串常量 长度不可改变 StringBuffer和StringBuilder是字符串变量 他们两个的长度可以改变 但StringBuffer是线程安全的 而StringBuilder是非线程安全的 Str
  • ** LeetCode 刷题 459

    这是一道我没做出来的简单题 5555 学习 方法1 移动匹配 如果一个字符串可以由一个字串重复获得 那么将两个相同字符串并起来 一定可以在中间再找到该字符串 class Solution public bool repeatedSubstr
  • JavaEE-过滤器和监听器 案例分析

    目录 过滤器和监听器 什么是过滤器 过滤器编程接口 接口Filter的主要方法 设计过滤器 实例1 编写一个过滤器审计用户对资源的访问 什么是监听器 监听器编程接口 设计监听器 实例 编写一个HttpSession事件监听器用来记录当前在线
  • Python pandas读取Excel 数据写入到数据库

    需求得到天眼查的法人信息数据导入到数据库中 经过多次不断试错最后使用Python导入 先上代码 import pandas as pd import sqlalchemy as sqla import os 读取Excel数据 def ge
  • net::ERR_HTTP2_PROTOCOL_ERROR 200错误

    场景 web端的环境上某一个请求报这个问题了 其他的请求正常 打开f12控制台看到异常net ERR HTTP2 PROTOCOL ERROR 200错误 排查 查看nginx的error log 报是某一个临时文件的权限不足 原因是启动n
  • CSVDE导出AD域下指定子OU的Group中所有成员的信息

    CSVDE导出AD域下指定子OU的Group中所有成员的信息 最近搞CSVDE搞的头疼 网上各种乱七八糟的资料 都是东搞一下西搞一下 好不容易找到一篇系统性的介绍使用CSVDE来对AD进行导入导出操作的文章 具体链接 http www co
  • mybatis(六) 处理枚举类型

    处理枚举类型 若想映射枚举类型 Enum 则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选一个来使用 比如说我们想存储取近似值时用到的舍入模式 默认情况下 MyBatis 会利用 EnumT
  • python数据清洗的三个常用的处理方式!

    关于python数据处理过程中三个主要的数据清洗说明 分别是缺失值 空格 重复值的数据清洗 这里还是使用pandas来获取excel或者csv的数据源来进行数据处理 若是没有pandas的非标准库需要使用pip的方式安装一下 pip ins
  • 华为OD机试真题-分班-2023年OD统一考试(B卷)

    题目描述 幼儿园两个班的小朋友在排队时混在了一起 每位小朋友都知道自己是否与前面一位小朋友是否同班 请你帮忙把同班的小朋友找出来 小朋友的编号为整数 与前一位小朋友同班用Y表示 不同班用N表示 输入描述 输入为空格分开的小朋友编号和是否同班
  • 深度网络架构的设计技巧<一>:Can CNNs Be More Robust Than Transformers?

    导读 启发于Transformer结构 作者将三种设计引入到CNN中 加强后的CNN取得比ViT更强的鲁棒性 这三种设计 实现简单 仅仅几行代码就能实现高效的CNN结构设计 ArXiv https arxiv org abs 2206 03
  • 数据挖掘的基础

    目录 数据挖掘 一 数据挖掘理解 二 数据准备 1 缺失值处理 2 异常值处理 3 数据偏差的处理 4 数据的标准化 5 特征选择 三 数据建模 1 分类问题 2 聚类问题 3 回归问题 4 关联问题 四 评估模型 1 混淆矩阵与准确率指标
  • C语言删除一个字符串中的多余空格字符

    删除一个字符串中的多余空格字符 使得字符串中的每个单词之间只有一个空格字符 code C C char my delblank char str char newStr char start str while start start st
  • 2021年计算机保研经验帖(真平民)(上交网安、复旦、南大、同济、北航、东南)

    个人背景 中九5 数学辅修 数模省一 组织经历较丰富 干啥啥不行 活动第一名 软著及项目若干 无论文无科研 什么叫学混啊 有强烈的上海和南京倾向 有倾向是好事 但是也不要过于自信 因为各个学校的审核很迷 还是要适当海投保证自己有学上 夏令营
  • (二):Qt信号槽连接及触发原理

    经过 Qt信号槽之 准备阶段 学习 我们知道信号是函数 里面调用了 QMetaObject activate 函数 而对于接收端的槽函数或信号 是通过私有静态函数 qt static metacall 调用元方法 源头是有的 接收端也是有的