QT信号槽原理(一)connect函数

2023-10-31

2个类型和1个签名

Qt::ConnectionType

信号的连接类型

  • Qt::DirectConnection
    直接连接,相当于在emit信号的地方直接调用接收者的槽函数,多见于在同一线程中去连接信号和槽,是一个同步的调用过程。
  • Qt::QueuedConnection
    队列连接,在emit信号时,会往接收者所在线程的消息队列post一个QMetaCallEvent,接收者线程在未来某个时刻处理消息队列中的该事件时就会调用接收者的槽函数,是一个异步的调用过程。这种方式下,如果信号的参数是个原始指针,那需要确保原始指针的生命周期直到事件被处理时仍然有效;其他类型(包括引用)的参数,qt会自动去克隆。还要注意的是,该类型下,信号的参数会放到event中,所以参数类型比如是QMetatype,如果不是,需要使用qRegisterMetaType()去注册。
  • Qt::BlockingQueuedConnection
    也是队列连接,不同于QueuedConnection在post事件之后立马返回,BlockingQueuedConnection在post事件之后,会一直阻塞直到该事件被处理。所以要注意,当发送者和接收者在同一线程时,千万布套使用这种方式,不然就会导致线程死锁。另外,虽然这是队列连接,但是由于是阻塞的,所以可以看成是同步的,也就不存在参数的生命周期问题。
  • Qt::AutoConnection
    自动连接,如果发送者和接收者在同一线程,那么就是DirectConnection,否则就使用QueuedConnection。
  • Qt::UniqueConnection
  • 严格来说,这并不是一种连接方式,而仅仅是一个掩码,表示唯一连接,它可以与上面四种连接方式以 | 的方式组合,比如 Qt::DirectConnection|Qt::UniqueConnection。如果发送者的信号已于接收者的槽连接过,再进行连接时就会失败。

QMetaMethod::MethodType

类中方法的类型

  • QMetaMethod::Method
    是用invoke修饰的成员函数
  • QMetaMethod::Signal
    信号
  • QMetaMethod::Slot
    槽函数
  • QMetaMethod::Constructor
    构造函数

方法签名

QMetaMethod的methodSignature()返回方法的签名,签名是个字符串。
下面是QMetaMethod获取方法签名的源码:

QByteArray QMetaMethodPrivate::signature() const
{
    QByteArray result;
    result.reserve(256);
    result += name();
    result += '(';
    QList<QByteArray> argTypes = parameterTypes();
    for (int i = 0; i < argTypes.size(); ++i) {
        if (i)
            result += ',';
        result += argTypes.at(i);
    }
    result += ')';
    return result;
}

可以看出签名实际是方法声明去掉返回值和参数名称后的字符串。
例如:
信号声明:
void clicked(bool state)
那么信号的签名就是clicked(bool)

connect函数源码解析

函数原型:

QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type)
五个参数:发送者,信号,接收者,槽函数或信号,连接类型

第一步:参数检查

if (sender == nullptr
        || receiver == nullptr
        || signal.methodType() != QMetaMethod::Signal
        || method.methodType() == QMetaMethod::Constructor) {
    qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
             sender ? sender->metaObject()->className() : "(nullptr)",
             signal.methodSignature().constData(),
             receiver ? receiver->metaObject()->className() : "(nullptr)",
             method.methodSignature().constData() );
    return QMetaObject::Connection(0);
}

发送者和接收者不能为空这是基本条件,另外,第二个参数必须是信号,第四个参数不能是构造函数(从第一节可以看出,除了构造函数外,还有信号、槽函数、普通成员函数三种类型)。

第二步:获取信号和槽的index

int signal_index;
int method_index;
{
    int dummy;
    QMetaObjectPrivate::memberIndexes(sender, signal, &signal_index, &dummy);
    QMetaObjectPrivate::memberIndexes(receiver, method, &dummy, &method_index);
}

const QMetaObject *smeta = sender->metaObject();
const QMetaObject *rmeta = receiver->metaObject();
if (signal_index == -1) {
    qWarning("QObject::connect: Can't find signal %s on instance of class %s",
                 signal.methodSignature().constData(), smeta->className());
        return QMetaObject::Connection(0);
}
if (method_index == -1) {
    qWarning("QObject::connect: Can't find method %s on instance of class %s",
             method.methodSignature().constData(), rmeta->className());
    return QMetaObject::Connection(0);
}

