【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中查看。

原创粉丝点击