Qt信号与槽机制源码分析

在前一篇中,我翻译了一篇关于信号与槽机制详解的文章Qt信号与槽工作机制–译文。在这一篇文章中,我将根据自己的理解从Qt源码中分析该机制。建议在看本文章之前先去看看前面提到的译文或者直接看原文原文How Qt Signals and Slots Work。本文只分析了大概的思路,很多细节并没有深究下去,如有错误,敬请大家指正。
以下所有代码的测试基于Qt5.4.0,MOC版本是67。


测试代码

同样,我们还是使用官方的例程进行讲解。
Counter.h

#ifndef COUNTER_H
#define COUNTER_H
#include <QObject>
#include <QDebug>

class Counter : public QObject
{
    Q_OBJECT
    int m_value;

public:
    int value() const {return m_value;}

public slots:
    void setValue(int value);

signals:
    void valueChanged(int newValue);

public:
    Counter();
    ~Counter();
};

#endif // COUNTER_H

Counter.c

#include "counter.h"

Counter::Counter()
{
    m_value = 0;
}

void Counter::setValue(int value)
{
    if(value != m_value)
    {
        m_value = value;
        emit valueChanged(value);
    }
}

Counter::~Counter()
{

}

main.c

#include "counter.h"

int main()
{
    Counter a, b;
    QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
    qDebug() << "First :b.value() is " << b.value();
    a.setValue(12);
    qDebug() << "Second:b.value() is " << b.value();
    return 0;
}

我们在Debug目录下找到MOC根据Counter.h产生的C++文件:moc_counter.cpp

/****************************************************************************
** Meta object code from reading C++ file 'counter.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.4.0)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/

#include "../../Test/counter.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'counter.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.4.0. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif

QT_BEGIN_MOC_NAMESPACE
struct qt_meta_stringdata_Counter_t {
    QByteArrayData data[6];
    char stringdata[46];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_Counter_t, stringdata) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_Counter_t qt_meta_stringdata_Counter = {
    {
QT_MOC_LITERAL(0, 0, 7), // "Counter"
QT_MOC_LITERAL(1, 8, 12), // "valueChanged"
QT_MOC_LITERAL(2, 21, 0), // ""
QT_MOC_LITERAL(3, 22, 8), // "newValue"
QT_MOC_LITERAL(4, 31, 8), // "setValue"
QT_MOC_LITERAL(5, 40, 5) // "value"

    },
    "Counter\0valueChanged\0\0newValue\0setValue\0"
    "value"
};
#undef QT_MOC_LITERAL

static const uint qt_meta_data_Counter[] = {

 // content:
       7,       // revision
       0,       // classname
       0,    0, // classinfo
       2,   14, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       0,       // flags
       1,       // signalCount

 // signals: name, argc, parameters, tag, flags
       1,    1,   24,    2, 0x06 /* Public */,

 // slots: name, argc, parameters, tag, flags
       4,    1,   27,    2, 0x0a /* Public */,

 // signals: parameters
    QMetaType::Void, QMetaType::Int,    3,

 // slots: parameters
    QMetaType::Void, QMetaType::Int,    5,

       0        // eod
};

void Counter::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        Counter *_t = static_cast<Counter *>(_o);
        switch (_id) {
        case 0: _t->valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 1: _t->setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        void **func = reinterpret_cast<void **>(_a[1]);
        {
            typedef void (Counter::*_t)(int );
            if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Counter::valueChanged)) {
                *result = 0;
            }
        }
    }
}

const QMetaObject Counter::staticMetaObject = {
    { &QObject::staticMetaObject, qt_meta_stringdata_Counter.data,
      qt_meta_data_Counter,  qt_static_metacall, Q_NULLPTR, Q_NULLPTR}
};


const QMetaObject *Counter::metaObject() const
{
    return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}

void *Counter::qt_metacast(const char *_clname)
{
    if (!_clname) return Q_NULLPTR;
    if (!strcmp(_clname, qt_meta_stringdata_Counter.stringdata))
        return static_cast<void*>(const_cast< Counter*>(this));
    return QObject::qt_metacast(_clname);
}

int Counter::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
    _id = QObject::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
