深入探索Qt框架系列之元对象编译器

上一篇文章简单介绍了Qt框架中的三大编译器(MOC、UIC、RCC),其中我认为最核心,最重要的就是元对象编译器(MOC),下面我们将深入探索MOC生成的代码,并逐步解析。

本文将以下面的源码来解析MOC生成了哪些代码。
test_moc.h:

#ifndef TEST_MOC_H
#define TEST_MOC_H#include <QDebug>
#include <QObject>class test_moc : public QObject {Q_OBJECT
public:test_moc(QObject* parent = nullptr): QObject(parent){}public slots:void on_TestSlot() { qDebug() << __FUNCTION__; }void on_TestSlot_Param(int num) { qDebug() << __FUNCTION__ << num; }signals:void sigTestSignals();void sigTestSignals_Param(int num);
};#endif // TEST_MOC_H

main.cpp:

#include "test_moc.h"#include <QApplication>int main(int argc, char* argv[])
{QApplication a(argc, argv);test_moc m1, m2;QObject::connect(&m1, SIGNAL(sigTestSignals()), &m2, SLOT(on_TestSlot()));QObject::connect(&m2, SIGNAL(sigTestSignals_Param(int)), &m1, SLOT(on_TestSlot_Param(int)));return a.exec();
}

元对象编译器(MOC)

Qt信号槽和属性系统基于在运行时自省对象的能力,能够列出对象的方法和属性,并拥有有关它们的各种信息,例如参数类型。
因为C++本身是不支持运行时自省对象的能力的,因此Qt附带了一个工具来支持它。这个工具就是MOC。它是一个代码生成器(通过编译来生成新的代码)。
MOC解析头文件并生成一个附加的C++文件,该文件与程序的其余部分一起编译。生成的C++文件包含自省所需的所有信息。

Qt宏定义

在Qt程序中我们可以看到一些关键字不属于纯C++关键字:signalsslotsQ_OBJECTemitSIGNALSLOT。这些被称为C++的Qt扩展宏,它们实际上就是简单的宏,在qobjectdefs.h文件中定义。

signals,slots
#ifndef QT_ANNOTATE_ACCESS_SPECIFIER
# define QT_ANNOTATE_ACCESS_SPECIFIER(x)
#endif
...
# define Q_SLOTS QT_ANNOTATE_ACCESS_SPECIFIER(qt_slot)
# define Q_SIGNALS public QT_ANNOTATE_ACCESS_SPECIFIER(qt_signal)
...
#define slots Q_SLOTS
#define signals Q_SIGNALS

上面代码主要是定义了slotssignals两个宏,摘自qobjectdefs.h文件。
我们可以看到这两个宏展开后就是一个空宏,因为# define QT_ANNOTATE_ACCESS_SPECIFIER(x)定义的宏是一个空宏。

Q_OBJECT
#define Q_OBJECT \
public: \QT_WARNING_PUSH \Q_OBJECT_NO_OVERRIDE_WARNING \static const QMetaObject staticMetaObject; \virtual const QMetaObject *metaObject() const; \virtual void *qt_metacast(const char *); \virtual int qt_metacall(QMetaObject::Call, int, void **); \QT_TR_FUNCTIONS \
private: \Q_OBJECT_NO_ATTRIBUTES_WARNING \Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \QT_WARNING_POP \struct QPrivateSignal {}; \QT_ANNOTATE_CLASS(qt_qobject, "")

Q_OBJECT定义了一堆函数和一个静态对象staticMetaObject,这些函数在MOC生成的文件中会有实现。

emit
#ifndef QT_NO_EMIT
# define emit
#endif

emit也是一个空宏。而且它不会被MOC解析,也就是说emit没有任何实际意义(除了用来给开发人员提示外)。

SIGNAL,SLOT
Q_CORE_EXPORT const char *qFlagLocation(const char *method);
# define QLOCATION "\0" __FILE__ ":" QT_STRINGIFY(__LINE__)
...
# define SLOT(a)     qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a)   qFlagLocation("2"#a QLOCATION)

这两个宏只是使用预处理器将参数转换为字符串,并在前面添加一段代码。
在调试模式下,如果信号连接不起作用,会以警告的形式输出代码的具体位置,QLOCATION宏就是对应的位置字符串。

MOC生成的代码

上面示例代码通过MOC生成的代码将逐一展示并解释。