通过QMetaObjectPrivate::memberIndexes获取信号或槽的index,然后再去检查index。
从moc后的代码来看,信号和槽的index,从信号开始,按声明顺序从0开始依次递增的排列,然后接着是槽声明的顺序,接着信号最后一个序号排列。
例如下面的类声明:

class MocTest : public QObject {
    Q_OBJECT
public:
    explicit MocTest(QObject* parent = nullptr);

signals:
    void testSignal(const QString& name, int age);

public slots:
    void testSlot(const QString& name, int age);
};

信号testSignal的index为0, 槽函数testSlot的index为1。

第三步:检查信号和槽的参数是否匹配

if (!QMetaObject::checkConnectArgs(signal.methodSignature().constData(), method.methodSignature().constData())) {
    qWarning("QObject::connect: Incompatible sender/receiver arguments"
             "\n        %s::%s --> %s::%s",
             smeta->className(), signal.methodSignature().constData(),
             rmeta->className(), method.methodSignature().constData())     
   return QMetaObject::Connection(0);
}

这里的比较基于这样一个要求:槽函数参数个数≤信号参数个数,且参数类型和顺序必须一致。想想也很容易明白,槽函数参数少,那信号多出来的参数可以不传下去,但是如果槽函数参数多的话,那多出来的参数应该传什么值呢。
在第一节我们说过方法签名的组成:方法名称(参数类型1,参数类型2,…),下面看checkConnectArgs源码:

bool QMetaObject::checkConnectArgs(const char *signal, const char *method)
{
    const char *s1 = signal;
    const char *s2 = method;
    while (*s1++ != '(') { }                        // scan to first '('
    while (*s2++ != '(') { }
    if (*s2 == ')' || qstrcmp(s1,s2) == 0)        // method has no args or
        return true;                                //   exact match
    int s1len = qstrlen(s1);
    int s2len = qstrlen(s2);
    if (s2len < s1len && strncmp(s1,s2,s2len-1)==0 && s1[s2len-1]==',')
        return true;                                // method has less args
    return false;
}

按上面提到的比较要求,比较参数列表的话,就是看括号里的参数列表字符串,如果槽函数参数列表字符串是信号参数列表字符串的子串或者相同,那ok,否则fail。

第四步:检查信号参数类型

int *types = 0;
if ((type == Qt::QueuedConnection)
        && !(types = queuedConnectionTypes(signal.parameterTypes())))
    return QMetaObject::Connection(0);

在前面讲连接类型我们提到如果是队列连接,参数类型比如是QMetaType或者是注册到QMetaType。这一步就是检查参数类型是否是QMetaType。
那BlockingQueuedConnection也是队列连接,为啥不需要检查参数类型呢?
那是因为虽然BlockingQueuedConnection是队列连接,但是通过阻塞变成了同步调用,所以不存在参数生命周期问题,所以不会去克隆信号参数,所以也就不需要是QMetaType。

第五步:创建连接对象

QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
    sender, signal_index, signal.enclosingMetaObject(), receiver, method_index, 0, type, types));
return handle;

所有的检查都通过后,就通过QMetaObjectPrivate::connect来创建连接对象。
这个函数主要分3小步:

检查Qt::UniqueConnection

    QObjectPrivate::ConnectionData *scd  = QObjectPrivate::get(s)->connections.loadRelaxed();
    if (type & Qt::UniqueConnection && scd) {
        if (scd->signalVectorCount() > signal_index) {
            const QObjectPrivate::Connection *c2 = scd->signalVector.loadRelaxed()->at(signal_index).first.loadRelaxed();

            int method_index_absolute = method_index + method_offset;

            while (c2) {
                if (!c2->isSlotObject && c2->receiver.loadRelaxed() == receiver && c2->method() == method_index_absolute)
                    return nullptr;
                c2 = c2->nextConnectionList.loadRelaxed();
            }
        }
        type &= Qt::UniqueConnection - 1;
    }