void Counter::valueChanged(int _t1)
{
    void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
QT_END_MOC_NAMESPACE

上述的运行结果如下:
运行结果


main()函数

在main()中,我们首先创建两个Counter类型的对象a和b:Counter a, b;
之后我们将对象a中信号valueChanged(int)与对象b中的槽函数setValue(int)绑定起来:QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
最后调用a对象的槽函数:a.setValue(12);,调用完该函数之后,对象b中的m_value的值由原来的0变为12。很明显,我们的信号与槽连接成功。


setValue()函数

void Counter::setValue(int value)
{
    if(value != m_value)
    {
        m_value = value;
        emit valueChanged(value);
    }
}

在该函数中,首先判断传入的参数是不是与Counter类的属性变量m_value是否不等,之所以做这一步,是因为如果两者相等还是emit信号,那么这个信号与槽的连接将进入死循环,永远无法结束。最后emit一个信号valueChanged(value)。在qobject.h中定义了emit这个宏:
qobjectdefs.h

# define emit // nothing

也就是说,在setValue()函数中只是纯粹的调用valueChanged()函数。


信号函数:valueChanged()

我们只在Counter.h声明了一个信号函数:

signals:
    void valueChanged(int newValue);

但是我们并没有去实现它,那么它是怎么实现的呢?这时候就能体现出MOC的强大之处了。我们前面有提到过:MOC通过Counter.h函数生成代码保存在moc_counter.cpp的文件中。我们在该C++文件中找到了信号函数valueChanged()的实现代码:

// SIGNAL 0
void Counter::valueChanged(int _t1)
{
    void *_a[] = { Q_NULLPTR, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

reinterpret_cast运算符是用来处理无关类型之间的转换,它会产生一个新的值,这个值会与原始参数有完全相同的比特位。reinterpret_cast<const void*>(&_t1)的意思是将指向整型_t1的指针转换为const void*。
const_cast是用来溢出变量的const或volatile限定符。const_cast<void*>(reinterpret_cast<const void*>(&_t1))按我的理解是将指向整型_t1的指针转换为void*。我们只要记住,在这里_a[1]的值是一个指向信号函数参数的指针。
之后就调用QMetaObject::activate()并传入参数。


activate()参数:staticMetaObject

还是在moc_counter.cpp文件中,我们找到了staticMetaObject对象的初始化数据,它是QMetaObject类型。staticMetaObject包含了Counter类中一些非常重要的信息,需要多加注意。

const QMetaObject Counter::staticMetaObject = {
    { &QObject::staticMetaObject, qt_meta_stringdata_Counter.data,
      qt_meta_data_Counter,  qt_static_metacall, Q_NULLPTR, Q_NULLPTR}
};
const QMetaObject *Counter::metaObject() const
{
    return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}

我们在qobjectdefs.h中找到了QMetaObject的构造函数

struct { // private data
        const QMetaObject *superdata;
        const QByteArrayData *stringdata;
        const uint *data;
        typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
        StaticMetacallFunction static_metacall;
        const QMetaObject * const *relatedMetaObjects;
        void *extradata; //reserved for future use
    } d;

因此我们可以得到如下的等式关系:
1、superdata = &QObject::staticMetaObject;
2、stringdata = qt_meta_stringdata_Counter.data;
3、data = qt_meta_data_Counter;
4、static_metacall = qt_static_metacall;
5、relatedMetaObjects = Q_NULLPTR;
6、extradata = Q_NULLPTR;
其中2、3、4点的信息我们将会在后面用到。


QMetaObject::activate()函数

接下来我们来看信号函数valueChanged()中最后调用的QMetaObject::activate(this, &staticMetaObject, 0, _a);的函数原型。
QMetaObject::activate()定义在qobject.cpp文件中。

/*!
    \internal
 */
void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
                           void **argv)
{
    activate(sender, QMetaObjectPrivate::signalOffset(m), local_signal_index, argv);
}

/*!
    \internal
 */
void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
{
    // 详细解释请见文章开头提到的文章
}

activate()函数接收4个参数:
第1个参数表示发送信号的对象,这里当然是指对象a了;
第2个参数是指向QMetaObject类型的指针,这里我们传进去的是&staticMetaObject,该结构体中包含了很多重要的信息,我们在前面已经讲过了。
第3个参数是在这个类中该信号的索引值。因为我们在Counter类中只定义了一个信号ValueChanged(),所以该信号的索引值为0。那么如果我们定义多个信号,信号的索引值与信号定义出现的顺序呈一一对应关系。
第4个参数是接收一个指向指针的指针,这里传递的是指向数组的指针_a,我们前面也说了_a[1]的值是指向传递给信号函数valueChanged()的参数newValue,很明显,传递给newValue的值是12。
QMetaObject::activate()的重载函数:void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)与前一个版本只差别在第2个参数,这里第2个参数传递的是信号的偏移量QMetaObjectPrivate::signalOffset(m)而不是指向QMetaObject对象的指针m。我一直没有找到QMetaObjectPrivate::signalOffset()的相关定义,在此根据其函数名理解该函数的作用是:从类中获取信号的偏移量。也就是在qt_meta_data_Counter数组中找到信号的偏移量。无论是调用哪个版本的activate()函数,都要告知其(传递参数给它)以下信息:
1、发送的对象sender;
2、该信号在所有信号中的偏移量signalOffset;
每一个对象中的每一个信号都维护了一个连接的双向列表connectionList(后面会提到),该链表中都有与该信号相互连接的信息,比如说接收对象,接收的槽函数,参数等等信息。只要找到该信号的偏移量,就可以找到其维护的双向列表,也就可以找到槽函数了。
3、该信号在该类中的索引值local_signal_index;
4、该信号携带的参数_a[1]。
我们这里讲了emit一个信号的过程,但是只提到了信号与槽机制的一点点皮毛。为什么emit一个信号后会有一个槽函数响应呢?信号与槽是怎样绑定的?都绑定哪些信息呢?


QObject::connect()

在main()函数中,我们调用了QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));将信号与槽绑定起来。在分析connect函数之前,我们先来看看宏SIGNAL()SLOT()都是什么鬼。

