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_OBJECT

public:
    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.)"
#endif

QT_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_LITERAL

static 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

  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值