宏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<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
// SIGNAL 1
int moctest::sigf2(char _t1, int _t2)
{
int _t0{};
void *_a[] = { const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t0))), 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);
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<void*>(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<int*>(_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<int*>(_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<moctest *>(_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<int *>(_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函数
参考
Qt5.14源码
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出