宏SIGNAL和SLOT

我们在qobjectdefs.h中找到了那两个宏的定义:

#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

这两个宏无非是将信号与槽函数的名称(函数名以及参数)转换为字符串,仅此而已。在方法名称前面给个标识0,在槽函数前面给个标识1,在信号函数前面给个标识2,这3个标识为后面解析参数是所属哪种类型的起到很大的作用。我们在这边是不是可以推测这就是很多编程语言的函数名不能以数字开头的原因之一吗?还有我们注意到,这里的宏是将信号与槽的名称转换为字符串,这里的名称包括了参数。于是我们可以得出:信号与槽的参数不能包括宏。如果包括宏,是要等里面的宏先展开然后SLOT和SIGNAL再展开还是先SLOT和SIGNAL展开呢?这都是没有定义的。

Qt4.8版本以前的connect()

好激动,大boss出现了,秒了它。。。
我们在这里使用的是Qt4.8版本之前旧的connect()语法,如果想要理解Qt5.0以上版本该语法的解释,可以去看这篇文章:Signals and Slots in Qt5.
我们首先看看这个函数介绍(在qobject.cpp文件中):

threadsafe
    Creates a connection of the given \a type from the \a signal in
the \a sender object to the \a method in the \a receiver object.
Returns a handle to the connection that can be used to disconnect
it later.
    You must use the \c SIGNAL() and \c SLOT() macros when specifying
the \a signal and the \a method, for example:
\snippet code/src_corelib_kernel_qobject.cpp 22
    This example ensures that the label always displays the current
scroll bar value. Note that the signal and slots parameters must not
contain any variable names, only the type. E.g. the following would
not work and return false:
\snippet code/src_corelib_kernel_qobject.cpp 23
A signal can also be connected to another signal:
\snippet code/src_corelib_kernel_qobject.cpp 24
    In this example, the \c MyWidget constructor relays a signal from
a private member variable, and makes it available under a name
that relates to \c MyWidget.
    A signal can be connected to many slots and signals. Many signals
can be connected to one slot.
    If a signal is connected to several slots, the slots are activated
in the same order in which the connections were made, when the
signal is emitted.
    The function returns a QMetaObject::Connection that represents
