【QT】深入qt信号与槽实现原理

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

  • 7
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Qt信号机制是一种用于在对象之间进行通信的机制。在使用Qt开发程序时,我们可以通过信号来连接不同对象的动作和事件,以实现对象之间的交互。 根据Qt的源码和示例代码的深入剖析,我们可以了解到以下关键点: 1. 信号的基本概念:信号是一个特殊的函数,它可以被触发并发送消息。是普通的对象成员函数,可用于接收信号函数与信号函数通过连接来建立关联。 2. 信号的连接:在Qt中,我们可以使用QObject::connect()函数来建立信号的连接。这个函数接受发送信号的对象、信号函数的名称、接收信号的对象、函数的名称,以及连接类型等参数。 3. 信号的工作机制:Qt通过元对象系统(Meta-Object System)来实现信号的机制。在编译过程中,Qt的元对象编译器(Meta-Object Compiler,MOC)会解析包含信号的类,并生成元对象代码。这些元对象代码包含了信号的相关信息,以及用于信号的连接和触发的机制。 4. 信号的触发:当发送信号的对象调用信号函数时,Qt会根据连接关系找到对应的函数,并将信号的参数传递给函数进行处理。这种触发机制是基于Qt的事件循环(Event Loop)实现的。 通过以上剖析,我们可以了解到Qt信号机制的基本原理和工作流程,并能更深入地理解其内部实现。这对于使用Qt进行程序开发和解决问题是非常有帮助的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Qt信号源码剖析](https://blog.csdn.net/m0_60259116/article/details/128551391)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [Qt信号机制源码分析](https://blog.csdn.net/encourage2011/article/details/46126219)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值