字符串表
struct qt_meta_stringdata_test_moc_t {QByteArrayData data[7];char stringdata0[80];
};
#define QT_MOC_LITERAL(idx, ofs, len) \Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \qptrdiff(offsetof(qt_meta_stringdata_test_moc_t, stringdata0) + ofs \- idx * sizeof(QByteArrayData)) \)
static const qt_meta_stringdata_test_moc_t qt_meta_stringdata_test_moc = {{
QT_MOC_LITERAL(0, 0, 8), // "test_moc"
QT_MOC_LITERAL(1, 9, 14), // "sigTestSignals"
QT_MOC_LITERAL(2, 24, 0), // ""
QT_MOC_LITERAL(3, 25, 20), // "sigTestSignals_Param"
QT_MOC_LITERAL(4, 46, 3), // "num"
QT_MOC_LITERAL(5, 50, 11), // "on_TestSlot"
QT_MOC_LITERAL(6, 62, 17) // "on_TestSlot_Param"},"test_moc\0sigTestSignals\0\0sigTestSignals_Param\0""num\0on_TestSlot\0on_TestSlot_Param"
};
#undef QT_MOC_LITERAL

这段代码的核心是初始化静态的qt_meta_stringdata_test_moc对象,其中QByteArrayData数组是用来存储类名、信号和槽的名字。QT_MOC_LITERAL宏用来创建一个静态QByteArray,它引用下面字符串中的索引值。qt_meta_stringdata_test_moc_t::stringdata0数据就是存储类名、信号和槽的名字、参数名的字符串表。

自省表
static const uint qt_meta_data_test_moc[] = {// content:8,       // revision0,       // classname0,    0, // classinfo4,   14, // methods0,    0, // properties0,    0, // enums/sets0,    0, // constructors0,       // flags2,       // signalCount// signals: name, argc, parameters, tag, flags1,    0,   34,    2, 0x06 /* Public */,3,    1,   35,    2, 0x06 /* Public */,// slots: name, argc, parameters, tag, flags5,    0,   38,    2, 0x0a /* Public */,6,    1,   39,    2, 0x0a /* Public */,// signals: parametersQMetaType::Void,QMetaType::Void, QMetaType::Int,    4,// slots: parametersQMetaType::Void,QMetaType::Void, QMetaType::Int,    4,0        // eod
};

content部分中,每行有一个值或两个值组成,如果有两个值,那么第一个值代表计数,第二个值代表该数组中描述开始的索引。比如方法methods有四个,分别是两个槽函数和两个信号,所以methods的第一个值是4。前面content包含了索引0~13,第14开始是信号和槽,所以methods的第二个值是14。
方法(信号和槽)字段由5个int组成:

  • 第一个是名称

这里的值对应的是上面字符串表中的索引值,比如当前信号名称是sigTestSignals,对应字符串表中的索引是1。

  • 第二个是参数的数量
  • 第三个是参数描述的索引,该索引对应的是当前自省表中的索引值(即后面parameters部分对应的索引)
  • 后面两个值先忽略

调用信号和槽
void test_moc::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{if (_c == QMetaObject::InvokeMetaMethod) {auto *_t = static_cast<test_moc *>(_o);Q_UNUSED(_t)switch (_id) {case 0: _t->sigTestSignals(); break;case 1: _t->sigTestSignals_Param((*reinterpret_cast< int(*)>(_a[1]))); break;case 2: _t->on_TestSlot(); break;case 3: _t->on_TestSlot_Param((*reinterpret_cast< int(*)>(_a[1]))); break;default: ;}} else if (_c == QMetaObject::IndexOfMethod) {int *result = reinterpret_cast<int *>(_a[0]);{using _t = void (test_moc::*)();if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&test_moc::sigTestSignals)) {*result = 0;return;}}{using _t = void (test_moc::*)(int );if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&test_moc::sigTestSignals_Param)) {*result = 1;return;}}}
}

这段代码分为两部分:

  1. 调用信号和槽

_c == QMetaObject::InvokeMetaMethod时,通过id索引对应的方法,然后调用。

  1. 信号索引

_c == QMetaObject::IndexOfMethod时,通过_a来匹配信号,并返回对应信号的索引值(result)。

QMetaObject
QT_INIT_METAOBJECT const QMetaObject test_moc::staticMetaObject = { {QMetaObject::SuperData::link<QObject::staticMetaObject>(),qt_meta_stringdata_test_moc.data,qt_meta_data_test_moc,qt_static_metacall,nullptr,nullptr
} };const QMetaObject *test_moc::metaObject() const
{return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}void *test_moc::qt_metacast(const char *_clname)
{if (!_clname) return nullptr;if (!strcmp(_clname, qt_meta_stringdata_test_moc.stringdata0))return static_cast<void*>(this);return QObject::qt_metacast(_clname);
}int test_moc::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{_id = QObject::qt_metacall(_c, _id, _a);if (_id < 0)return _id;if (_c == QMetaObject::InvokeMetaMethod) {if (_id < 4)qt_static_metacall(this, _c, _id, _a);_id -= 4;} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {if (_id < 4)*reinterpret_cast<int*>(_a[0]) = -1;_id -= 4;}return _id;
}

