首页 > 编程语言 >Qt源码解析——元对象系统热身

Qt源码解析——元对象系统热身

时间:2023-11-10 23:58:27浏览次数:126  
标签:const Qt int 热身 源码 QMetaObject static MainWindow qt

关键词:Qt 源码 QObject QMetaObject 元对象系统 属性 事件 信号 槽

概述

原系列文章地址

官方文档第二章内容就是元对象系统,它在介绍里描述到:

Qt的元对象系统提供了信号和槽机制(用于对象间的通信)、运行时类型信息和动态属性系统。

元对象系统基于三个要素:

  1. QObject类为那些可以利用元对象系统的对象提供了一个基类
  2. 在类声明的私有部分中使用Q_OBJECT宏用于启用元对象特性,比如动态属性、信号和槽。
  3. 元对象编译器(moc)为每个QObject子类提供必要的代码来实现元对象特性。

moc工具读取C++源文件,如果发现一个或多个包含Q_OBJECT宏的类声明,它会生成另一个C++源文件,其中包含了这些类的每个元对象的代码。这个生成的源文件被#include进入类的源文件,更常见的是被编译并链接到类的实现中。

引入这个系统的主要原因是信号和槽机制,此外它还提供了一些额外功能:

  • QObject::metaObject() 返回与该类相关联的元对象。
  • QMetaObject::className() 在运行时以字符串形式返回类名,而无需通过 C++ 编译器提供本地运行时类型信息(RTTI)支持。
  • QObject::inherits() 函数返回一个对象是否是在 QObject 继承树内继承了指定类的实例。
  • QObject::tr()QObject::trUtf8() 用于国际化的字符串翻译。
  • QObject::setProperty()QObject::property() 动态地通过名称设置和获取属性。
  • QMetaObject::newInstance() 构造该类的新实例。

上面说到的元对象系统三要素,第3点moc会在后面用单独篇章分析,下面就不再展开,第1点我们在上一篇中做了简单的分析,本篇我们看看第2点——Q_OBJECT到底怎么启用了元对象系统(然而启用非常复杂,我们先浏览个大概,所以标题叫热身)。

staticMetaObject

找到源码中出现QMetaObject的地方:

//qobject.h
class Q_CORE_EXPORT Qobject{
    Q_OBJECT
    //...
protected:
    static const QMetaObject staticQtMetaObject;
    //...
}

QMetaObject相关的变量只有2个地方出现,既然前面说了Q_OBJECT和元对象系统相关,那我们就直接看Q_OBJECT的定义:

//qobjectdefs.h
#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, "")

我们关注变量static const QMetaObject staticMetaObject,这是一个QMetaObject类型的静态变量,它应该是和元对象系统相关,文档对QMetaObject的描述:

QMetaObject类包含有关Qt对象的元信息。每个在应用程序中使用的QObject子类都会创建一个QMetaObject实例,该实例存储了该QObject子类的所有元信息。此对象可通过QObject::metaObject()方法获得。

QMetaObject就是元对象系统的关键了,查看QMetaObject的定义:

//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject{
    //...
    struct { // private data
        const QMetaObject *superdata;
        const QByteArrayData *stringdata;
        const uint *data;
        typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
        StaticMetacallFunction static_metacall;
        const QMetaObject * const *relatedMetaObjects;
        void *extradata; //reserved for future use
    } d;
}

QMetaObject是个结构体,没有构造函数。忽略掉所有方法声明,只剩一个结构体变量,而且我们在qobject.cpp中也没有看到staticMetaObject对应的初始化。那会不会在子类中初始化了?我们新建一个空的QMainWindow工程,继承关系是这样的:

//MainWindow->QMainWindow->QWidget->QObject

遗憾的是我们并没有在MainWindowQMainWindowQWidget的构造器中找到staticMetaObject初始化的痕迹。

moc_mainwindow.cpp

想起来官方文档说moc会处理Q_OBJECT宏,那就去moc文件找找——果然找到了staticMetaObject相关的语句:

//moc_mainwindow.cpp
QT_INIT_METAOBJECT const QMetaObject MainWindow::staticMetaObject = { {
    &QMainWindow::staticMetaObject,
    qt_meta_stringdata_MainWindow.data,
    qt_meta_data_MainWindow,
    qt_static_metacall,
    nullptr,
    nullptr
} };

