3.MOC文件解读(下)——MOC文件中的函数

宏Q_OBJECT中的数据部分已经在在上一篇https://blog.csdn.net/Master_Cui/article/details/109007524分析完了,但是,MOC文件中还有一部分函数也在MOC文件中实现

对应的函数如下

 virtual const QMetaObject *metaObject() const; \
 virtual void *qt_metacast(const char *); \
 virtual int qt_metacall(QMetaObject::Call, int, void **); \
 Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \

除了上面的虚函数外,还有两个信号函数,由MOC实现

// SIGNAL 0
void moctest::sigf1(double _t1)
{
    void *_a[] = { nullptr, const_cast(reinterpret_cast(std::addressof(_t1))) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

// SIGNAL 1
int moctest::sigf2(char _t1, int _t2)
{
    int _t0{};
    void *_a[] = { const_cast(reinterpret_cast(std::addressof(_t0))), const_cast(reinterpret_cast(std::addressof(_t1))), const_cast(reinterpret_cast(std::addressof(_t2))) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
    return _t0;
}

两个信号函数代码都是先定义一个void*的指针数组,数组的第一个元素都是空,给元对系统内部注册元方法参数类型时使用,剩下的元素都是信号函数的参数的指针,然后将数组传入activate

 

virtual const QMetaObject *metaObject() const; 的实现

const QMetaObject *moctest::metaObject() const
{
    return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}

一般情况下,QObject::d_ptr->metaObject总为空,此时返回MOC文件中的数据staticMetaObject的地址,当使用qml编写界面程序时,会返回QObject::d_ptr->dynamicMetaObject()

 

virtual void *qt_metacast(const char *); 的实现

void *moctest::qt_metacast(const char *_clname)
{
    if (!_clname) return nullptr;
    if (!strcmp(_clname, qt_meta_stringdata_moctest.stringdata0))
        return static_cast(this);
    return QObject::qt_metacast(_clname);
}

这个函数首先判断字符串的指针是否为空,然后将字符串和MOC文件中表示类名的字符串进行比较,如果字符串内容是当前的类名,那么将当前对象的地址转为void*并返回,否则调用基类的qt_metacast,这个过程一直迭代到根上的基类 QObject::qt_metacast(_clname) 为止,如果 _clname 不在类的继承树上,那么返回值就是空。

 

virtual int qt_metacall(QMetaObject::Call, int, void **);的实现

int moctest::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 < 5)
            qt_static_metacall(this, _c, _id, _a);
        _id -= 5;
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
        if (_id < 5)
            *reinterpret_cast(_a[0]) = -1;
        _id -= 5;
    }
    return _id;
}

其中QMetaObject::Call是个枚举,在qobjectdefs.h中,枚举的定义如下:

enum Call {
        InvokeMetaMethod,
        ReadProperty,
        WriteProperty,
        ResetProperty,
        QueryPropertyDesignable,
        QueryPropertyScriptable,
        QueryPropertyStored,
        QueryPropertyEditable,
        QueryPropertyUser,
        CreateInstance,
        IndexOfMethod,
        RegisterPropertyMetaType,
        RegisterMethodArgumentMetaType
    };

InvokeMetaMethod 代表元方法调用,比如信号槽的调用。

然后从 ReadProperty 到 QueryPropertyUser ,是属性操作相关的。

CreateInstance 是用元构造函数生成新实例的调用方式。

IndexOfMethod qt_static_metacall()会根据这个调用方式,查询匹配的信号函数的相对序号。基类和当前类都有一大堆元方法,这些所有的元方法都有它的绝对序号和相对序号,绝对序号是从顶层基类 QObject 开始计数,相对序号从当前类开始计数。

RegisterPropertyMetaType是注册属性类型的调用方式

RegisterMethodArgumentMetaType是注册元方法参数类型的调用方式,与 qt_meta_data_moctest 数组元方法参数类型条目中信号槽参数(

// signals: parameters
QMetaType::Void, QMetaType::Double,    2,
QMetaType::Int, QMetaType::Char, QMetaType::Int,    2,    2,

 // slots: parameters
 QMetaType::Void, QMetaType::Double,    2,
 QMetaType::Int, QMetaType::Char,    2,
 QMetaType::Bool, QMetaType::Char,    2,

)对应。

 

对于信号槽的调用,_id 是元方法的绝对序号, _a 在信号函数里被封装,和信号函数中的_a对应

接着调用基类的qt_metacall

_id = QObject::qt_metacall(_c, _id, _a);

该方法的主要作用是重新获得信号槽的相对序号_id

然后

   if (_id < 0)
        return _id;//如果相对序号_id<0,说明被调用的信号槽不在当前的对象中,直接返回
    if (_c == QMetaObject::InvokeMetaMethod) {//要调用信号槽
        if (_id < 5)//被调用的信号槽在当前的对象中,调用qt_static_metacall对信号槽进行调用
            qt_static_metacall(this, _c, _id, _a);
        _id -= 5;//刷新id_
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {//如果是要对信号槽的参数进行匹配检查
        if (_id < 5)//被调用的信号槽在当前的对象中
            *reinterpret_cast(_a[0]) = -1;//将指针数组的第一个元素值为-1,表示匹配检查过了
        _id -= 5;
    }
    return _id;

所以,这个函数的作用就是:1、发起对信号槽函数的调用。2、对信号槽函数进行参数检查

 

static void qt_static_metacall的实现

void moctest::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast(_o);//将_o转化为当前对象的指针
        Q_UNUSED(_t)
        switch (_id) {//然后根据相对序号_id调用具体的信号槽函数
        case 0: _t->sigf1((*reinterpret_cast< double(*)>(_a[1]))); break;
        case 1: { int _r = _t->sigf2((*reinterpret_cast< char(*)>(_a[1])),(*reinterpret_cast< int(*)>(_a[2])));
            if (_a[0]) *reinterpret_cast< int*>(_a[0]) = std::move(_r); }  break;//如果信号槽函数有返回值,将返回值保存到指针数组的第一个元素指向的对象中
        case 2: _t->slotf((*reinterpret_cast< double(*)>(_a[1]))); break;
        case 3: { int _r = _t->slotf2((*reinterpret_cast< char(*)>(_a[1])));
            if (_a[0]) *reinterpret_cast< int*>(_a[0]) = std::move(_r); }  break;
        case 4: { bool _r = _t->slotf3((*reinterpret_cast< char(*)>(_a[1])));
            if (_a[0]) *reinterpret_cast< bool*>(_a[0]) = std::move(_r); }  break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {//如果是信号序号的查询调用,在QT5中,connect前必须检查信号是否为函数指针,然后在做关联,然后在_a[0]中可以知道
        int *result = reinterpret_cast(_a[0]);//先取得指针数组第一个元素的地址
        {
            using _t = void (moctest::*)(double );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&moctest::sigf1)) {//判断信号函数的类型与_t是否一致
                *result = 0;//如果一致,将result指向的内容变为信号的相对序号
                return;
            }
        }
        {
            using _t = int (moctest::*)(char , int );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&moctest::sigf2)) {
                *result = 1;
                return;
            }
        }
    }
}

至此,MOC文件中的代码解析完毕,然后,信号槽得需要connect进行连接,后面会解析connect函数

 

参考

https://qtguide.ustclug.org/

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

你可能感兴趣的