这段代码实现了Q_OBJECT宏声明的接口和对象:test_moc::staticMetaObjectmetaObject()qt_metacast()qt_metacall()

  • staticMetaObject

这部分代码是用来初始化静态元对象的,主要包含字符串表中的索引数据(qt_meta_stringdata_test_moc.data)、自省表数据(qt_meta_data_test_moc)以及调用信号和槽的接口(qt_static_metacall)。

  • metaObject()

该函数是返回元对象,其中QObject::d_ptr->dynamicMetaObject()仅用于动态元对象(QML对象),一般情况下,该函数只返回staticMetaObject

  • qt_metacast()

该函数是Qt的动态类型转换工具,类似于C++中的dynamic_cast
调用该函数可以安全地将QObject或其派生类的实例向下转换为更具体的类型。

  • qt_metacall()

该函数是QObject的一个核心功能,它在运行时调用对象的方法(如信号、槽或其他通过Q_INVOKABLE宏定义的方法)。信号槽的调用就是通过该函数实现的。

信号
// SIGNAL 0
void test_moc::sigTestSignals()
{QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}// SIGNAL 1
void test_moc::sigTestSignals_Param(int _t1)
{void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

其实信号经过元对象编译后就是一个普通的函数,将该类的指针、静态元对象数据以及参数数组传递给QMetaObject::activate

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/18438.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【错误记录】HarmonyOS 运行报错 ( Failure INSTALL _PARSE _FAILED _USESDK _ERROR )

文章目录 一、报错信息二、问题分析三、解决方案 一、报错信息 在 DevEco Studio 中 , 使用 远程设备 , 向 P40 Failure[INSTALL_PARSE_FAILED_USESDK_ERROR] compileSdkVersion and releaseType of the app do not match the apiVersion and releaseType on the device. 二、…

SpringMVC枚举类型字段处理

在日常的项目开发中经常会遇到一些取值范围固定的字段&#xff0c;例如性别、证件类型、会员等级等&#xff0c;此时我们可以利用枚举来最大程度减少字段的乱定义&#xff0c;统一管理枚举的值。 SpringMVC中对于枚举也有默认的处理策略&#xff1a; 对于RequestParam&#xf…

管理开发进度

在系统开发现场&#xff0c;必须要对项目的推进状况进行管理。不过&#xff0c;针对大型项目&#xff0c;要一下子对其整体进行统一的管理是很困难的。这时就需要将其划分成更小的单位进行管理。 这种用于分解的单位被称为任务。以任务为单位对开发进度进行管理的方法…

平常心看待已发生的事

本篇主要记录自己在阅读此篇文章&#xff08;文章链接&#xff1a; 这才是扼杀员工积极性的真正原因&#xff08;管理者必读&#xff09; &#xff09;和这两天京东的东哥“凡是长期业绩不好&#xff0c;从来不拼搏的人&#xff0c;不是我的兄弟”观点后的一些想法。 自己在微…

静态测试---基于WorkList的活跃变量分析

本文主要用于记录在活跃变量分析实验中的报错及解决&#xff0c;涉及静态测试的详细原理内容较少&#xff0c;编译运行底层逻辑偏多。 一、实验要求 1&#xff09;使用llvm基于框架实现一个基于WorkList的活跃变量分析demo。变量在某个程序点有两种状态&#xff0c;live 或 dea…

利用 Scapy 库编写源路由攻击脚本

一、介绍 源路由攻击是一种网络攻击方法&#xff0c;攻击者通过利用IP数据包中的源路由选项来控制数据包的传输路径&#xff0c;从而绕过安全设备或防火墙&#xff0c;直接访问目标系统。源路由功能允许数据包的发送方指定数据包通过的路径&#xff0c;而不是由路由器根据路由…

计数排序法

计数排序的核心在于将输入数据转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序&#xff0c;计数排序要求输入的数据必须是有确定范围的整数。 当输入的元素是n个0到k之间的整数时&#xff0c;它的运行时间复杂度是O(nk)。计数排序不是比较排序&#xff0c;…

Xshell 5(xmanager5)报错

总结 所有的错误都是因为Xshell版本太低&#xff0c;与新的Linux系统不兼容导致的。 所以解决办法都是使用Xshell7 XShell 7 &#xff08;解压、运行绿化.bat&#xff09; https://pan.baidu.com/s/151W_MeLrrceUZQIFiNlMdg?pwd8888错误1&#xff1a;找不到匹配的host key算…

【LeetCode刷题】滑动窗口解决问题:串联所有单词的子串(困难)、最小覆盖子串(困难)

【LeetCode刷题】Day 10 题目1&#xff1a;30. 串联所有单词的子串&#xff08;困难&#xff09;思路分析&#xff1a;思路1&#xff1a;滑动窗口哈希map 题目2&#xff1a;LCR 017.最小覆盖子串思路分析思路1&#xff1a;滑动窗口哈希表 题目1&#xff1a;30. 串联所有单词的子…

基于51单片机的直流电机调速设计

一.硬件方案 本系统采用STC89C51控制输出数据&#xff0c;由单片机IO口产生PWM信号&#xff0c;送到直流电机&#xff0c;直流电机通过测速电路将实时转速送回单片机&#xff0c;进行转速显示&#xff0c;从而实现对电机速度和转向的控制&#xff0c;达到直流电机调速的目的。…

20240528训练题目(2022 国际大学生程序设计竞赛亚洲区域赛 (南京站))

D题 题目描述 You’re the researcher of the International Chat Program Company (ICPC). Today, you discover the following chat history when reviewing some research data. SUA (2022/12/04 23:01:25) I’m out of ideas for competitive programming problems! Pl…

【TC8】如何测试IOP中PHY芯片的Llink-up time

在TC8一致性测试用例中,物理层的测试用例分为两个部分:IOP和PMA。其中IOP中对PHY芯片的Link-up时间的测试,又包含三个测试用例。它们分别是: OABR_LINKUP_01: Link-up time - Trigger: Power on Link PartnerOABR_LINKUP_02: Link-up time - Trigger: Power on DUTOABR_LIN…

qt把虚拟键盘部署到arm开发板上(imx6ull)

分为了qt官方配置的虚拟键盘以及各路大神自己开源的第三方键盘&#xff0c;我本来想尝试利用官方键盘结果一直失败&#xff0c;最后放弃了&#xff0c;后面我用的第三方键盘参考了如下文章&#xff1a; https://blog.csdn.net/2301_76250105/article/details/136441243 https…

算法基础之台阶-Nim游戏

台阶-Nim游戏 核心思想&#xff1a;博弈论 可以看作第i阶台阶上有i个含有i个石子的堆这样所有台阶上一共n!个堆就变成了经典Nim优化&#xff1a;发现偶数阶台阶上2n堆异或 0 , 奇数阶台阶异或 原本石子数量 因此 当遍历到奇数阶时异或一下就行 #include <iostream>…

git 学习随笔

git 学习随笔 基本概念 git 对待数据类似快照流的形式而不是类似 cvs 那样的纪录文件随时间逐步积累的差异 git 中所有数据在存储钱都会计算校验和&#xff08;hash) 三种状态&#xff1a;已提交(committed)&#xff0c;已修改(modified)&#xff0c;已暂存(staged)。 add…

二叉树习题精讲-单值二叉树

单值二叉树 965. 单值二叉树 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/univalued-binary-tree/description/ 判断这里面的所有数值是不是一样 方案1&#xff1a;遍历 方案2&#xff1a;拆分子问题 /*** Definition for a binary tree node.* struc…

AcWing 835. Trie字符串统计——算法基础课题解

AcWing 835. Trie 字符串统计 题目描述 维护一个字符串集合&#xff0c;支持两种操作&#xff1a; I x 向集合中插入一个字符串 &#x1d465;&#xff1b;Q x 询问一个字符串在集合中出现了多少次。 共有 &#x1d441; 个操作&#xff0c;所有输入的字符串总长度不超过 1…

k8s群集调度之 pod亲和 node亲和 标签指定

目录 一 调度约束 1.1K8S的 List-Watch 机制 ⭐⭐⭐⭐⭐ 1.1.1Pod 启动典型创建过程 二、调度过程 2.1Predicate&#xff08;预选策略&#xff09; 常见的算法 2.2priorities&#xff08;优选策略&#xff09;常见的算法 三、k8s将pod调度到指定node的方法 3.1指…

Python并发编程 06 进程、协程

文章目录 一、多进程调用二、Process类1、主要参数2、实例方法3、属性4、代码示例 三、进程通讯1、进程队列通讯2、管道通讯3、Manager对象 四、进程同步五、进程池六、协程1、协程简述2、用greenlet库实现协程3、用gevent库实现协程 一、多进程调用 与多线程调用相似 from m…

最强端侧多模态模型MiniCPM-V 2.5,8B 参数,性能超越 GPT-4V 和 Gemini Pro

前言 近年来&#xff0c;人工智能领域掀起了一股大模型热潮&#xff0c;然而大模型的巨大参数量级和高昂的算力需求&#xff0c;限制了其在端侧设备上的应用。为了打破这一局限&#xff0c;面壁智能推出了 MiniCPM 模型家族&#xff0c;致力于打造高性能、低参数量的端侧模型。…