结合QMetaObject的声明,我们很容易看出这是在对QMetaObject的变量赋值:

变量名
const QMetaObject *superdata &QMainWindow::staticMetaObject
const QByteArrayData *stringdata qt_meta_stringdata_MainWindow.data
const uint *data qt_meta_data_MainWindow
StaticMetacallFunction static_metacall qt_static_metacall
const QMetaObject * const *relatedMetaObjects nullptr
void *extradata nullptr

对于const QMetaObject *superdata = &QMainWindow::staticMetaObject;

MainWindowstaticMetaObjectsuperdata持有了QMainWindowstaticMetaObject``,说明MainWindow可以访问QMainWindowstaticMetaObject。由于并不能看到moc_qmainwindow.cpp等,我们只能从变量名合理猜测任何类的staticMetaObject都持有了父类的staticMetaObject

做个实验测试一下:

//mainwindow.cpp
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    //...
    const QMetaObject *metaDta = staticMetaObject.d.superdata;
    while(metaDta){
        qDebug() << metaDta->className();
        metaDta = metaDta->d.superdata;
    }
}

/*
输出结果:
QMainWindow
QWidget
QObject
*/

果不其然,打印结果是输出了MainWindow所有父类的className。那么我们基本可以断定,继承链中staticMetaObject的持有关系如下图所示:

对于const QByteArrayData *stringdata = qt_meta_stringdata_MainWindow.data;

moc文件里找到qt_meta_stringdata_MainWindow变量:

//moc_mainwindow.cpp
static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {
    {
QT_MOC_LITERAL(0, 0, 10) // "MainWindow"

    },
    "MainWindow"
};

qt_meta_stringdata_MainWindow是一个qt_meta_stringdata_MainWindow_t类型,这里对它进行了初始化。继续找到qt_meta_stringdata_MainWindow_t的定义:

//moc_mainwindow.cpp
struct qt_meta_stringdata_MainWindow_t {
    QByteArrayData data[1];
    char stringdata0[11];
};

也就是说stringdata的值为QT_MOC_LITERAL(0, 0, 10) // "MainWindow"

继续找到QT_MOC_LITERAL的定义:

//moc_mainwindow.cpp
#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)) \
    )

这个宏的作用是创建一个静态的 QByteArrayData 结构体,该结构体包含了字符串字面值的元数据。再结合注释我们推断stringdata代表"MainWindow"字符串,这里似乎是保存的类名MainWindow。从变量名qt_meta_stringdata_MainWindow推断,这个变量应该就是保存的元对象相关的字符串字面量,但我们默认工程没有元对象,我们在代码中加一个signal

//mainwindow.h
signals:
    void testSignal();

重新编译,可以看到,qt_meta_stringdata_MainWindow变量的初始化有所改变,从注释看明显包含了我们所加信号的名称:

//moc_mainwindow.cpp
static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {
    {
QT_MOC_LITERAL(0, 0, 10), // "MainWindow"
QT_MOC_LITERAL(1, 11, 10), // "testSignal"
QT_MOC_LITERAL(2, 22, 0) // ""
    },
    "MainWindow\0testSignal\0"
};

对于const uint *data = qt_meta_data_MainWindow;

moc文件中找到qt_meta_data_MainWindow定义,它是一个uint数组,目前还看不出它的作用。

//moc_mainwindow.cpp
static const uint qt_meta_data_MainWindow[] = {
 // content:
       8,       // revision
       0,       // classname
       0,    0, // classinfo
       0,    0, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       0,       // flags
       0,       // signalCount
       0        // eod
};

对于StaticMetacallFunction static_metacall = qt_static_metacall;

moc文件里找到qt_static_metacall定义,如果是默认工程,似乎也不做什么:

//moc_mainwindow.cpp
void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    Q_UNUSED(_o);
    Q_UNUSED(_id);
    Q_UNUSED(_c);
    Q_UNUSED(_a);
}

对于const QMetaObject * const *relatedMetaObjects = nullptr;void *extradata = nullptr;暂时不讨论。

我们目前找到了staticMetaObject初始化的位置,知道它被赋值了一些数据结构,这些数据结构都和moc相关。

