Qt moc系统的黑魔法?

2024-05-25 05:52
文章标签 系统 qt moc 黑魔法

本文主要是介绍Qt moc系统的黑魔法?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Qt的元对象系统(Meta-Object System)是Qt框架的核心功能之一,为C++语言增加了一些动态特性,借助元对象系统Qt可以实现以下功能

  • 信号与槽机制(Signals and Slots)
  • 运行时类型信息(Run-Time Type Information, RTTI)
  • 属性系统(Property System)
  • 动态对象创建(Dynamic Object Creation)
  • 动态方法调用(Dynamic Method Invocation)
  • 安全的类型转换(Dynamic Cast)

元对象系统的实现需要借助moc (Meta-Object_Compiler)工具, moc工具扫描包含Q_OBJECT宏的头文件,为为我们自动生成了元对象系统的实现代码,我们以一个简单的MainWindow例子来探究一下moc系统有什么黑魔法,示例完整代码放在文章最后。

QObject派生类的元信息编码和存储

元(Meta)在C++中是和编译期相关的概念,元对象用来存储一个类在编译器就确定的类型相关信息, 即每一个类的类型和方法信息都是编译期都是已经确定了的,在一般自己实现C++反射系统的方案中,存储类的元信息,无不例外的都是借助一个静态成员变量,由于静态变量是存储在静态存储区,生命周期伴随整个程序,并且其初始化是在程序加载后,main入口函数之前,所以用一个静态类来存储元信息便很自然了,如下,Qt中就是给每一个定义Q_OBJECT宏的类定义了一个static QMetaObject成员对象。

staticMetaObject这个静态成员的定义就在moc工具自动生成的moc_mainwindow.cpp文件之中,其中主要包含类的派生信息和信号与槽函数信息。

qt_meta_stringdata_MainWindow和qt_meta_data_MainWindow的定义在下面两张截图之中,我们先来看一下qt_meta_stringdata_MainWindow,这个结构里面将信号和槽函数的信息编码存储到了一个字符串之中,QT_MOC_LITERAL(0, 0, 10)中,第一个参数为编号,第二个为在下面字符串中的起始位置,第二个参数为函数名称的长度。

qt_meta_data_MainWindow这个结构指定了每个信号和槽函数在上面的“字符串”中的位置以及函数的返回值,参数类型信息,这两个结构就编码存储了信号槽函数的所有信息了。

槽函数的调用

这里先讨论一个问题,如果知道一个函数的字符串名称,怎么调用这个函数? 注意,这里的问题是,你现在只知道一个字符串, 虽然你知道它是那个函数,但是你怎么来调用?

std::string func_name = "my_slot";
//how to call my_slot() ?
func_name()?  // this is error~//the possiable way
if(func_name == "my_slot"){my_slot();
} else if( func_name = "my_slot1"){my_slot1();
}

在老版本的Qt中,connect链接信号和槽的时候,也是直接连接的函数字符串名称,通过字符串来调用函数,就不得不借用我们moc文件里面生成的qt_static_metacall了,这里的实现也是很明了,首先找到函数的索引,然后通过switch case进行函数调用。

void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{if (_c == QMetaObject::InvokeMetaMethod) {auto *_t = static_cast<MainWindow *>(_o);Q_UNUSED(_t)switch (_id) {case 0: _t->my_signal(); break;case 1: _t->my_signal_param((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< double(*)>(_a[2]))); break;case 2: _t->on_actionExit_triggered(); break;case 3: _t->my_slot(); break;case 4: _t->my_slot_param((*reinterpret_cast< int(*)>(_a[1]))); break;default: ;}} else if (_c == QMetaObject::IndexOfMethod) {int *result = reinterpret_cast<int *>(_a[0]);{using _t = void (MainWindow::*)();if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::my_signal)) {*result = 0;return;}}{using _t = void (MainWindow::*)(int , double );if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::my_signal_param)) {*result = 1;return;}}}
}

