QT元对象系统和信号槽
初识QObject和QMetaObject
QObject是qt的一个核心基类
QMetaObject为一个元数据对象 MOC编译器可以为每个QObject派生类类基于Q_OBJECT宏生成代码
主要是定义对应的QMetaObject类对象和QObject子类的方法重写。
QMetaObject定义了QObject子类的元数据:类名称,信号信息(字符串,个数)和槽函数的执行函数体等等
MOC为某个QObject子类生成的代码中重写的const QMetaObject *MainWindow::metaObject() 方法就是返回相应定义的QMetaObject
例如创建一个最简单的应用程序
里面有一个继承自QMainwindow的mainwindow类,为其ui添加一个pushbutton,并增加btn的click槽函数。并且增加了一个名为sigTest的信号。
signals:
void sigTest();
private slots:
void on_pushButton_clicked();
经过moc编译的编译以后,我们可以看到moc_mainwindow.cpp中的代码
Moc编译器对QMetaObject和QObject的影响
生成了类型的字符串信息内容
static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {
{
QT_MOC_LITERAL(0, 0, 10), // "MainWindow"
QT_MOC_LITERAL(1, 11, 7), // "sigTest" moc编译器根据我们所添加的信号和槽函数添加的信息
QT_MOC_LITERAL(2, 19, 0), // ""
QT_MOC_LITERAL(3, 20, 21) // "on_pushButton_clicked"
},
"MainWindow\0sigTest\0\0on_pushButton_clicked"//所有的信号和槽函数的名称都添加到元对象的信息 MainWindow作为 const char * QMetaObject ::className () const 所返回的数据
};
struct qt_meta_stringdata_Widget_t {
QByteArrayData data[9];
char stringdata0[68];
};
- 每个QByteArray对应一个下面\0结尾的字符串,以此方便对字符串数据的管理
当我们不停的增加信号的时候,就会向这部分的字符串数据中添加新的信号量和槽函数的描述。
生成元对象类的详细信息描述
static const uint qt_meta_data_MainWindow[] = {
// content:
7, // revision
0, // classname
0, 0, // classinfo
2, 14, // methods 这个元对象所包含的信号和槽函数的方法范围其中包含一个signal和一个slot
0, 0, // properties
0, 0, // enums/sets
0, 0, // constructors
0, // flags
1, // signalCount
// signals: name, argc, parameters, tag, flags
1, 0, 24, 2, 0x06 /* Public */,//第一值是QByteArrayData中的索引值,用来找对应的字符串名称,第二个是参数个数,第三个是参数大小
// slots: name, argc, parameters, tag, flags
3, 0, 25, 2, 0x08 /* Private */,//这里出现的1,3这些序号在提交信号处理的过程中查找槽函数时是会用到的
// signals: parameters
QMetaType::Void,
// slots: parameters
QMetaType::Void,
0 // eod
};
初始化静态的元对象
QT_INIT_METAOBJECT const QMetaObject MainWindow::staticMetaObject = {
{ &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow.data,
qt_meta_data_MainWindow, qt_static_metacall, nullptr, nullptr}
};
重写const QMetaObject *QObject::metaObject() const方法
const QMetaObject *MainWindow::metaObject() const //返回上述初始化了的静态元对象的指针
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
允许指定的元对象类型名称转换成对应的QObject对象指针
void *MainWindow::qt_metacast(const char *_clname)
{
if (!_clname) return nullptr;
if (!strcmp(_clname, qt_meta_stringdata_MainWindow.stringdata0))
return static_cast<void*>(this);//如果与字符串数据中的第一个数据名称一致,则返回Mainwindow的对象指针
return QMainWindow::qt_metacast(_clname);
}
重写 int QOject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)函数
当信号被触发的时候,走到元对象系统的静态方法int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)时,会调用Object的qt_metacall方法,而这个方法时个纯虚函数,moc编译器会帮我们生成mainwindow类的metacall的函数实现
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 < 2)
qt_static_metacall(this, _c, _id, _a);
_id -= 2;
} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
if (_id < 2)
*reinterpret_cast<int*>(_a[0]) = -1;
_id -= 2;
}
return _id;
}
qt_static_metacall的具体实现也由moc编译器自动生成
void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
MainWindow *_t = static_cast<MainWindow *>(_o);
Q_UNUSED(_t)
switch (_id) {
case 0: _t->sigTest(); break;
case 1: _t->on_pushButton_clicked(); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
{
typedef void (MainWindow::*_t)();
if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::sigTest)) {
*result = 0;
return;
}
}
}
Q_UNUSED(_a);
}
为所有的信号添加函数实现
实现体的内容都是调用元对象的active方法,并传递参数。如果还有其他的信号,则再增加一个函数定义
// SIGNAL 1
void MainWindow::sigTestEx()
{
QMetaObject::activate(this, &staticMetaObject, 1, nullptr);//静态方法
}
Connect函数让QMetaObject可以管理调度Object的信号槽
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
会为每个sender对象添加一个QObjectConnectionListVector类型的列表connectionLists来记录连接到这个sendor上的所有的信号(signalindex)对应的接受者以及他们的槽函数的信息(slotindex)
当QMetaObject的active被触发的时候,会遍历sender对象的connectionLists 。通过几种不同的途径调用到QObject子类的槽函数中
执行receiver的元对象的static_metacall函数指针
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;
在moc生产的元对象代码中,我们初始化静态元对象数据时指定了第四个成员变量为qt_static_metacall
通过QMetaObject::metacall 调用某个对象的int qt_metacall(QMetaObject::Call _c, int _id, void **_a)
而这个方法在上述moc过程中被重写了,使其可以调用到qt_static_metacall
总结
可见元对象编译器主要做两件事
为对象生成数据信息(类名称,信号和槽名称,参数类型关联)
给信号函数和槽函数生成调用实现的代码,再将代码交由QMetaObject进行管理调度,所有信号和槽函数的关联在对象内部是不可见的。