QMetaObject其他成员

回过头来,我们看看QMetaObject的其他成员。

//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject
{
    class Connection;
	//...
}

class Q_CORE_EXPORT QMetaObject::Connection {
    //...
};

ConnectionQMetaObject的内部类,文档描述:

Represents a handle to a signal-slot (or signal-functor) connection.

它代表了信号-槽的连接,那就是说我们平常使用的connect都和它相关,是个非常重要的角色。

我们可以看看我们一般使用的connect的定义:

//qobject.h
template <typename Func1, typename Func2>
    static inline typename std::enable_if<QtPrivate::FunctionPointer<Func2>::ArgumentCount == -1, QMetaObject::Connection>::type
            connect(/*...*/)
    {
        //...
        return connectImpl(/*...*/);
    }

调用了connectImpl()

//qobject.h
static QMetaObject::Connection connectImpl(/*...*/);

的确是返回了QMetaObject::Connection,由此可见Connection是信号-槽系统的关键角色,它代表了一个建立的连接。

再看看其他接口:

//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject
{
	//...
    //基本信息
    const char *className() const;
    const QMetaObject *superClass() const;
    bool inherits(const QMetaObject *metaObject) const Q_DECL_NOEXCEPT;
    //和类信息相关
    int classInfoOffset() const;
    int classInfoCount() const;
    int indexOfClassInfo(const char *name) const;
    QMetaClassInfo classInfo(int index) const;
    //和方法相关
    int methodOffset() const;
    int methodCount() const;
    int indexOfMethod(const char *method) const;
    QMetaMethod method(int index) const;
    //和枚举相关
    int enumeratorOffset() const;
    int enumeratorCount() const;
    int indexOfEnumerator(const char *name) const;
    QMetaEnum enumerator(int index) const;
	//和属性相关
    int propertyOffset() const;
    int propertyCount() const;
    int indexOfProperty(const char *name) const;
    QMetaProperty property(int index) const;
    QMetaProperty userProperty() const;
	//和构造器相关
    int constructorCount() const;
    int indexOfConstructor(const char *constructor) const;
    QMetaMethod constructor(int index) const;
	//和信号、槽相关
    int indexOfSignal(const char *signal) const;
    int indexOfSlot(const char *slot) const;
    static bool checkConnectArgs(const char *signal, const char *method);
    static bool checkConnectArgs(const QMetaMethod &signal,
                                 const QMetaMethod &method);
    static QByteArray normalizedSignature(const char *method);
    static QByteArray normalizedType(const char *type);
    //...
}


这些方法几乎提供了获取所有"元成员"信息的方式(好玩的是源码作者强迫症一样地把功能类似的方法放到了一起),包括构造器、方法、属性等,之所以说“元成员”,是因为被Q_INVOKABLEQ_PROPERTY等宏修饰的成员才具有"元能力"(当然,这也是后话了)。熟悉其他语言中反射特性的同学应该对这些方法的构成和名字比较熟悉,元对象系统的确为Qt提供了类似反射的能力。

接下来是和信号-槽相关的接口:

//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject
{
    // internal index-based connect
    static Connection connect(const QObject *sender, int signal_index,
                              const QObject *receiver, int method_index,
                              int type = 0, int *types = nullptr);
    // internal index-based disconnect
    static bool disconnect(const QObject *sender, int signal_index,
                           const QObject *receiver, int method_index);
    //...
    // internal index-based signal activation
    static void activate(QObject *sender, int signal_index, void **argv);
    //...
}


从注释来看,这些接口用于内部,是以索引为基础的一些方法,暂时没接触到它们使用的场景。

接下来是很多重载或者模板的invokeMethod()

//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject
{
    //...
    invokeMethod(/*...*/);
    //...
}

官方文档说明:

Invokes the member (a signal or a slot name) on the object obj

看来是用于调用obj的信号或者槽。

接下来是newInstance()

//qobjectdefs.h
struct Q_CORE_EXPORT QMetaObject
{
    //...
    QObject *newInstance(/*...*/);
    //...
}

它是用来调用构造函数的。

总结

