当信号被调用时,与其关联的槽函数会被调用。调用时机与连接类型有关。
通过映射的方式实现:通过将信号槽建立一个映射。当信号被调用的时候,通过访问映射表,调用其对应的槽函数。
如果将信号用字符串存储,将槽函数用函数指针存储,那么对应的结构可以为:
std::mapcallback> m_callbackMap;
实现链接
connect(sendr,sig,receive,slot)
{
map.insert(sig,slot);//sig为string, slot为function
}
调用
void invok(const std::string &name)
{
auto it = m_callbackMap.find(name);
//迭代器判断
if (it != m_callbackMap.end()) {
//迭代器有效的情况,直接调用
it->second();
}
}
#define Q_OBJECT \
public: \
Q_OBJECT_CHECK \
static const QMetaObject staticMetaObject; \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
QT_TR_FUNCTIONS \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
private: \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
struct QPrivateSignal {};
函数实体代码都由 moc 工具自动生成,保存在 moc_*.cpp 里面,也就是当前类支持元对象系统的关键内部代码。
下面来分析一下:
static const QMetaObject staticMetaObject;
这句定义了关键的静态元对象 staticMetaObject。这个对象会保存该类的元对象系统信息。使用静态元对象,说明该类的所有实例都会共享这个静态元对象,而不需要重复占用内存。
virtual const QMetaObject *metaObject() const;
这个虚函数是获取当前类对象里面内部元对象的公开接口,通常情况下都会返回类的静态元对象 staticMetaObject,如果当前类的对象内部使用了动态元对象(仅 QML 程序才有),才会出现返回非静态元对象。
virtual void *qt_metacast(const char *);
qt_metacast 是程序运行时的对象指针转换,它可以将派生类对象的指针安全地转为基类对象指针,这是 Qt 不依赖编译器特性,自己实现的运行时类型转换。qt_metacast 参数是基类名称字符串,返回值是转换后的基类对象指针,如果转换不成功,返回 NULL。
QT_TR_FUNCTIONS
这个宏声明用于定义翻译的 tr() 和 trUtf8() 两个内联函数,其实本质都是调用 staticMetaObject.tr() 函数。
virtual int qt_metacall(QMetaObject::Call, int, void **);
qt_metacall 是非常重要的虚函数,在信号到槽的执行过程中,qt_metacall 就是负责槽函数的调用,属性系统的读写等也是靠 qt_metacall 实现。后面专门会讲它的源码。
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);
这一个私有的静态函数,Q_DECL_HIDDEN_STATIC_METACALL 是个空宏,没啥用,就是提醒程序员这是一个隐蔽的私有静态函数。前面的 qt_metacall 会调用该私有静态函数实现槽函数调用,真正调用槽函数的就是 qt_static_metacall。
struct QPrivateSignal {};
QPrivateSignal 是一个私有的空结构体,对函数功能来说没啥用,就是在信号被触发时,挂在参数里提醒程序员这是一个私有信号的触发。
#ifndef QT_NO_SIGNALS_SLOTS_KEYWORDS
#define slots
#define signals public
#endif
# define Q_SLOTS
# define Q_SIGNALS public
上面宏定义是说,如果没有定义不能使用 Qt 信号和槽关键字的情况下,启用 slots 和 signals 关键字定义,slots 根本就是空宏,什么都没有。signals 就是 C++ 关键字 public。
class Sig_SLOT : public QDialog
{
Q_OBJECT
public:
Sig_SLOT(QWidget *parent = Q_NULLPTR);
void init();
private slots:
void slot_set_label_number();
signals:
void label_number_change(int value);
void number_change(int value, QString time);
private:
Ui::Sig_SLOTClass ui;
};
既然声明的signals是一个public,那么moc如何处理signals呢?
可以看到在moc_xxx.cpp中,信号由moc函数自动生成了。
// SIGNAL 0
void Sig_SLOT::label_number_change(int _t1)
{
void *_a[] = { nullptr, const_cast(reinterpret_cast(&_t1)) };
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
// SIGNAL 1
void Sig_SLOT::number_change(int _t1, QString _t2)
{
void *_a[] = { nullptr, const_cast(reinterpret_cast(&_t1)), const_cast(reinterpret_cast(&_t2)) };
QMetaObject::activate(this, &staticMetaObject, 1, _a);
}
可以看到信号最终将会变为一个函数
程序员不能给信号编写函数实体代码,那是因为必须由 moc 工具为信号生成实体代码,如果程序员自作聪明编一个信号函数实体,程序编译时会报错,因为重名的函数参数还一样,会报重定义错误。
_ a 的定义,_ a 里面第一个指针是 Q_NULLPTR(就是 NULL),这个指针是预留给 元对系统内部注册元方法参数类型时使用;第二个指针是参数_t1 的指针;如果有更多的参数,那么会继续将指针填充到 _a 里面。_a 是用于传递从信号到槽函数的参数的。
为什么信号的参数可以比槽函数的参数多?因为槽函数接收到 _a 指针数组时,只需要取出自己需要的前面几个参数就够了,槽函数不管多余的参数。信号里的参数不能比槽函数里的少,那样槽函数访问指针数组时会越界,造成内存访问错误。
QMetaObject::activate 函数是负责联络接收方槽函数的,它根据源头对象指针 this、源头的元对象指针 &staticMetaObject、信号序号 2、信号参数数组 _a 去找寻需要激活的槽函数,最终会调用每个关联到该信号的槽函数
结论:qt_meta_stringdata_XXX_t 中存储了类名、信号、信号参数、槽函数、槽函数参数,属性名 等字符串变量。分为两部分存储:QByteArraData数组及stringdata0。其QByteArrayData存入了访问stringdata0子字符串的指针变量及长度。通过QByteArrayData::data()函数可以方便的访问qt_meta_stringdata_Sig_SLOT_t 实例中的字符串。基于这些字符串,Qt 程序才能在运行时自行查询类的名称、根据元方法名称字符串调用元方法、根据属性名称查询设置属性值等。
解析:
struct qt_meta_stringdata_Sig_SLOT_t {
QByteArrayData data[8];
char stringdata0[85];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_Sig_SLOT_t, stringdata0) + ofs \
- idx * sizeof(QByteArrayData)) \
)
static const qt_meta_stringdata_Sig_SLOT_t qt_meta_stringdata_Sig_SLOT = {
{
QT_MOC_LITERAL(0, 0, 8), // "Sig_SLOT"
QT_MOC_LITERAL(1, 9, 19), // "label_number_change"
QT_MOC_LITERAL(2, 29, 0), // ""
QT_MOC_LITERAL(3, 30, 10), // "textNumber"
QT_MOC_LITERAL(4, 41, 13), // "number_change"
QT_MOC_LITERAL(5, 55, 2), // "va"
QT_MOC_LITERAL(6, 58, 4), // "time"
QT_MOC_LITERAL(7, 63, 21) // "slot_set_label_number"
},
"Sig_SLOT\0label_number_change\0\0textNumber\0"
"number_change\0va\0time\0slot_set_label_number"
};
#undef QT_MOC_LITERAL
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_Sig_SLOT_t, stringdata0) + ofs \
- idx * sizeof(QByteArrayData)) \
)
QT_MOC_LITERAL 宏的意义就是根据三元组 (idx, ofs, len) 计算并填充 QByteArrayData 实例里面 size 和 offset 两个成员变量(其他成员用固定值),然后通过 QByteArrayData::data() 函数就能获取 stringdata 里序号为 idx 的字符串起始位置指针。 :
size代表字符串长度,offset表示通过QByteArray实例访问stringdata0中子字符串首地址的偏移量(data函数)。
offset 计算方式比较特别:
qptrdiff(offsetof(qt_meta_stringdata_Sig_SLOT_t, stringdata0) + ofs - idx * sizeof(QByteArrayData))
外层的 qptrdiff 就是把里面的数据长度变得与系统平台指针长度一样,对于 32 位系统把里面数据变成 32 位长度 qint32,对于 64 位系统把里面的数据变成 64 位长度 qint64。本人测试用的 32 位系统,所以不用管外层的 qptrdiff() ,当 qint32 类型数值就行了。
offsetof(qt_meta_stringdata_Sig_SLOT_t, stringdata0)是获取stringdata0在整个结构体中的偏移量。
减去idx * sizeof(QByteArrayData))的原因
:QByateArray访问字符串的机制导致。我们可以看到,QArrayData内部本身并不存储字符串,他只是有五个成员,这里只会用到size和offset。
struct Q_CORE_EXPORT QArrayData
{
QtPrivate::RefCount ref;
int size;
uint alloc : 31;
uint capacityReserved : 1;
qptrdiff offset; // in bytes from beginning of header
void *data()
{
Q_ASSERT(size == 0
|| offset < 0 || size_t(offset) >= sizeof(QArrayData));
return reinterpret_cast(this) + offset;
}
const void *data() const
{
Q_ASSERT(size == 0
|| offset < 0 || size_t(offset) >= sizeof(QArrayData));
return reinterpret_cast(this) + offset;
}
//后面无关的都忽略掉
};
因此,如果想通过QArrayData访问qt_meta_string_xxx_t中的元对象槽函数及信息,那么必须将string_data0中每个字符串对应的size和offset填入QArrayData中。由于QArrayData中data是this+offset。因此每个QArrayData的this地址会相对qt_meta_stringdata_Sig_SLOT的地址增加一个sizeof(QArrayData)个单位。 要想使用QArrayData的data函数正确访问到对应的字符串,那么我们必须对offset进行修正,由于this指针的地址每次递增sizeof(QArrayData)大小,因此我们必须要将offset-sizeof(QArrayData)*index,这样才能访问stringdata0中相应的字符串。
注:16 = sizeof(QByteArrayData)
我们可以通过一下代码将结果打印出来
for (int i=0; i<8; i++)
{
printf("%s\n", qt_meta_stringdata_Sig_SLOT.data[i].data());
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yy2987oB-1604499482958)(C:\Users\ChengKeKe\AppData\Roaming\Typora\typora-user-images\image-20201102133222695.png)]
static const uint qt_meta_data_Sig_SLOT[] = {
// content:
7, // revision
0, // classname
0, 0, // classinfo
3, 14, // methods
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
2, // signalCount
// signals: name, argc, parameters, tag, flags
1, 1, 29, 2, 0x06 /* Public */,
4, 2, 32, 2, 0x06 /* Public */,
// slots: name, argc, parameters, tag, flags
7, 0, 37, 2, 0x08 /* Private */,
// signals: parameters
QMetaType::Void, QMetaType::Int, 3,
QMetaType::Void, QMetaType::Int, QMetaType::QString, 5, 6,
// slots: parameters
QMetaType::Void,
0 // eod 代表定义结束
};
1. content为目录:它的个数是固定14个。
其定义为:
//QMetaObjectPrivate 结构体就是对 qt_meta_data_Sig_SLOT 等数组(Widget是类名)的统一封装,结构体还声明了一大堆函数,就是用于处理该数组的数据条目。qt_meta_data_Sig_SLOT 数组的目录部分就是为了描述后面不定长度的数据条目,如 信号、槽、属性条目等。
struct QMetaObjectPrivate
{
enum { OutputRevision = 7 }; // Used by moc, qmetaobjectbuilder and qdbus
int revision;
int className;
int classInfoCount, classInfoData;
int methodCount, methodData;
int propertyCount, propertyData;
int enumeratorCount, enumeratorData;
int constructorCount, constructorData; //since revision 2
int flags; //since revision 3
int signalCount; //since revision 4
// revision 5 introduces changes in normalized signatures, no new members
// revision 6 added qt_static_metacall as a member of each Q_OBJECT and inside QMetaObject itself
// revision 7 is Qt 5
//后面的一大堆函数省略
};
revision 为 7,这是 moc_.cpp 文件格式的修订版本号,之前 Qt 4.8. 用的是修订版 6,而目前 Qt 5 用的是修订版 7。
className 总是为 0,这是前面 qt_meta_data_Sig_SLOT.data[0].data() 指向的字符串。
classInfoCount 计数为 0,这是 Q_CLASSINFO附加信息对应的计数,因为我们这个综合示例没有加额外信息声明,所以是 0。
classInfoData 为 0,因为附加信息计数为零,在本数组 qt_meta_data_Sig_SLOT里面没有 classinfo 条目。
methodCount 计数为 3,元方法计数,总共有 2 个信号,1个 set 槽函数。
methodData 为 14,表示元方法的数据条目从本数组 qt_meta_data_Sig_SLOT[14] 开始。
propertyCount 计数为 0,有 0 个属性声明。
propertyData 为 0,表明没有属性数据。
enumeratorCount 计数为 0 ,因为我们没有用 Q_ENUMS(…) 声明枚举类型。
enumeratorData 为 0 ,没有枚举类型的数据条目。
constructorCount 计数为 0,因为我们没有用 Q_INVOKABLE 声明元构造函数。
constructorData 为 0,没有元构造函数的数据条目。
flags 为 0,因为我们没有用 Q_FLAGS(…) 声明标志位。
signalCount 计数为 3,元方法的数据条目以信号函数打头,信号函数需要 moc 工具生成代码,所以需要信号计数,其他元方法的函数实体是由程序员编写的,不用额外计数。
**2. 信号和槽的索引表示:signals及slots: **
// signals: name, argc, parameters, tag, flags
1, 1, 29, 2, 0x06 /* Public */,
4, 2, 32, 2, 0x06 /* Public */,
// slots: name, argc, parameters, tag, flags
7, 0, 37, 2, 0x08 /* Private */,
**3. 参数:parameters: **
// signals: parameters
QMetaType::Void, QMetaType::Int, 3,
QMetaType::Void, QMetaType::Int, QMetaType::QString, 5, 6,
// slots: parameters
QMetaType::Void,
属性
本实例没有,主要一点就行,flags由以下特性或运算得出
enum PropertyFlags {
Invalid = 0x00000000,
Readable = 0x00000001,
Writable = 0x00000002,
Resettable = 0x00000004,
EnumOrFlag = 0x00000008,
StdCppSet = 0x00000100,
// Override = 0x00000200,
Constant = 0x00000400,
Final = 0x00000800,
Designable = 0x00001000,
ResolveDesignable = 0x00002000,
Scriptable = 0x00004000,
ResolveScriptable = 0x00008000,
Stored = 0x00010000,
ResolveStored = 0x00020000,
Editable = 0x00040000,
ResolveEditable = 0x00080000,
User = 0x00100000,
ResolveUser = 0x00200000,
Notify = 0x00400000,
Revisioned = 0x00800000
};
Q_OBJECT宏内部声明了一个静态元对象:
static const QMetaObject staticMetaObject;
在moc_xxx.cpp中对此变量进行初始化,如下:
QT_INIT_METAOBJECT const QMetaObject Sig_SLOT::staticMetaObject = {
{ &QDialog::staticMetaObject, qt_meta_stringdata_Sig_SLOT.data,
qt_meta_data_Sig_SLOT, qt_static_metacall, nullptr, nullptr}
};
普通的 Qt 窗体程序都会使用这个静态元对象,它就是元对象系统的核心数据。QMetaObject 就是封装和处理元对象系统数据的核心类,它的内部有一个关键的私有数据块 d,与上面大括号里的赋值一一对应,它的声明位于
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;
};
里面 typedef 一句是声明函数指针类型,实际函数指针变量是下面一行的 static_metacall。typedef只是一个类型声明,不算struct成员变量。实际函数指针变量是下面一行的 static_metacall。
元对象系统使用的静态数据就是这些,两个全局数据块 qt_meta_stringdata_Sig_SLOT、qt_meta_data_Sig_SLOT以及类自己的静态元对象 QDialog::staticMetaObject,设置这些数据都是在做准备工作,最终都是要靠函数来运转的
Q_OBJECT 中有三个函数,在moc阶段也生成对应的实现代码在moc_Sig_SLOT.cpp中
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
private: \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
const QMetaObject *Sig_SLOT::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
对于普通的 Qt 图形界面程序,QObject::d_ptr->metaObject 总是为 NULL,只有 QML 界面程序才会使用动态元对象。所以例子中的 Sig_SLOT::metaObject() 函数不会返回动态元对象,在不使用 QML 的情况下,Widget::metaObject() 函数总是返回我们上面小节介绍的静态元对象指针 &staticMetaObject 。metaObject() 是虚函数,如果我们在程序运行时获得了一个 QObject* 指针 pObj,不知道它原本是什么派生类的,那么就可以执行:pObj->metaObject()->className();
void *Sig_SLOT::qt_metacast(const char *_clname)
{
if (!_clname) return nullptr;
if (!strcmp(_clname, qt_meta_stringdata_Sig_SLOT.stringdata0))
return static_cast(this);
return QDialog::qt_metacast(_clname);
}
qt_meta_stringdata_Widget.stringdata 里面其实有很多个以 ‘\0’ 分隔的字符串,打头的是类名,由于 strcmp 以 ‘\0’ 作为结束标志,所以 strcmp 只能看到打头的类名字符串,而看不到后面的一大堆东西,因此能直接将 _clname 与 qt_meta_stringdata_Widget.stringdata 做比较。如果 _clname 与当前类名不一样,那么就继续调用基类的 QWidget::qt_metacast(_clname),这个过程一直迭代到根上的基类 QObject::qt_metacast(_clname) 为止,如果 _clname 不在类的继承树上,那么返回值就是 NULL。
qt_metacast() 函数的作用就是能在运行时根据字符串名,将当前对象转为相应的基类对象指针,如果转换不成功就是 NULL。这是 Qt 自己的运行时类型转换,而不用要求编译器的特性。
!!!该函数非常重要!!!
int Sig_SLOT::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
_id = QDialog::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
if (_id < 3)
qt_static_metacall(this, _c, _id, _a);
_id -= 3;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 3)
*reinterpret_cast(_a[0]) = -1;
_id -= 3;
}
return _id;
}
无论是属性的 get/set,还是从信号到槽函数的调用过程等,都可能用到这个函数。
函数参数:函数头有三个参数,_c 是元调用方式,而 _id 和 _a 的意义根据用途有区别:
enum Call {
InvokeMetaMethod,
ReadProperty,
WriteProperty,
ResetProperty,
QueryPropertyDesignable,
QueryPropertyScriptable,
QueryPropertyStored,
QueryPropertyEditable,
QueryPropertyUser,
CreateInstance,
IndexOfMethod,
RegisterPropertyMetaType,
RegisterMethodArgumentMetaType
};
函数体:
_id = QDialog::qt_metacall(_c, _id, _a);
调用基类的qt_metacall函数,这一句代码同时完成了两个任务:第一,如果参数里的绝对序号 _id 是属于基类的,那么基类会调用相应的元方法或进行属性操作(递归,直到id<0为止);第二,如果参数里的绝对序号 _id 是当前类 Widget 自己声明的元方法或属性,那么绝对序号 _id 经过基类处理做减法,那么基类 QWidget::qt_metacall 返回的新 _id 就是我们当前类的元方法或属性的相对 _id ,这样就能根据当前的元方法或属性进行操作了。
if (_id < 0)
return _id;
当相对序号 _id < 0 时,说明以前的绝对序号由基类处理完了,我们这一层类就不需要干活,直接返回就可以了。_id < 0 是说明活都干完了。
接下来就是元方法调用的代码:
if (_c == QMetaObject::InvokeMetaMethod)
{
if (_id < 3)
qt_static_metacall(this, _c, _id, _a);
_id -= 3;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 3)
*reinterpret_cast(_a[0]) = -1;
_id -= 3;
}
3 为该类的3个元方法:2个信号,1个槽。
QMetaObject::InvokeMetaMethod 表明为元对象调用。id<3表明本级类的元方法,然后调用qt_static_metacall 静态函数来处理。
QMetaObject::RegisterMethodArgumentMetaType,是注册元方法参数类型
看 qt_static_metacall() 函数里面 if - else if 的结构,可以知道它是两种用途,
第一种就是上面 qt_metacall() 调用IndexOfMethod元方法函数时,会通过 qt_static_metacall() 来实现;
另一种用途是以 QMetaObject::IndexOfMethod 方式调用。用于查询信号的相对序号。
注意qt_static_metacall() 负责查询本级类的信号函数的相对序号,而 Qt 文档中另一个函数是查询元方法绝对序号的
:
int QMetaObject::indexOfMethod(const char * method) const,首字母小写,这是一个普通函数
void Sig_SLOT::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
Sig_SLOT *_t = static_cast(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->label_number_change((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: _t->number_change((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< QString(*)>(_a[2]))); break;
case 2: _t->slot_set_label_number(); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast(_a[0]);
{
typedef void (Sig_SLOT::*_t)(int );
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&Sig_SLOT::label_number_change)) {
*result = 0;
return;
}
}
{
typedef void (Sig_SLOT::*_t)(int , QString );
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&Sig_SLOT::number_change)) {
*result = 1;
return;
}
}
}
}
1. 先看QMetaObject::InvokeMetaMethod元方法的调用
void Sig_SLOT::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
Sig_SLOT *_t = static_cast(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->label_number_change((*reinterpret_cast< int(*)>(_a[1]))); break;
case 1: _t->number_change((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< QString(*)>(_a[2]))); break;
case 2: _t->slot_set_label_number(); break;
default: ;
}
}
第一个是对象指针 _o,因为静态函数没有对象的 this 指针,需要手动传递;
第二个是元调用的方式,QMetaObject::InvokeMetaMethod 是元方法调用,QMetaObject::IndexOfMethod 是元方法(信号)查询;
第三个是元方法的相对序号,调用元方法需要这个参数,而查询信号不用这个参数;
第四个是参数指针数组,调用元方法时,_a 是参数指针数组,而查询信号相对序号时,_a[0]记录相对序号数值。
qt_static_metacall(this, _c, _id, _a); 手动传递了 this 指针,_c 是调用方式,_id 是元方法的相对序号,_a 是参数指针数组。
qt_static_metacall() 先把参数里的 _o 指针(原来是 this 指针)转换为原本的 Sig_SLOT* 类型,现在对象指针叫 _t ,
然后根据相对序号_id,调用对应的槽函数及信号,参数包存在指针数组_a中,_a[0]为空,所以从a[1]开始取参数,Moc工具已经生成了应该取几个参数的代码,因此不必考虑过多。
问题:为什么此处还要调用信号?
----》因为Qt不仅支持信号槽的连接,还支持信号与信号的连接。为了支持这两种情况,因此将所有的信号槽的进行编号,在此处按照相对索引进行调用。
2. IndexOfMethod 部分代码比较简单:函数指针类型对比,如果和信号是一种类型,则返回该信号的序号。填入result。
if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast(_a[0]);
{
typedef void (Sig_SLOT::*_t)(int );
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&Sig_SLOT::label_number_change)) {
*result = 0;
return;
}
}
{
typedef void (Sig_SLOT::*_t)(int , QString );
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&Sig_SLOT::number_change)) {
*result = 1;
return;
}
}
qt_static_metacall() 里面关于元方法相对序号查询的代码,实际仅与信号函数有关,没有槽函数什么事。这与上面代码的用途有关,在新式语法的 connect 函数里,比如:
connect(ui->pushButton, &QPushButton::clicked, this, &Widget::FoodIsComing);
这句函数调用里面,怎么知道 clicked 一定是信号,而不是其他成员函数呢?所以新式语法里面必须先检查 &QPushButton::clicked 是不是信号函数指针,然后再做关联。新式语法的 connect 函数调用,源头的函数指针必须是信号函数指针,因此需要通过源头的 qt_static_metacall() 来查询源头的函数指针是不是信号函数指针,根据反馈的 _a[0] 指向的整型变量数值来确认。
新式语法对于接收端的函数指针其实没什么要求,可以是信号、槽、普通成员函数,甚至是 Lambda 函数,所以接收端函数不需要判断是不是槽。之前没说新式语法接收端的函数指针可以是普通成员函数、Lambda 函数,是不想把问题搞复杂。
总结一下之前的所学:
到此,一个元对象定义结束了,可以使用Qt的信号槽机制了,信号触发会发送active数据,接收时调用qt_static_metacall函数。
这句函数调用里面,怎么知道 clicked 一定是信号,而不是其他成员函数呢?所以新式语法里面必须先检查 &QPushButton::clicked 是不是信号函数指针,然后再做关联。新式语法的 connect 函数调用,源头的函数指针必须是信号函数指针,因此需要通过源头的 qt_static_metacall() 来查询源头的函数指针是不是信号函数指针,根据反馈的 _a[0] 指向的整型变量数值来确认。
新式语法对于接收端的函数指针其实没什么要求,可以是信号、槽、普通成员函数,甚至是 Lambda 函数,所以接收端函数不需要判断是不是槽。之前没说新式语法接收端的函数指针可以是普通成员函数、Lambda 函数,是不想把问题搞复杂。
总结一下之前的所学:
到此,一个元对象定义结束了,可以使用Qt的信号槽机制了,信号触发会发送active数据,接收时调用qt_static_metacall函数。