信号函数的实现

我们在定义信号的时候,只给出了一个信号函数的申明,信号函数的实现也是moc自动生成的

// SIGNAL 0
void MainWindow::my_signal()
{QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}// SIGNAL 1
void MainWindow::my_signal_param(int _t1, double _t2)
{void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))), const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

总结:

我们可以发现,QT的元对象系统并没有什么神奇之处,主要干了下面几件事情

  • 通过一个静态成员QMetaObject存储类的元信息
  • 通过一个字符串编码存储了信号和槽函数信息
  • 通过信号和槽函数的索引,在switch case中调用槽函数
  • 自动生成信号函数的实现代码

mainwindow示例:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = 0);explicit MainWindow(QWidget* parent, int id);~MainWindow();void my_general_fun();private slots:void on_actionExit_triggered();void my_slot();void my_slot_param(int iiii);signals:void my_signal();void my_signal_param(int jjjj, double kkkk);private:Ui::MainWindow *ui;
};#endif // MAINWINDOW_H

moc生成的实现代码:

/****************************************************************************
** Meta object code from reading C++ file 'mainwindow.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.15.2)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/#include <memory>
#include "../../../../mainwindow.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'mainwindow.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.15.2. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endifQT_BEGIN_MOC_NAMESPACE
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
struct qt_meta_stringdata_MainWindow_t {
QByteArrayData data[10];
char stringdata0[99];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_MainWindow_t, stringdata0) + ofs \
- idx * sizeof(QByteArrayData)) \
)
static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {
{
QT_MOC_LITERAL(0, 0, 10), // "MainWindow"
QT_MOC_LITERAL(1, 11, 9), // "my_signal"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 15), // "my_signal_param"
QT_MOC_LITERAL(4, 38, 4), // "jjjj"
QT_MOC_LITERAL(5, 43, 4), // "kkkk"
QT_MOC_LITERAL(6, 48, 23), // "on_actionExit_triggered"
QT_MOC_LITERAL(7, 72, 7), // "my_slot"
QT_MOC_LITERAL(8, 80, 13), // "my_slot_param"
QT_MOC_LITERAL(9, 94, 4) // "iiii"},
"MainWindow\0my_signal\0\0my_signal_param\0"
"jjjj\0kkkk\0on_actionExit_triggered\0"
"my_slot\0my_slot_param\0iiii"
};
#undef QT_MOC_LITERALstatic const uint qt_meta_data_MainWindow[] = {
// content:
8,       // revision
0,       // classname
0,    0, // classinfo
5,   14, // methods
0,    0, // properties
0,    0, // enums/sets
0,    0, // constructors
0,       // flags
2,       // signalCount// signals: name, argc, parameters, tag, flags
1,    0,   39,    2, 0x06 /* Public */,
3,    2,   40,    2, 0x06 /* Public */,// slots: name, argc, parameters, tag, flags
6,    0,   45,    2, 0x08 /* Private */,
7,    0,   46,    2, 0x08 /* Private */,
8,    1,   47,    2, 0x08 /* Private */,// signals: parameters
QMetaType::Void,
QMetaType::Void, QMetaType::Int, QMetaType::Double,    4,    5,// slots: parameters
QMetaType::Void,
QMetaType::Void,
QMetaType::Void, QMetaType::Int,    9,0        // eod
};void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{if (_c == QMetaObject::InvokeMetaMethod) {auto *_t = static_cast<MainWindow *>(_o);Q_UNUSED(_t)switch (_id) {case 0: _t->my_signal(); break;case 1: _t->my_signal_param((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< double(*)>(_a[2]))); break;case 2: _t->on_actionExit_triggered(); break;case 3: _t->my_slot(); break;case 4: _t->my_slot_param((*reinterpret_cast< int(*)>(_a[1]))); break;default: ;}} else if (_c == QMetaObject::IndexOfMethod) {int *result = reinterpret_cast<int *>(_a[0]);{using _t = void (MainWindow::*)();if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::my_signal)) {*result = 0;return;}}{using _t = void (MainWindow::*)(int , double );if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::my_signal_param)) {*result = 1;return;}}}
}QT_INIT_METAOBJECT const QMetaObject MainWindow::staticMetaObject = { {QMetaObject::SuperData::link<QMainWindow::staticMetaObject>(),qt_meta_stringdata_MainWindow.data,qt_meta_data_MainWindow,qt_static_metacall,nullptr,nullptr
} };const QMetaObject *MainWindow::metaObject() const
{return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}void *MainWindow::qt_metacast(const char *_clname)
{if (!_clname) return nullptr;if (!strcmp(_clname, qt_meta_stringdata_MainWindow.stringdata0))return static_cast<void*>(this);return QMainWindow::qt_metacast(_clname);
}int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{_id = QMainWindow::qt_metacall(_c, _id, _a);if (_id < 0)return _id;if (_c == QMetaObject::InvokeMetaMethod) {if (_id < 5)qt_static_metacall(this, _c, _id, _a);_id -= 5;} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {if (_id < 5)*reinterpret_cast<int*>(_a[0]) = -1;_id -= 5;}return _id;
}// SIGNAL 0
void MainWindow::my_signal()
{QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}// SIGNAL 1
void MainWindow::my_signal_param(int _t1, double _t2)
{void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))), const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE

这篇关于Qt moc系统的黑魔法?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1000656

相关文章

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

Linux系统之dns域名解析全过程

《Linux系统之dns域名解析全过程》:本文主要介绍Linux系统之dns域名解析全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、dns域名解析介绍1、DNS核心概念1.1 区域 zone1.2 记录 record二、DNS服务的配置1、正向解析的配置

Linux系统中配置静态IP地址的详细步骤

《Linux系统中配置静态IP地址的详细步骤》本文详细介绍了在Linux系统中配置静态IP地址的五个步骤,包括打开终端、编辑网络配置文件、配置IP地址、保存并重启网络服务,这对于系统管理员和新手都极具... 目录步骤一:打开终端步骤二:编辑网络配置文件步骤三:配置静态IP地址步骤四:保存并关闭文件步骤五:重

Qt 中 isHidden 和 isVisible 的区别与使用小结

《Qt中isHidden和isVisible的区别与使用小结》Qt中的isHidden()和isVisible()方法都用于查询组件显示或隐藏状态,然而,它们有很大的区别,了解它们对于正确操... 目录1. 基础概念2. 区别清见3. 实际案例4. 注意事项5. 总结1. 基础概念Qt 中的 isHidd

QT移植到RK3568开发板的方法步骤

《QT移植到RK3568开发板的方法步骤》本文主要介绍了QT移植到RK3568开发板的方法步骤,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录前言一、获取SDK1. 安装依赖2. 获取SDK资源包3. SDK工程目录介绍4. 获取补丁包二

Qt把文件夹从A移动到B的实现示例

《Qt把文件夹从A移动到B的实现示例》本文主要介绍了Qt把文件夹从A移动到B的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录如何移动一个文件? 如何移动文件夹(包含里面的全部内容):如何删除文件夹:QT 文件复制,移动(

Windows系统下如何查找JDK的安装路径

《Windows系统下如何查找JDK的安装路径》:本文主要介绍Windows系统下如何查找JDK的安装路径,文中介绍了三种方法,分别是通过命令行检查、使用verbose选项查找jre目录、以及查看... 目录一、确认是否安装了JDK二、查找路径三、另外一种方式如果很久之前安装了JDK,或者在别人的电脑上,想

Linux系统之authconfig命令的使用解读

《Linux系统之authconfig命令的使用解读》authconfig是一个用于配置Linux系统身份验证和账户管理设置的命令行工具,主要用于RedHat系列的Linux发行版,它提供了一系列选项... 目录linux authconfig命令的使用基本语法常用选项示例总结Linux authconfi