热身就到这里,总结一下,Q_OBJECT宏用于启用元对象特性,其中staticMetaObject的初始化在moc_xxx.cpp中进行,moc_xxx.cpp包含了许多“元成员”的字符串信息和实现。QMetaObject是元对象系统的关键成员,提供了元信息的接口。

标签:const,Qt,int,热身,源码,QMetaObject,static,MainWindow,qt
From: https://www.cnblogs.com/joggingjack/p/17825365.html

相关文章

  • Linux软件包(源码包和二进制包)
    Linux下的软件包众多,且几乎都是经GPL授权、免费开源(无偿公开源代码)的。这意味着如果你具备修改软件源代码的能力,只要你愿意,可以随意修改。GPL,全称GeneralPublicLicense,中文名称“通用性公开许可证”,简单理解GPL就是一个保护软件自由的一个协议,经GPL协议授权的软件必须开源......
  • Vue源码学习(十六):diff算法(三)暴力比对
    好家伙,这是diff的最后一节了 0.暴力比对的使用场景 没有可复用的节点:当新旧虚拟DOM的结构完全不同,或者某个节点不能被复用时,需要通过暴力比对来创建新的节点,并在真实DOM上进行相应的插入操作。0.1.例子一://创建vnodeletvm1=newVue({data:{name:'张三'......
  • APISIX源码安装问题解决
    官网手册的安装语句:curlhttps://raw.githubusercontent.com/apache/apisix/master/utils/install-dependencies.sh-sL|bash-执行install-dependencies.sh报如下错误:Transactioncheckerror:file/usr/share/gcc-4.8.2/python/libstdcxx/v6/printers.pyfrominstal......
  • qt代码积累
    QT自适应电脑分辨率,在main函数中最前端设置如下代码intmain(intargc,char*argv[]){#if(QT_VERSION>QT_VERSION_CHECK(5,6,0))QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);#endifQApplicationa(argc,argv);appapp;returna.ex......
  • 车联网场景中的MQTT协议应用
    基本概念解释MQTT解释MQTT(MessageQueuingTelemetryTransport)是一种轻量级、基于TCP/IP协议栈构建的异步通信,和发布-订阅模式的消息传输协议。适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它在物联网应用中广受欢迎,能够实现传感器、执行器和其它设备之间的高效通信......
  • PyQt5-QLabel控件是什么?方法有哪些?具体如何使用?
    (QLabel控件是什么?方法有哪些?具体如何使用?)1QLabel控件作用?QLabel控件用于显示不可编辑的文本或图片,也用于设置超链接、富文本,以及作为其他控件的;QLabel控件是PyQt5是一个非常常用又非常基础的一个控件。2QLabel源码源码路径:PyQt5\QtWidgets\QLabel.py部分源码:cla......
  • 智慧工地源码:建筑管理的新型方式
    智慧工地是利用物联网、云计算、大数据等技术,实现对建筑工地实时监测、管理和控制的一种新型建筑管理方式,其中,数据分析功能起着至关重要的作用。1、数据采集智慧工地中的数据采集主要通过传感器、监控摄像头等设备进行。这些设备可以实时采集到工地的环境数据、施工人员和设备信息......
  • qt QCharts 设置背景,背景透明
     classChartwidget:publicQChartViewvoidChartwidget::setBackground(){m_chart->setBackgroundVisible(false);//去背景QColorcolor(255,255,255,0);QPalettepal(palette());pal.setColor(QPalette::Window,color);setAutoFillBac......
  • (四)Spring源码解析:bean的加载流程解析
    一、概述在前几讲中,我们着重的分析了Spring对xml配置文件的解析和注册过程。那么,本节内容,将会试图分析一下bean的加载过程。具体代码,如下图所示:1.1>doGetBean(...)针对bean的创建和加载,我们可以看出来逻辑都是在doGetBean(...)这个方法中的,所以,如下就是针对于这个方法的整体源码注......
  • TiDB 源码编译之 TiProxy 篇
    作者:ShawnYanTiProxy简介TiProxy是一个基于Apache2.0协议开源的、轻量级的TiDB数据库代理,基于Go语言编写,支持MySQL协议。TiProxy支持负载均衡,接收来自应用程序的请求,然后将其发送到TiDB集群。支持自动故障转移,当后端TiDBServer发生故障,可以自动将连接转移到......