如果连接类型带了UniqueConnection掩码,则检查当前是否符合UniqueConnection。

创建connection对象,并填充各个数据

    std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};
    c->sender = s;
    c->signal_index = signal_index;
    c->receiver.storeRelaxed(r);
    QThreadData *td = r->d_func()->threadData;
    td->ref();
    c->receiverThreadData.storeRelaxed(td);
    c->method_relative = method_index;
    c->method_offset = method_offset;
    c->connectionType = type;
    c->isSlotObject = false;
    c->argumentTypes.storeRelaxed(types);
    c->callFunction = callFunction;

创建Connection对象,填充各个数据成员。

将connection对象添加到sender中

QObjectPrivate::get(s)->addConnection(signal_index, c.get());

将上一步创建的connection对象添加到sender的连接列表中。
到此,一个完整的connect执行流程就分析完成了。

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

QT信号槽原理(一)connect函数 的相关文章

  • 在 4K 屏幕上使用 Matplotlib 和 TKAgg 或 Qt5Agg 后端

    我在 Ubuntu 16 04 上使用 Matplotlib 2 0 和 Python 3 6 来创建数据图 电脑显示器的分辨率为 4k 分辨率为 3840x2160 绘图数字看起来非常小 字体也很小 我已经尝试过TKAgg and Qt5
  • Qt 5.5 QOpenGLWidget 链接错误未链接任何 openGL 调用

    我尝试使用 Qt 5 5 1 构建一个简单的 OpenGL 应用程序 一切都很好 直到我尝试使用 glClearColor 等 openGL 本机函数调用 该小部件实际上编译并产生黑屏 但在我尝试使用任何 openGL 本机函数后 它甚至不
  • 如何在 QT 中绘制点?

    我正在用 QT 用 C 编写一个应用程序 其中有 n 个点并计算它的凸包 然而 一旦计算出来 我不知道如何绘制点并绘制船体的边界 制作菜单按钮等很简单 但我不确定我是否知道执行此操作的工具 你怎么做到这一点 图形视图 addEllipse
  • 如何使 Python、QT 和 Webkit 在无头服务器上工作?

    我有 Debian Linux 服务器 我用它来做各种事情 我希望它能够完成一些我需要定期完成的网络抓取工作 这段代码可以是在这里找到 http bit ly QeqvzX import sys from PyQt4 QtGui impor
  • 仅在内部/外部抚摸路径?

    Given a QPainterPath http qt project org doc qt 4 8 qpainterpath html如何仅在路径的内侧或外侧边缘 或非闭合路径的左侧或右侧 描边路径 QPainter strokePat
  • Qt 人工鼠标点击无法正常工作

    小玩具应用程序可以在这里找到 http gist github com 517445 http gist github com 517445 我正在尝试将人工鼠标事件发送到小部件 我使用QApplication sendEvent为此 接下
  • 在 Qt 服务器上验证用户身份

    我正在尝试使用 C QtTcpSocket 为个人项目 多人国际象棋游戏 实现身份验证系统 我的朋友建议了一种验证用户的方法 但我想问是否有更简单或更好的方法 来自 Python 背景 做这个项目主要是为了加深对 C 的理解 我将发布我朋友
  • QComboBox 下拉项边距

    我想设计我的风格QComboBox为下拉项目留出边距 现在是这样的 我想要这样的东西 我尝试过 QComboBox QAbstractItemView item margin 3px 但它不起作用 你能帮我解决这个问题吗 您想在项目之间设置
  • 在 QtCreator 中使用 .pro 和 .pri 正确(树)显示 Qt 项目

    是否可以使用项目包含文件 pri 显示不止一个额外级别 例如如果 pro文件包括 pri文件包含两个 pri files pro pri pri pri 在 QtCreator 项目 中显示具有误导性 pro pri pri pri 它根本
  • 第一次信号发射后自动断开

    我正在从文件加载网页 然后替换其中的一些 html self template web page QtWebKit QWebPage self template web page mainFrame load QtCore QUrl tem
  • QT:删除QGridLayout中QLabel之间的空格

    我将一些具有不同颜色的 QLabels 添加到 QGridLayout 作为 QWidget 的布局 现在我在每个 Qlabel 与下一个 Qlabel 之间有一个间距 我想将其删除 我尝试将标签的边距设置为 0 将 GridLayout
  • 清除pyqt中布局中的所有小部件

    有没有办法清除 删除 布局中的所有小部件 self plot layout QtGui QGridLayout self plot layout setGeometry QtCore QRect 200 200 200 200 self r
  • 如何在Qt 5中的paintEvent上使用mouseMoveEvent?

    我是 Qt 和 c 的新手 所以我遇到了一些困难 我正在尝试创建一个小部件 它可以获取 mouseMoveEvent 位置并在鼠标位置的像素图上绘制椭圆 下面你可以看到代码 include myimage h include
  • 使用 OpenGL 渲染 QImage

    与我相关的其他问题 https stackoverflow com questions 20126354 render qimage from sooffscreenrenderer in qglwidget 我认为更核心的问题是 如何渲染
  • QTableView 并双击一个单元格

    我正在开发测试用例编辑器 该编辑器包含 USART 传输和接收数据包格式 编辑器是一个表格视图 发送和接收数据包的长度为八个字节 例如 0x01 0x02 0x03 0x08 它在我的第五和第六栏中 现在 我希望此列中的单元格为只读 但是当
  • 如何doxygen注释Qt属性?

    我想将 Doxygen 注释附加到我的 Q PROPERTY 例如 song h class Song public QObject Q OBJECT private Q PROPERTY QString title READ title
  • 覆盖 QWebView 中的页面回复

    我试图在 Qt 的 QWebView 中拦截页面 表单请求 并在某些情况下使用替代内容进行响应 QNetworkReply ngcBrowser createRequest Operation operation const QNetwor
  • 连接到 QNetworkReply::error 信号

    我正在使用 Qt5 的新连接语法 QNetworkReply 有一个名为error http qt project org doc qt 5 0 qtnetwork qnetworkreply html error 2还有一个函数叫做err
  • 通过引用传递 [C++]、[Qt]

    我写了这样的东西 class Storage public Storage QString key const int value const void add item QString int private QMap
  • Qt 支持 Windows 蓝牙 API 吗?

    谁能告诉我 Qt 是否支持 Windows 蓝牙 API 如果是这样 您能否分享一些有关如何使用它的信息 自上次答复以来 这个问题的答案发生了一些变化 Qt 5 2 版为 Linux BlueZ 和 BlackBerry 设备实现了蓝牙 A

