【QT】深入qt信号与槽实现原理
来源:互联网 发布:mac系统u盘恢复 编辑:程序博客网 时间:2024/05/18 22:13
1、先上示例代码
先上示例代码直观地感受一下qt信号与槽的用法,后面再详细解释。通过QtCreator创建一个Qt Widget工程(没有创建ui文件,其它选项为默认值),工程名为SS,最后在SS目录下会生成5个文件:main.cpp、mainwindow.cpp、mainwindow.h、SS.pro和SS.pro.user,然后对这几个文件稍作修改,最终的源码如下。
SS.pro——
QT += core guiQT += widgetsTARGET = SSTEMPLATE = appSOURCES += main.cpp\ mainwindow.cppHEADERS += mainwindow.h
SS.pro.user——
这是一个xml文件,保存了SS工程在QtCreator中的相关配置信息,不是我们关注的对象。
main.cpp——
#include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[]){ QApplication a(argc, argv); MainWindow w; w.show(); return a.exec();}
mainwindow.h——
#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>class MainWindow : public QMainWindow{ Q_OBJECTpublic: MainWindow(QWidget *parent = 0); ~MainWindow();protected: void mousePressEvent(QMouseEvent *event);//Q_SIGNALS:signals: void mousePressed();//private Q_SLOTS:private slots: void onMousePressed();};#endif // MAINWINDOW_H
mainwindow.cpp——
#include "mainwindow.h"#include <QDebug>MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent){// connect(this, &MainWindow::mousePressed, this, &MainWindow::onMousePressed); connect(this, SIGNAL(mousePressed()), this, SLOT(onMousePressed())); setGeometry(100, 100, 360, 360);}MainWindow::~MainWindow(){ disconnect(this, SIGNAL(mousePressed()), this, SLOT(onMousePressed()));}void MainWindow::mousePressEvent(QMouseEvent *event){ Q_UNUSED(event) emit mousePressed(); // Q_EMIT}void MainWindow::onMousePressed(){ qDebug() << "[SLOT] MainWindow::onMousePressed";}
接着,通过QtCreator编译SS工程,在形如build-SS-XXX-XXX的目录下产生编译结果,共6个文件:Makefile、SS、main.o、mainwindow.o、moc_mainwindow.o和moc_mainwindow.cpp,且不管这些文件是如何生成的,有什么作用。
最后,通过QtCreator运行SS工程,在弹出的窗口上按下鼠标就会输出“[SLOT] MainWindow::onMousePressed”,这个log就是通过信号与槽实现的。
2、信号与槽简介
信号(signal)与槽(slot)是qt的一大特色,由元对象系统(meta object system)提供,用于对象间的通信,类似的还有借助于函数指针的回调机制,理论上,信号与槽比回调的反应速度要慢,但前者用起来更灵活。下面以SS工程为例,简单介绍一下信号与槽的用法。SS.pro为工程文件,main.cpp文件实现了必需的main函数,这两个文件不作更多解释,重点在于mainwindow.h和mainwindow.cpp。
使用信号与槽,首先,类必须直接或间接继承自QObject,如示例中的MainWindow继承自QMainWindow,而QMainWindow间接继承自QObject;然后,在类入口处使用O_OBJECT宏,这是必须的;接着,使用signals或Q_SIGNALS声明信号,如示例中的mousePressed,信号类似于成员函数,只不过其返回类型一般为void,但可以有参数,而且只有声明不需定义,使用private、protected或public slots或Q_SLOTS声明槽并定义槽,如示例中的onMousePressed,槽就是个普通的成员函数,只不过声明时多了个slots或Q_SLOTS而已;最后使用connect连接信号与槽,信号与信号也可以连接,当信号发送时,就会触发与之连接的槽,使用disconnect断开连接,两者连接时它们的参数列表必须相同,示例中在构造函数中connect,析构函数中disconnect,重写了虚函数mousePressEvent,当有鼠标按下事件时就会调到这个函数,函数中通过emit发送mousePressed信号,进而触发与之连接的onMousePressed槽,输出log。connect和disconnect有多个重载函数,这里不作详细介绍,其中connect连接的信号与槽可以通过取地址符直接取对应的地址,或者使用SIGNAL与SLOT进行包装,但后者更好用。
3、编译过程分析(Qt5)
上面提到了编译结果,有两个文件比较奇怪,moc_mainwindow.cpp和moc_mainwindow.o。首先,通过qmake及其默认配置和SS.pro生成Makefile,然后,通过这个Makefile继续编译。接下来,使用g++编译main.cpp生成main.o,使用g++编译mainwindow.cpp生成mainwindow.o,使用元对象编译器moc编译mainwindow.h生成moc_mainwindow.cpp,使用g++编译moc_mainwindow.cpp生成moc_mainwindow.o,最后使用g++链接main.o、mainwindow.o和moc_mainwindow.cpp和生成SS,over。重点在于moc,我们来看一下由moc生成的moc_mainwindow.cpp是如何保存信号与槽相关信息的。
qt_meta_stringdata_MainWindow变量——
struct qt_meta_stringdata_MainWindow_t { QByteArrayData data[4]; char stringdata[40];};#define QT_MOC_LITERAL(idx, ofs, len) \ Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ qptrdiff(offsetof(qt_meta_stringdata_MainWindow_t, stringdata) + 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, 12), // "mousePressed"QT_MOC_LITERAL(2, 24, 0), // ""QT_MOC_LITERAL(3, 25, 14) // "onMousePressed" }, "MainWindow\0mousePressed\0\0onMousePressed"};#undef QT_MOC_LITERAL
qt_meta_stringdata_MainWindow是一个只读的静态变量,变量名中qt_meta_stringdata_为固定字段,MainWindow为对应的类名。qt_meta_stringdata_MainWindow的类型为qt_meta_stringdata_MainWindow_t,是个结构体,有两个数组成员,每个数组的长度是都是动态的。数组data有4个元素,元素排列顺序为当前类、第一个信号、占位符、其它信号、其它槽,信号在槽前面,信号和槽各自的顺序以声明的顺序排列,示例中MainWindow有1个信号和1个槽,所以加上类和占位符共4个元素,至少有1个信号或槽时后面就有1个占位符,否则只有当前类1个元素;每个元素都使用了QT_MOC_LITERAL参数宏,第一个参数表示元素索引,第二个参数表示元素在stringdata中的偏移量,第三个参数表示元素对应的字符串长度,实际上就是对QByteArrayData进行初始化,详细分析见下面的“QByteArrayData初始化”。stringdata是个字符数组,长度与data数组中的元素有关,顺序保存了data数组中各元素对应的字符串表示,即类名、信号名和槽名,占位符不占据任何长度,各个字段之间以空(null)字符分隔,示例中这个值为"MainWindow\0mousePressed\0\0onMousePressed"
。
QByteArrayData初始化——
下面深挖QByteArrayData结构及初始化方式,顺便学习下C++强大的模板用法,如下层层展开的代码所示。
// 1. QByteArrayData is QArrayData with 5 data memberstypedef QArrayData QByteArrayData;struct Q_CORE_EXPORT QArrayData{ QtPrivate::RefCount ref; // see below int size; // int uint alloc : 31; // unsigned int with 31 bits uint capacityReserved : 1; // unsignet int with 1 bit qptrdiff offset; // in bytes from beginning of header // see below // others ...};// 2. What is QtPrivate::RefCountnamespace QtPrivate{class RefCount{public: // others ... QBasicAtomicInt atomic; // typedef QBasicAtomicInteger<int> QBasicAtomicInt;};}// 2.1 QBasicAtomicIntegertemplate <typename T>class QBasicAtomicInteger{public: typedef QAtomicOps<T> Ops; typename Ops::Type _q_value; // >> ref of QArrayData stored here (type is int) << // others ...};// 2.2 QAtomicOpstemplate <typename T> struct QAtomicOps : QBasicAtomicOps<sizeof(T)>{ typedef T Type;};// 2.3 QBasicAtomicOpstemplate <int size> struct QBasicAtomicOps : QGenericAtomicOps<QBasicAtomicOps<size> >{ // something ...};// 2.4 QGenericAtomicOpstemplate <typename BaseClass> struct QGenericAtomicOps{ // something ...};// 3. What is qptrdifftypedef QIntegerForSizeof<void*>::Signed qptrdiff; // qint32(int - 32 bit signed) or qint64(long long - 64 bit signed)template <class T> struct QIntegerForSizeof: QIntegerForSize<sizeof(T)> { };template <int> struct QIntegerForSize;template <> struct QIntegerForSize<1> { typedef quint8 Unsigned; typedef qint8 Signed; };template <> struct QIntegerForSize<2> { typedef quint16 Unsigned; typedef qint16 Signed; };template <> struct QIntegerForSize<4> { typedef quint32 Unsigned; typedef qint32 Signed; };template <> struct QIntegerForSize<8> { typedef quint64 Unsigned; typedef qint64 Signed; };// 4. What is 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_MainWindow_t, stringdata) + ofs \ - idx * sizeof(QByteArrayData)) \ )// 4.1 macro definations#define Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset) \ Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset)#define Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset) \ { Q_REFCOUNT_INITIALIZE_STATIC, size, 0, 0, offset } \#define Q_REFCOUNT_INITIALIZE_STATIC { Q_BASIC_ATOMIC_INITIALIZER(-1) }# define Q_BASIC_ATOMIC_INITIALIZER(a) { (a) }// 4.2 What is offsetof// offsetof from sqlite3.c#ifndef offsetof#define offsetof(STRUCTURE,FIELD) ((int)((char*)&((STRUCTURE*)0)->FIELD))#endif// offsetof from stddef.h#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
qt_meta_data_MainWindow变量——
static const uint qt_meta_data_MainWindow[] = { // content: 7, // revision 7 is Qt 5 0, // classname 0, 0, // classinfo count and data 2, 14, // methods count and data 0, 0, // properties count and data 0, 0, // enums/sets count and data 0, 0, // constructors count and data 0, // flags since revision 3 1, // signalCount since revision 4 // signals: name, argc, parameters, tag, flags 1, 0, 24, 2, 0x06 /* Public */, // slots: name, argc, parameters, tag, flags 3, 0, 25, 2, 0x08 /* Private */, // signals: parameters QMetaType::Void, // slots: parameters QMetaType::Void, 0 // eod};
qt_meta_data_MainWindow变量中数据类型为unit,有几个关键的地方,content中methods为2表示共有2个信号和槽,signalCount为1表示共有1个信号,接着是信号和槽的相关信息,最后一个元素为0标记结束。
4、有用的宏
示例中的宏Q_OBJECT、signals、Q_SIGNALS、slots、Q_SLOTS、emit等是非常有用的,在头文件qobjectdefs.h中定义,根据是否为moc编译而分为两个版本,源码如下。
// The following macros are our "extensions" to C++// They are used, strictly speaking, only by the moc.#ifndef Q_MOC_RUN#ifndef QT_NO_META_MACROS# if defined(QT_NO_KEYWORDS)# define QT_NO_EMIT# else# ifndef QT_NO_SIGNALS_SLOTS_KEYWORDS# define slots# define signals public# endif# endif# define Q_SLOTS# define Q_SIGNALS public# define Q_EMIT#ifndef QT_NO_EMIT# define emit#endif// others ...#endif // QT_NO_META_MACROS/* qmake ignore Q_OBJECT */#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 {};#else // Q_MOC_RUN#define slots slots#define signals signals#define Q_SLOTS Q_SLOTS#define Q_SIGNALS Q_SIGNALS#define Q_OBJECT Q_OBJECT#endif //Q_MOC_RUN
关键在于上面的Q_OBJECT,其中声明的函数由moc编译时实现,另外还实现了信号,前面提到了信号只声明不定义,其实信号也是函数,只不过由moc实现,示例中的moc_mainwindow.cpp相关源码及分析如下。
// qt_static_metacall函数从其名字来看是一个调用函数的方法// 参数_c值为InvokeMetaMethod时说明将调用函数// 然后根据参数_id值去调用对应的信号或槽// 参数_c值为IndexOfMethod时通过成员指针对信号地址进行检查// 返回值为信号对应的_idvoid MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a){ if (_c == QMetaObject::InvokeMetaMethod) { MainWindow *_t = static_cast<MainWindow *>(_o); switch (_id) { case 0: _t->mousePressed(); break; case 1: _t->onMousePressed(); break; default: ; } } else if (_c == QMetaObject::IndexOfMethod) { int *result = reinterpret_cast<int *>(_a[0]); void **func = reinterpret_cast<void **>(_a[1]); { typedef void (MainWindow::*_t)(); if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&MainWindow::mousePressed)) { *result = 0; } } } Q_UNUSED(_a);}// staticMetaObject变量保存了所有的元数据const QMetaObject MainWindow::staticMetaObject = { { &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow.data, qt_meta_data_MainWindow, qt_static_metacall, Q_NULLPTR, Q_NULLPTR}};// metaObject函数用于获取QMetaObjectconst QMetaObject *MainWindow::metaObject() const{ return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;}// qt_metacast函数用于提取参数_clname对应类的信号与槽的名字// 因为qt_meta_stringdata_MainWindow.stringdata的第一个数据段保存的是类名// 所以可以通过strcmp进行类名比较void *MainWindow::qt_metacast(const char *_clname){ if (!_clname) return Q_NULLPTR; if (!strcmp(_clname, qt_meta_stringdata_MainWindow.stringdata)) return static_cast<void*>(const_cast< MainWindow*>(this)); return QMainWindow::qt_metacast(_clname);}// qt_metacall函数根据参数_id及_c执行不同的动作// 当_id<2且-c==InvokeMetaMethod时// 执行前面介绍的qt_static_metacall// 这里的数字2表示的是信号和槽的总数为2int 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;}// SIGNAL 0// 发送信号其实调用的就是这个信号函数// 信号函数由moc通过QMetaObject::activate实现// 第一个参数为当前对象指针this// 第二个参数为上面介绍的staticMetaObject// 第三个参数为从0开始的信号索引// 第四个参数为空指针NULLvoid MainWindow::mousePressed(){ QMetaObject::activate(this, &staticMetaObject, 0, Q_NULLPTR);}
示例中的connect函数用到了SIGNAL与SLOT宏,它们分debug和非debug两个版本,非debug版本就是在参数前面添加一个数字,信号为2,槽为1,源码如下。
// qglobal.h/* These two macros makes it possible to turn the builtin line expander into a * string literal. */#define QT_STRINGIFY2(x) #x#define QT_STRINGIFY(x) QT_STRINGIFY2(x)// qobjectdefs.hQ_CORE_EXPORT const char *qFlagLocation(const char *method);#ifndef QT_NO_META_MACROS#ifndef QT_NO_DEBUG# define QLOCATION "\0" __FILE__ ":" QT_STRINGIFY(__LINE__)# ifndef QT_NO_KEYWORDS# define METHOD(a) qFlagLocation("0"#a QLOCATION)# endif# define SLOT(a) qFlagLocation("1"#a QLOCATION)# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)#else# ifndef QT_NO_KEYWORDS# define METHOD(a) "0"#a# endif# define SLOT(a) "1"#a# define SIGNAL(a) "2"#a#endif#define QMETHOD_CODE 0 // member type codes#define QSLOT_CODE 1#define QSIGNAL_CODE 2#endif // QT_NO_META_MACROS
5、connect
使用信号前,首先要进行connect,示例中的connect代码如下。
connect(this, SIGNAL(mousePressed()), this, SLOT(onMousePressed()));
connect有多个重载函数,下面以示例中的用法为例展开说明,源码如下。
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type){ // 先判断函数参数是否有效 if (sender == 0 || receiver == 0 || signal == 0 || method == 0) { qWarning("QObject::connect: Cannot connect %s::%s to %s::%s", sender ? sender->metaObject()->className() : "(null)", (signal && *signal) ? signal+1 : "(null)", receiver ? receiver->metaObject()->className() : "(null)", (method && *method) ? method+1 : "(null)"); return QMetaObject::Connection(0); } QByteArray tmp_signal_name; // 检查信号对应的宏SIGNAL是否正确使用 // SIGNAL在信号前面添加了数字2 // check_signal_macro通过这个数字2进行检查 // 是否正确使用了SIGNAL if (!check_signal_macro(sender, signal, "connect", "bind")) return QMetaObject::Connection(0); const QMetaObject *smeta = sender->metaObject(); const char *signal_arg = signal; ++signal; // 跳过SIGNAL宏中的数字2 QArgumentTypeArray signalTypes; Q_ASSERT(QMetaObjectPrivate::get(smeta)->revision >= 7); // moc设置了revision为7 // 提取信号名signalName和参数列表signalTypes // decodeMethodSignature函数使用了strchr函数定位左、右括号在signal字符串中的位置 QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes); // 提取信号索引signal_index // 从当前类到父类查找signalName对应的索引 // 失败时返回-1 int signal_index = QMetaObjectPrivate::indexOfSignalRelative( &smeta, signalName, signalTypes.size(), signalTypes.constData()); if (signal_index < 0) { // check for normalized signatures tmp_signal_name = QMetaObject::normalizedSignature(signal - 1); signal = tmp_signal_name.constData() + 1; signalTypes.clear(); signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes); smeta = sender->metaObject(); signal_index = QMetaObjectPrivate::indexOfSignalRelative( &smeta, signalName, signalTypes.size(), signalTypes.constData()); } if (signal_index < 0) { err_method_notfound(sender, signal_arg, "connect"); err_info_about_objects("connect", sender, receiver); return QMetaObject::Connection(0); } signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index); signal_index += QMetaObjectPrivate::signalOffset(smeta); // 同理下面获取槽的名字和索引 // 因为信号可以连接到槽和另外一个信号 // 所以对槽进行处理时还要判断是否为信号 QByteArray tmp_method_name; int membcode = extract_code(method); if (!check_method_code(membcode, receiver, method, "connect")) return QMetaObject::Connection(0); const char *method_arg = method; ++method; // skip code QByteArray methodName; QArgumentTypeArray methodTypes; const QMetaObject *rmeta = receiver->metaObject(); int method_index_relative = -1; Q_ASSERT(QMetaObjectPrivate::get(rmeta)->revision >= 7); switch (membcode) { case QSLOT_CODE: method_index_relative = QMetaObjectPrivate::indexOfSlotRelative( &rmeta, methodName, methodTypes.size(), methodTypes.constData()); break; case QSIGNAL_CODE: method_index_relative = QMetaObjectPrivate::indexOfSignalRelative( &rmeta, methodName, methodTypes.size(), methodTypes.constData()); break; } if (method_index_relative < 0) { // check for normalized methods tmp_method_name = QMetaObject::normalizedSignature(method); method = tmp_method_name.constData(); methodTypes.clear(); methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes); // rmeta may have been modified above rmeta = receiver->metaObject(); switch (membcode) { case QSLOT_CODE: method_index_relative = QMetaObjectPrivate::indexOfSlotRelative( &rmeta, methodName, methodTypes.size(), methodTypes.constData()); break; case QSIGNAL_CODE: method_index_relative = QMetaObjectPrivate::indexOfSignalRelative( &rmeta, methodName, methodTypes.size(), methodTypes.constData()); break; } } if (method_index_relative < 0) { err_method_notfound(receiver, method_arg, "connect"); err_info_about_objects("connect", sender, receiver); return QMetaObject::Connection(0); } // 检查信号与槽的参数列表是否一致 if (!QMetaObjectPrivate::checkConnectArgs(signalTypes.size(), signalTypes.constData(), methodTypes.size(), methodTypes.constData())) { qWarning("QObject::connect: Incompatible sender/receiver arguments" "\n %s::%s --> %s::%s", sender->metaObject()->className(), signal, receiver->metaObject()->className(), method); return QMetaObject::Connection(0); } // 对connect的类型进行处理 int *types = 0; if ((type == Qt::QueuedConnection) && !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) { return QMetaObject::Connection(0); } // 最后通过QMetaObjectPrivate::connect进行真正的connect#ifndef QT_NO_DEBUG QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index); QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset()); check_and_warn_compat(smeta, smethod, rmeta, rmethod);#endif QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect( sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types)); return handle;}
下面是QMetaObjectPrivate::connect的源码实现。
QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender, int signal_index, const QMetaObject *smeta, const QObject *receiver, int method_index, const QMetaObject *rmeta, int type, int *types){ // sender和receiver去const QObject *s = const_cast<QObject *>(sender); QObject *r = const_cast<QObject *>(receiver); // 获取receiver中method的偏移量 // 因为其method_index是个相对值 int method_offset = rmeta ? rmeta->methodOffset() : 0; Q_ASSERT(!rmeta || QMetaObjectPrivate::get(rmeta)->revision >= 6); QObjectPrivate::StaticMetaCallFunction callFunction = rmeta ? rmeta->d.static_metacall : 0; // 对sender和receiver上锁(mutex pool) QOrderedMutexLocker locker(signalSlotLock(sender), signalSlotLock(receiver)); // type为Qt::UniqueConnection时作特殊处理 // 确保connect的唯一性 if (type & Qt::UniqueConnection) { QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists; if (connectionLists && connectionLists->count() > signal_index) { const QObjectPrivate::Connection *c2 = (*connectionLists)[signal_index].first; int method_index_absolute = method_index + method_offset; while (c2) { if (!c2->isSlotObject && c2->receiver == receiver && c2->method() == method_index_absolute) return 0; c2 = c2->nextConnectionList; } } type &= Qt::UniqueConnection - 1; } // 最后是真正的connect对象QObjectPrivate::Connection实例化 // 存储了所有的connect信息 // addConnection最终保存了这个connect操作 QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection); c->sender = s; c->signal_index = signal_index; c->receiver = r; c->method_relative = method_index; c->method_offset = method_offset; c->connectionType = type; c->isSlotObject = false; c->argumentTypes.store(types); c->nextConnectionList = 0; c->callFunction = callFunction; QObjectPrivate::get(s)->addConnection(signal_index, c.data()); // 解锁 locker.unlock(); // connect成功后还会调用一次connectNotify函数 // connectNotify是个虚函数 // 我们可以重写connectNotify在connenct成功后进行额外的相关操作 QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index); if (smethod.isValid()) s->connectNotify(smethod); return c.take();}
6、activate
发送信号时,实际上是调用了QMetaObject::activate函数,这是Qt用于内部实现的函数,开发者无法直接使用这个函数。
// internal index-based signal activation static void activate(QObject *sender, int signal_index, void **argv); static void activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv); static void activate(QObject *sender, int signal_offset, int local_signal_index, void **argv);
activate最终是通过上面的最后一个函数实现的,参数分别为信号发送者对象指针、信号在元对象数据结构中的偏移量及信号索引、信号参数,可以想象,这个函数就是在前面添加的connect列表中查找并调用这个信号连接的槽或者信号,源码实现如下。
void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv){ // 信号在元对象数据结构中的实际索引 int signal_index = signalOffset + local_signal_index; // 判断信号是否已经connect // 判断是否注册了信号监听回调函数(用于QTest) if (!sender->d_func()->isSignalConnected(signal_index) && !qt_signal_spy_callback_set.signal_begin_callback && !qt_signal_spy_callback_set.signal_end_callback) { return; // nothing connected to these signals, and no spy } // 判断信号是否被block if (sender->d_func()->blockSig) return; // 用于QTest if (sender->d_func()->declarativeData && QAbstractDeclarativeData::signalEmitted) QAbstractDeclarativeData::signalEmitted(sender->d_func()->declarativeData, sender, signal_index, argv); // 用于QTest begin void *empty_argv[] = { 0 }; if (qt_signal_spy_callback_set.signal_begin_callback != 0) { qt_signal_spy_callback_set.signal_begin_callback(sender, signal_index, argv ? argv : empty_argv); } // HANDLE句柄即当前的线程id // unix平台上通过pthread_self获取 Qt::HANDLE currentThreadId = QThread::currentThreadId(); { // 上锁(多线程、异步) QMutexLocker locker(signalSlotLock(sender)); struct ConnectionListsRef { QObjectConnectionListVector *connectionLists; ConnectionListsRef(QObjectConnectionListVector *connectionLists) : connectionLists(connectionLists) { if (connectionLists) ++connectionLists->inUse; } ~ConnectionListsRef() { if (!connectionLists) return; --connectionLists->inUse; Q_ASSERT(connectionLists->inUse >= 0); if (connectionLists->orphaned) { if (!connectionLists->inUse) delete connectionLists; } } QObjectConnectionListVector *operator->() const { return connectionLists; } }; ConnectionListsRef connectionLists = sender->d_func()->connectionLists; // connectionLists为空时unlock后直接return if (!connectionLists.connectionLists) { locker.unlock(); // 用于QTest end if (qt_signal_spy_callback_set.signal_end_callback != 0) qt_signal_spy_callback_set.signal_end_callback(sender, signal_index); return; } // 获取connect列表 const QObjectPrivate::ConnectionList *list; if (signal_index < connectionLists->count()) list = &connectionLists->at(signal_index); else list = &connectionLists->allsignals; do { QObjectPrivate::Connection *c = list->first; // 循环取得一个非空的Connection if (!c) continue; // We need to check against last here to ensure that signals added // during the signal emission are not emitted in this emission. QObjectPrivate::Connection *last = list->last; do { // 查找有效的receiver if (!c->receiver) continue; QObject * const receiver = c->receiver; // 判断当前线程与receiver线程是否一致 const bool receiverInSameThread = currentThreadId == receiver->d_func()->threadData->threadId; // 根据connect类型及receiverInSameThread进行不同的处理 // 立即执行queued_activate或者放入消息队列postEvent等待后续处理 if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread) || (c->connectionType == Qt::QueuedConnection)) { queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker); continue;#ifndef QT_NO_THREAD } else if (c->connectionType == Qt::BlockingQueuedConnection) { locker.unlock(); if (receiverInSameThread) { qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: " "Sender is %s(%p), receiver is %s(%p)", sender->metaObject()->className(), sender, receiver->metaObject()->className(), receiver); } // 多线程时势必要用到同步机制(锁、信号量) QSemaphore semaphore; QMetaCallEvent *ev = c->isSlotObject ? new QMetaCallEvent(c->slotObj, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore) : new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore); QCoreApplication::postEvent(receiver, ev); semaphore.acquire(); locker.relock(); continue;#endif } QConnectionSenderSwitcher sw; if (receiverInSameThread) { sw.switchSender(receiver, sender, signal_index); } // 下面通过三种方法去调用信号连接的槽 const QObjectPrivate::StaticMetaCallFunction callFunction = c->callFunction; const int method_relative = c->method_relative; if (c->isSlotObject) { c->slotObj->ref(); QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj); locker.unlock(); // 方法一 通过call调用receiver中的函数 obj->call(receiver, argv ? argv : empty_argv); // Make sure the slot object gets destroyed before the mutex is locked again, as the // destructor of the slot object might also lock a mutex from the signalSlotLock() mutex pool, // and that would deadlock if the pool happens to return the same mutex. obj.reset(); locker.relock(); } else if (callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) { //we compare the vtable to make sure we are not in the destructor of the object. locker.unlock(); const int methodIndex = c->method(); if (qt_signal_spy_callback_set.slot_begin_callback != 0) qt_signal_spy_callback_set.slot_begin_callback(receiver, methodIndex, argv ? argv : empty_argv); // 方法二 callFunction即moc实现的qt_static_metacall callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv); if (qt_signal_spy_callback_set.slot_end_callback != 0) qt_signal_spy_callback_set.slot_end_callback(receiver, methodIndex); locker.relock(); } else { const int method = method_relative + c->method_offset; locker.unlock(); if (qt_signal_spy_callback_set.slot_begin_callback != 0) { qt_signal_spy_callback_set.slot_begin_callback(receiver, method, argv ? argv : empty_argv); } // 方法三 通过metacall调用moc实现的qt_matacall metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv); if (qt_signal_spy_callback_set.slot_end_callback != 0) qt_signal_spy_callback_set.slot_end_callback(receiver, method); locker.relock(); } // orphaned为true时说明connectionLists的所属QObject已经销毁 // 尽管connectionLists是inUse但没有什么意思 // 所以跳出循环 if (connectionLists->orphaned) break; } while (c != last && (c = c->nextConnectionList) != 0); if (connectionLists->orphaned) break; } while (list != &connectionLists->allsignals && //start over for all signals; ((list = &connectionLists->allsignals), true)); } // 用于QTest(end) if (qt_signal_spy_callback_set.signal_end_callback != 0) qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);}
7、over
简单来说,信号与槽的关键就是Qt的元对象系统,通过moc编译隐藏了具体的实现细节,这些内容可以在moc_xxx.cpp中查看。
- 【QT】深入qt信号与槽实现原理
- Qt信号与槽实现原理
- Qt信号与槽实现原理
- QT中关于信号与槽机制的实现原理
- QT中关于信号与槽机制的实现原理
- QT中关于信号与槽机制的实现原理
- QT中关于信号与槽机制的实现原理
- QT中关于信号与槽机制的实现原理
- QT中关于信号与槽机制的实现原理
- QT信号与槽机制的实现原理
- Qt深入理解信号与槽
- QT的信号与槽原理(经典)
- QT的信号与槽原理(经典)
- QT的信号与槽原理
- QT的信号与槽原理(经典)
- Qt信号与槽机制原理
- 深入信号和槽---QT
- qt 槽与信号
- ubuntu修改IP地址和网关的方法
- 利用VS获取程序图标
- ubuntu 命令行模式和图形界面切换
- Ubuntu 开机进入命令行模式
- 加农算法的MPI实现 【矩阵相乘】
- 【QT】深入qt信号与槽实现原理
- 高斯列主元消元法mpi实现
- Turan图
- 你的JWTs存储在哪里
- Unbuntu安装Docker
- HDFS工作机制
- 禁止文本框自动完成
- jvm调优
- 选定文本内容