a handle to a connection if it successfully
connects the signal to the slot. The connection handle will be invalid
if it cannot create the connection, for example, if QObject is unable
to verify the existence of either \a signal or \a method, or if their
signatures aren't compatible.
    You can check if the handle is valid by casting it to a bool.
By default, a signal is emitted for every connection you make;
two signals are emitted for duplicate connections. You can break
all of these connections with a single disconnect() call.
    If you pass the Qt::UniqueConnection \a type, the connection will only
be made if it is not a duplicate. If there is already a duplicate
(exact same signal to the exact same slot on the same objects),
the connection will fail and connect will return an invalid QMetaObject::Connection.
    The optional \a type parameter describes the type of connection
to establish. In particular, it determines whether a particular
signal is delivered to a slot immediately or queued for delivery
at a later time. If the signal is queued, the parameters must be
of types that are known to Qt's meta-object system, because Qt
needs to copy the arguments to store them in an event behind the
scenes. If you try to use a queued connection and get the error
message
\snippet code/src_corelib_kernel_qobject.cpp 25
call qRegisterMetaType() to register the data type before you
establish the connection.
\sa disconnect(), sender(), qRegisterMetaType(), Q_DECLARE_METATYPE()

上面的介绍总结起来有以下几点:
1、返回值是一个该连接的句柄,用于删除连接与表示连接成功与否;
2、这个版本的connect()必须要使用SIGNAL() 和 SLOT()宏指定信号和槽;
3、一个信号可以与其他信号连接;
4、一个信号可以与许多槽函数或者信号连接;多个信号可以与一个槽函数连接;
5、当一个信号与多个槽函数连接时,槽函数的响应是按照connect的顺序来的;
6、可选的参数用来指定槽函数是立即响应信号或者是稍后响应。
接下来我们要看到connect()的真面目了,我们在main.c里面调用了

QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));

我们把宏SIGNAL()和SLOT()展开,实际上传递的参数是如下的:

QObject::connect(&a, "2valueChanged(int)", &b, "1setValue(int)");

最后,我们来看看connect()的函数实现吧:

QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,
                                     const QObject *receiver, const char *method,
                                     Qt::ConnectionType type)
{
    // (1)
    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;
    // (2)
    if (!check_signal_macro(sender, signal, "connect", "bind"))
        return QMetaObject::Connection(0);
    const QMetaObject *smeta = sender->metaObject();       // (3)
    const char *signal_arg = signal;
    ++signal; //skip code
    QArgumentTypeArray signalTypes;
    Q_ASSERT(QMetaObjectPrivate::get(smeta)->revision >= 7);
    QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes); // (4)
    // (5)
    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);

    // (6)
    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);
    // (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);
    }

    // (8)
    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);
    }
    // (9)
    int *types = 0;
    if ((type == Qt::QueuedConnection)
            && !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) {
        return QMetaObject::Connection(0);
    }

#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
    // (10)
    QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
        sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));
    return handle;
}

我来分别解释一下代码中各段的意思:
(1):参数的有效性检查
判断传递进来的4个参数指针是否都为NULL,若有NULL,提供警告信息并返回错误;
(2):检查信号的有效性
实际上就是判断SIGNAL()展开后的得到的字符串的前面的标识是否为2。

if (!check_signal_macro(sender, signal, "connect", "bind"))
        return QMetaObject::Connection(0);

static bool check_signal_macro(const QObject *sender, const char *signal,
                                const char *func, const char *op)
{
    int sigcode = extract_code(signal);
    if (sigcode != QSIGNAL_CODE) {
        if (sigcode == QSLOT_CODE)
            qWarning("QObject::%s: Attempt to %s non-signal %s::%s",
                     func, op, sender->metaObject()->className(), signal+1);
        else
            qWarning("QObject::%s: Use the SIGNAL macro to %s %s::%s",
                     func, op, sender->metaObject()->className(), signal);
        return false;
    }
    return true;
}

(3):获取得到staticMetaObject,实际上就是Counter类的详细信息moc_counter.cpp,我们在文章的开头处有讲过了,这里面包含了很多该重要信息。

const QMetaObject *smeta = sender->metaObject();

(4):将信号的名称与参数分开

QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);


