【QT】深入qt信号与槽实现原理
2017-07-01 05:19
543 查看
1、先上示例代码
先上示例代码直观地感受一下qt信号与槽的用法,后面再详细解释。通过QtCreator创建一个Qt Widget工程(没有创建ui文件,其它选项为默认值),工程名为SS,最后在SS目录下会生成5个文件:main.cpp、mainwindow.cpp、mainwindow.h、SS.pro和SS.pro.user,然后对这几个文件稍作修改,最终的源码如下。SS.pro——
QT += core gui QT += widgets TARGET = SS TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp HEADERS += 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_OBJECT public: 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 members typedef 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::RefCount namespace QtPrivate { class RefCount { public: // others ... QBasicAtomicInt atomic; // typedef QBasicAtomicInteger<int> QBasicAtomicInt; }; } // 2.1 QBasicAtomicInteger template <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 QAtomicOps template <typename T> struct QAtomicOps : QBasicAtomicOps<sizeof(T)> { typedef T Type; }; // 2.3 QBasicAtomicOps template <int size> struct QBasicAtomicOps : QGenericAtomicOps<QBasicAtomicOps<size> > { // something ... }; // 2.4 QGenericAtomicOps template <typename BaseClass> struct QGenericAtomicOps { // something ... }; // 3. What is qptrdiff typedef 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时通过成员指针对信号地址进行检查 // 返回值为信号对应的_id void 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函数用于获取QMetaObject const 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表示的是信号和槽的总数为2 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; } // SIGNAL 0 // 发送信号其实调用的就是这个信号函数 // 信号函数由moc通过QMetaObject::activate实现 // 第一个参数为当前对象指针this // 第二个参数为上面介绍的staticMetaObject // 第三个参数为从0开始的信号索引 // 第四个参数为空指针NULL void 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.h Q_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 012 [深入] Qt setupUi函数的原理和实现
- QT信号与槽机制的实现原理
- QT中关于信号与槽机制的实现原理
- QT中关于信号与槽机制的实现原理
- qt 信号,槽及反射机制的实现原理 (记录 gui programing with qt 4)
- 深入理解信号阻塞实现原理
- Qt信号与槽实现原理
- 转:Spring技术内幕——深入解析Spring架构与设计原理(一)IOC实现原理
- QT源码解析(二)深入剖析QT元对象系统和信号槽机制
- 深入分析基于VCL派生的ActiveX控件的实现原理及应用
- Qt深入理解信号与槽
- 简化实现qt中的信号阻塞blockSignals
- Qt深入理解信号与槽
- 转:Spring技术内幕——深入解析Spring架构与设计原理(二)IOC实现原理
- 转:Spring技术内幕——深入解析Spring架构与设计原理(三)IOC实现原理