随机推荐

  • spdlog用法

    转自 spdlog学习笔记 说明 所有内容翻译自spdlog的wiki 受英语水平所限 有所错误或失真在所难免 如果您有更好的建议 请在博文下留言 线程安全 spdlog 命名空间下的是线程安全的 当loggers在不同的线程同时执行时 下
  • Django框架之DRF序列化与反序列化

    概念 序列化 将Python对象转换为json字符串 反之json字符串反序列化为对象 使用drf序列化组件的步骤如下 1 在你的app中新建一个py文件 新建一个序列化的类 2 在类中写要序列化的字段 在视图中使用序列化的类 1 实例化出
  • 从控制台输入基本工资,并计算输出实领工资

    题目 编写程序 public class Test01 这是一个main方法 程序的入口 public static void main String args 键盘输入 Scanner input new Scanner System i
  • 苹果电脑适合python吗_m1的MacBook pro适合python开发吗?

    我的MacBook Air M1到手了 我自己常用的python包都装了 都是arm的版本 不是用Rosseta跑的 请参考 短暂使用还没有发现什么BUG 只不过numpy和tf相关的包是Apple提供的 opencv也不能直接pip或者c
  • svm实现二分类_支持向量机原理详解(三): 核函数与非线性SVM

    前文推导了 线性 SVM的对偶问题 见支持向量机原理详解 二 拉格朗日对偶函数 SVM的对偶问题 不过 线性SVM以超平面来划分两类数据 如果数据本身是非线性的 那么以超平面作为决策边界就显得不太适用了 通过引入核函数 可以使SVM适用于非
  • 防雷工程专业术语及雷电浪涌保护器名词解释

    1 雷电次数 当雷暴进行时 隆隆的雷声持续不断 若其间雷声的时间间隔小于15分钟时 不论雷声断续传播的时间有多长 均算作是一次雷暴 若其间雷声的停息时间在15分钟以上时 就把前后分作是两次雷暴 2 雷电小时 就是说在该天文小时内发生过雷暴
  • springboot+mybatis+druid+postgreSql案例

    springboot mybatis druid postgreSql mysql 示例 springboot mybatis druid postgreSql mysql 示例 简介 结构解析 代码剖析 entity层 Dao层 serv
  • Java-System、Math、BigInteger、BigDecimal常用类的使用

    Java System Math BigInteger BigDecimal常用类的使用 1 System类 System类代表系统 系统级的很多属性和控制方法都放置在该类的内部 该类位于java lang包 由于该类的构造器是privat
  • 解决Linux7 ping出现 未知的名称或服务 错误

    问题描述 Linux7在利用命令nmtui进入图形化界面配置Linux的网络后为测试是否成功联网 ping配置的IP成功但ping如www baidu com常见网站时出现 gt ping www baidu com 未知的名称或服务 解决
  • 网络编程知识预备(5) ——libcurl库安装及其编程访问百度首页(断点续传)

    本文为学习笔记 整合课程内容以及下列文章 其中 libcurl函数库常用字段解读部分 参考博文 原文地址 作者 冬冬他哥哥 目录 Libcurl库简介 Libcurl等三方库的通用编译方法 三方库使用前通读方法 库的配置 编译 安装 调用l
  • protobuf生成prototxt文件

    使用protobuf可以分为以下几步 1 proto文件的定义 在这个文件中定义了最终生成的prototxt格式 举个例子如下所示 syntax proto2 package label proto message DetectLabel
  • scrapy缺点

    Scrapy 是一个功能强大的网络爬虫框架 但也有一些缺点 Scrapy 只能爬取静态网页 无法爬取动态网页 Scrapy 只能爬取一个网站的数据 无法爬取多个网站的数据 Scrapy 需要编写代码 对于那些不会编写代码的人来说可能会有些困
  • LiteOS内存管理

    1 内存管理简介 内存管理模块管理系统的内存资源 它是操作系统的核心模块之一 主要包括内存的初始化 分配以及释放 在系统运行过程中 内存管理模块通过对内存的申请 释放操作 来管理用户和OS对内存的使用 使内存的利用率和使用效率达到最优 同时
  • obs无法连接服务器?PotPlayer获取不到视频

    搭建基于nginx的rtmp直播服务器 https hywlovexyc info blog archives 572 rtmp server listen 1935 监听的端口 chunk size 4000 application yu
  • 删除中间节点

    题目 若链表中的某个节点 既不是链表头节点 也不是链表尾节点 则称其为该链表的 中间节点 假定已知链表的某一个中间节点 请实现一种算法 将该节点从链表中删除 例如 传入节点 c 位于单向链表 a gt b gt c gt d gt e gt
  • sqlserver查看数据表结构

    可以通过两种方式 1 sp help 表名 2 sp columns 表名
  • 我的世界服务器config文档,Essentials/配置文件/config.yml

    ops name color none nickname prefix max nick length 15 change displayname true change playerlist true add prefix suffix
  • Unity--虚拟轴

    1 查看虚拟轴 Edit gt project settings gt Input 2 获取虚拟轴 input GetAxis 用法 GetAxis Mouse X GetAxis Mouse Y GetAxis Mouse ScrollW
  • centos7Linux中的lvm、pv、vg、lv,以及Linux扩容,创建新分区

    概念 本着言简意赅的目的 以下概念会在扩容时用到 需熟知 不然会不理解具体操作在干什么 lvm 逻辑卷管理器 Logical Volume Manager 它使系统管理员可以更方便的为应用与用户分配存储空间 在LVM管理下的存储卷可以按需要
  • QT信号槽原理(一)connect函数

    目录 2个类型和1个签名 Qt ConnectionType QMetaMethod MethodType 方法签名 connect函数源码解析 函数原型 第一步 参数检查 第二步 获取信号和槽的index 第三步 检查信号和槽的参数是否匹