// Given a method \a signature (e.g. "foo(int,double)"), this function
// populates the argument \a types array and returns the method name.
QByteArray QMetaObjectPrivate::decodeMethodSignature(
        const char *signature, QArgumentTypeArray &types)
{
    Q_ASSERT(signature != 0);
    const char *lparens = strchr(signature, '(');
    if (!lparens)
        return QByteArray();
    const char *rparens = strrchr(lparens + 1, ')');
    if (!rparens || *(rparens+1))
        return QByteArray();
    int nameLength = lparens - signature;
    argumentTypesFromString(lparens + 1, rparens, types);
    return QByteArray::fromRawData(signature, nameLength);
}

将信号的名称作为返回值给signalName,将参数信息传递给signalTypes。把信号的名称与参数分开。
(5):第(5)到第(6)之间的内容看不懂,只知道它是为了获取信号在类中的索引值,为什么要大费周章的获取真是搞不明白。
推测与moc_counter.cpp中的qt_meta_data_Counter有关。
(6):从(6)开始往下到(8)是处理槽函数相关的工作,它所做的处理与信号函数是一样的。
(7):从(7)的代码中看到switch case 语句不仅检测SLOT的标识符1并且还检测SIGNAL的标识符2,是不是信号也可以和信号绑定的呢??我有在代码中进行验证,将信号与信号connect在一起,我在main.c中稍微做了修改:

int main()
{
    Counter a, b, c;
    QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SIGNAL(valueChanged(int)));
    QObject::connect(&b, SIGNAL(valueChanged(int)), &c, SLOT(setValue(int)));
    qDebug() << "First :c.value() is " << c.value();
    a.setValue(12);
    qDebug() << "Second:c.value() is " << c.value();
    return 0;
}

很明显,c.value()的结果是12。所以说:信号与信号是可以绑定的。

(8):检测信号函数和槽函数的参数类型以及参数个数是否是一致的。
(9):判断connect()第五个可选参数的连接类型。
(10):好吧,这个大boss还有分身。

QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
        sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));

好,让我们看看这个分身的原型:

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)

它的返回值是一个指向QObjectPrivate::Connection类型的指针。
第1个参数是发送信号的对象sender;
第2个参数是信号在类中的索引值signal_index;
第3个参数是发送对象的元对象smeta;
第4个参数是响应信号的对象receiver;
第5个参数是响应信号槽函数的索引值method_index_relative;
第6个参数是接收对象的元对象rmeta;
第7个和第8个参数是该连接的类型。
那我们继续看该分身的实现,有些内容将省略。

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)
{
    // 省略

    QObjectPrivate::StaticMetaCallFunction callFunction =
        rmeta ? rmeta->d.static_metacall : 0;

    //省略

    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();
    QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
    if (smethod.isValid())
        s->connectNotify(smethod);

    return c.take();
}
QObjectPrivate::StaticMetaCallFunction callFunction =
        rmeta ? rmeta->d.static_metacall : 0;

这个语句是从接收对象的元对象中获取调用函数(回调函数)的信息static_metacall我们在前面得出

static_metacall = qt_static_metacall;

qt_static_metacall又在moc_counter.cpp文件中,所以说moc_counter.cpp很重要吧。
接下来是创建一个QObjectPrivate::Connection类型的变量c(这个好像是模板吧,对于我的C++知识表示很着急。。)。反正我只知道把一些相关信息保存在connectionList中。至于怎样保存?如何保存?保存哪些东西?现在都还没有深究过。后面再把这里的知识补上。


总结

1、当我们在xxx.h文件中声明了信号和槽函数相关的内容,MOC会根据该内容生成一个对相应moc_xxx.cpp的文件,里面包含了该.h文件中很多重要的信息,比如信号与槽的索引值;
2、当我们在程序中调用connect()函数绑定信号与槽,实际上在内部做了很多个工作。为发送对象的信号所维护的connectionLists中添加接收对象的相关信息(名称、槽函数名称、参数类型……),实际上就是信息的绑定。
3、当我们在程序中emit一个信号,会从该信号维护的connectionLists找到与之绑定的槽函数并执行。
4、Qt中信号与槽的机制确实是通过回调函数实现的,但并不仅仅是这样的。它在内部做了很多的努力,比如安全性的保证、比如使用connectionLists保证信号与槽简单方便的使用。

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
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 ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值