Hello Qt -- source code analysis of Qt signal slot mechanism

Posted by abhikerl on Sun, 06 Mar 2022 17:13:03 +0100

Based on QT4 Version 8.6

1, Principle of signal slot mechanism

1. Introduction to signal slot

Signal slot is an implementation of observer mode, and its characteristics are as follows:

A. A signal is an event that can be observed, or at least a notification that an event has occurred;

B. A slot is an observer, which is usually a function called when the observed object changes - or when a signal is sent out;

C. The connection between the signal and the slot forms an observer observed relationship;

D. When the event or state changes, the signal will be sent; At the same time, the signaler is obliged to call all registered functions (slots) interested in this event (signal).

Signals and slots are many to many. A signal can be connected to multiple slots, and a slot can also monitor multiple signals.

Signal slot has nothing to do with language. There are many ways to implement signal slot. Different implementation mechanisms will lead to great differences in signal slot. Signal slot terminology was originally derived from Qt Library of Trolltech company. Due to its advanced design concept, it immediately attracted the attention of the computer science community and put forward various implementations. At present, signal slot is still one of the cores of Qt library. Many other libraries also provide similar implementations, and even some tool libraries specifically provide this mechanism.

Signal slot is an efficient communication interface between Qt objects and their derived class objects. It is not only the core feature of Qt, but also an important difference between Qt and other toolkits. The signal slot is completely independent of the standard C/C + + language. Therefore, to correctly handle the signal and slot, we must use a Qt tool called MOC (Meta Object Compiler). MOC tool is a C + + preprocessor, which can automatically generate the required additional code for high-level event processing.

2. Implementation of different platforms

The message mechanism in MFC does not adopt the virtual function mechanism in C + +, because there are too many messages and the virtual function overhead is too large. In Qt, the virtual function mechanism in C + + is not adopted, but the signal slot mechanism is adopted for the same reason. For deeper reasons, there are only two underlying implementation mechanisms of polymorphism, one is to look up the table by name and the other is to look up the table by location. The two methods have their own advantages and disadvantages, and the virtual function mechanism of C + + unconditionally adopts the latter, which leads to the problem that the cost is too high when there are few subclasses to overload the implementation of the base class. In addition, there are many subclasses in interface programming. Basically, the efficiency of the virtual function mechanism of C + + is too low, so the writers of each library have to make their own way. Of course, This is actually the defect of C + + language itself.

2, Example analysis of Qt signal slot

1. Use example of signal slot

Use simple examples:

#ifndef OBJECT_H
#define OBJECT_H

#include <QObject>
#include <QString>
#include <QDebug>

class Object : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int age READ age  WRITE setAge NOTIFY ageChanged)
    Q_PROPERTY(int score READ score  WRITE setScore NOTIFY scoreChanged)
    Q_PROPERTY(Level level READ level WRITE setLevel)
    Q_CLASSINFO("Author", "Scorpio")
    Q_CLASSINFO("Version", "1.0")
public:
    enum Level
    {
        Basic = 1,
        Middle,
        Advanced,
        Master
    };
    Q_ENUMS(Level)
protected:
    QString m_name;
    Level m_level;
    int m_age;
    int m_score;
    void setLevel(const int& score)
    {
        if(score <= 60)
        {
            m_level = Basic;
        }
        else if(score < 100)
        {
            m_level = Middle;
        }
        else if(score < 150)
        {
            m_level = Advanced;
        }
        else
        {
            m_level = Master;
        }
    }
public:
    explicit Object(QString name, QObject *parent = 0):QObject(parent)
    {
        m_name = name;
        setObjectName(m_name);
        connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
        connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
    }

    int age()const
    {
        return m_age;
    }

    void setAge(const int& age)
    {
        m_age = age;
        emit ageChanged(m_age);
    }

    int score()const
    {
        return m_score;
    }

    void setScore(const int& score)
    {
        m_score = score;
        setLevel(m_score);
        emit scoreChanged(m_score);
    }

    Level level()const
    {
        return m_level;
    }

    void setLevel(const Level& level)
    {
        m_level = level;
    }
signals:
    void ageChanged(int age);
    void scoreChanged(int score);
public slots:
     void onAgeChanged(int age)
     {
         qDebug() << "age changed:" << age;
     }

     void onScoreChanged(int score)
     {
         qDebug() << "score changed:" << score;
     }
};

#endif // OBJECT_H

Main function:

#include <QCoreApplication>
#include "Object.h"


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Object ob("object");

    //Set attribute age
    ob.setProperty("age", QVariant(30));
    qDebug() << "age: " << ob.age();
    qDebug() << "property age: " << ob.property("age").toInt();

    //Set attribute score
    ob.setProperty("score", QVariant(90));
    qDebug() << "score: " << ob.score();
    qDebug() << "property score: " << ob.property("score").toInt();

    qDebug() << "Level: " << ob.level();
    ob.setProperty("level", 4);
    qDebug() << "level: " << ob.level();
    qDebug() << "Property level: " << ob.property("level").toInt();

    //Introspection, query object information at runtime
    qDebug() << "object name: " << ob.objectName();
    qDebug() << "class name: " << ob.metaObject()->className();
    qDebug() << "isWidgetType: " << ob.isWidgetType();
    qDebug() << "inherit: " << ob.inherits("QObject");

    return a.exec();
}

2. SIGNAL and SLOT macros

The SIGNAL and SLOT macros are defined in / SRC / corelib / kernel / qobjectdefs H file.

Q_CORE_EXPORT const char *qFlagLocation(const char *method);

#define QTOSTRING_HELPER(s) #s
#define QTOSTRING(s) QTOSTRING_HELPER(s)
#ifndef QT_NO_DEBUG
# define QLOCATION "\0" __FILE__ ":" QTOSTRING(__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

The SIGNAL and SLOT macros will use the precompiler to convert some parameters into strings and add encoding in front.

In the debugging mode, if there is a problem with the connection of signal, the corresponding file location will be indicated when the warning message is prompted. qFlagLocation is used to locate the line information corresponding to the code and register the address information of the corresponding code into a table with two entries.

Object. The code of the macro part of SIGNAL and SLOT in H file is as follows:

connect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));

Through object H file to get object I file.

Precompiled using G + +

g++ -E Object.h -o Object.i -I/usr/local/Trolltech/Qt-4.8.6/include/QtCore -I/usr/local/Trolltech/Qt-4.8.6/include -I. 

Object.i the results in the document are as follows:

connect(this, qFlagLocation("2""ageChanged(int)" "\0" "Object.h" ":" "54"), 
        this, qFlagLocation("1""onAgeChanged(int)" "\0" "Object.h" ":" "54"));

connect(this, qFlagLocation("2""scoreChanged(int)" "\0" "Object.h" ":" "55"), 
        this, qFlagLocation("1""onScoreChanged(int)" "\0" "Object.h" ":" "55"));

3. Meta object of class

When compiling the program, make calls MOC to parse the project source code and generate the MOC of the corresponding class_ xxx. Cpp file,

const QMetaObject Object::staticMetaObject = {
    { &QObject::staticMetaObject, qt_meta_stringdata_Object,
      qt_meta_data_Object, &staticMetaObjectExtraData }
};

The filled values of the static member staticMetaObject are as follows:

//The metadata of the base class of the class represented by the metadata is filled as the metadata pointer of the base class & QWidget:: staticmetaobject
const QMetaObject *superdata;
//The signature mark of metadata is filled with qt_meta_stringdata_Widget.data
const char *stringdata;
//The pointer to the index array of metadata is filled with qt_meta_data_Widget
const uint *data;
//The pointer of the extended metadata table is filled with function pointers
const QMetaObject **extradata;
qt_static_metacall. 

The initialization of staticMetaObjectExtraData is as follows:

const QMetaObjectExtraData Object::staticMetaObjectExtraData = {
    0,  qt_static_metacall 
};

Internal member of type QMetaObjectExtraData static_metacall is a pointer to object:: QT_ static_ Function pointer to metacall.

The memory layout of Object is as follows:

The Object memory layout already contains static members staticMetaObject and staticMetaObjectExtraData.

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

QObject::d_ PTR - > metaobject is only used by dynamic meta objects (QML objects), so generally speaking, the virtual function metaObject() only returns the static metaobject of the class.

4. Metadata table

When compiling Qt program, make will call MOC tool to analyze the source file. If a class contains Q_OBJECT macro, MOC will generate the corresponding moc_xxx.cpp file.

moc_Object.cpp file contents:

The metadata of Object is as follows:

static const uint qt_meta_data_Object[] = {
       //Content: content information
       6,       //revision # version number of MOC generated code
       0,       //classname = class name, in QT_ meta_ stringdata_ The index in the object array is 0
       2,   14, //classinfo class information, with 2 cassinfo definitions,
       4,   18, //The methods} class has four user-defined methods, namely, the number of signals and slots,
       3,   38, //The location information of the properties property has three custom properties,
       1,   50, //enums/sets enumeration location information, there is a custom enumeration, in QT_ meta_ stringdata_ The index in the object array is 50
       0,    0, //constructors} constructor location information
       0,       // flags
       2,       // signalCount

     //classinfo: key, value / / class information is stored in qt_meta_stringdata_Object array,
      15,    7,    //For the first class information, the array index of key is 15, that is, Author, and the array index of value is 7, that is, Scorpio
      26,   22,      //For the second class information, the array index of key is 26, that is, the array index of Version and value is 22, that is, 1.0
     // signals: signature, parameters, type, tag, flags
      39,   35,   34,   34, 0x05,   //The signature of the first custom signal is stored in qt_meta_stringdata_Object array,
    //The index is 39, which is ageChanged(int)
      61,   55,   34,   34, 0x05,   //The signature of the second custom signal is stored in qt_meta_stringdata_Object array,
    //The index is 61, that is, scoreChanged(int)
 // slots: signature, parameters, type, tag, flags
      79,   35,   34,   34, 0x0a,   //The signature of the first custom slot function is stored in qt_meta_stringdata_Object array,
    //The index is 79, which means onAgeChanged(int)
      97,   55,   34,   34, 0x0a,   //The signature of the second custom slot function is stored in qt_meta_stringdata_Object array,
    //The index is 79, which means onScoreChanged(int)
 // properties: name, type, flags
      35,  117, 0x02495103,        //The signature of the first custom attribute is stored in QT_ meta_ stringdata_ In object, the index is 35, that is, age
      55,  117, 0x02495103,     //The signature of the second custom attribute is stored in QT_ meta_ stringdata_ In object, the index is 55, that is, score
     127,  121, 0x0009510b,     //The signature of the third custom attribute is stored in QT_ meta_ stringdata_ In object, the index is 127, that is, level
 // properties: notify_signal_id / / the signal number associated with the attribute
       0,
       1,
       0,
 // enums: name, flags, count, data
     121, 0x0,    4,   54,        //The definition of enumeration is stored in QT_ meta_ stringdata_ In object, the index is 121, that is, Level, which contains four enumeration constants

     //enum data: key, value / / enumerate key value pairs of data
     133, uint(Object::Basic),    //The array index is 133, or Basic
     139, uint(Object::Middle),    //The array index is 139, which is Middle
     146, uint(Object::Advanced),  //The array index is 146, or Advanced
     155, uint(Object::Master),    //The array index is 155, that is, Master
       0        //eod # metadata end tag
};

Introspection table is a uint array, which is divided into five parts: the first part, content, is divided into nine rows. The first line revision refers to the version number of the code generated by MOC (Qt4 is 6, Qt5 is 7). The second classname, that is, the class name, is an index that points to a location in the string table (in this case, bit 0).

static const char qt_meta_stringdata_Object[] = {
    "Object\0Scorpio\0Author\0""1.0\0Version\0\0"
    "age\0ageChanged(int)\0score\0scoreChanged(int)\0"
    "onAgeChanged(int)\0onScoreChanged(int)\0"
    "int\0Level\0level\0Basic\0Middle\0Advanced\0"
    "Master\0"
};

5. Realization of signal

MOC in generated moc_xxx.cpp file implements the signal, creates an array of pointers to parameters, and passes the pointer array to the QMetaObject::activate function. The first element of the array is the return value. The value in this example is 0 because the return value is void. The third parameter passed to the activate function is the index of the signal (0 in this case).

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

//Implementation of SIGNAL # 1# scoreChanged SIGNAL
void Object::scoreChanged(int _t1)
{
    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

6. Call of slot function

Using slot function in QT_ static_ The slot function is called at the index position of the metacall function:

void Object::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void**_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        Q_ASSERT(staticMetaObject.cast(_o));
        Object *_t = static_cast<Object *>(_o);
        switch (_id) {
        case 0: _t->ageChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 1: _t->scoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 2: _t->onAgeChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 3: _t->onScoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        default: ;
        }
    }
}

7. Index in meta object

In each QMetaObject object, slots, signals and other object callable functions will allocate an index starting from 0. The index is sequential. The signal is in the first bit, the slot is in the second bit, and finally other functions. This index is internally referred to as a relative index and does not contain the index bits of the parent object.

In order to realize the index of other functions included in the inheritance chain, an offset is added to the relative index to obtain the absolute index. The absolute index is the index used in the public API and is returned by functions like QMetaObject::indexOf(Signal, Slot, Method).

The join mechanism uses vectors indexed by signals. However, in a vector, all slots also occupy a certain space. Usually, there are more slots than signals in an object. Therefore, starting from Qt 4.6, a new internal implementation containing only signal index is used.

8. Connection between signal and slot

When starting the connection, the first thing Qt needs to do is to find the index of the required signal and slot. Qt will look up the string table of the meta object to find the corresponding index.

Then, create a QObjectPrivate::Connection object and add it to the internal linked list.

Since multiple slots are allowed to be connected to the same signal, a list of connected slots needs to be added for each signal. Each connection must contain the index of the receiving object and slot. When the receiving object is destroyed, the corresponding connection can also be destroyed automatically. Therefore, each receiving object needs to know who is connected to it so that it can clean up the connection.

The private data of QObject object QObjectPrivate is as follows:

class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
    Q_DECLARE_PUBLIC(QObject)
public:
    struct ExtraData
    {
        ExtraData() {}
        QList<QByteArray> propertyNames;
        QList<QVariant> propertyValues;
    };

    typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **);
    struct Connection
    {
        QObject *sender;
        QObject *receiver;
        StaticMetaCallFunction callFunction;
        // The next pointer for the singly-linked ConnectionList
        Connection *nextConnectionList;
        //senders linked list
        Connection *next;
        Connection **prev;
        QBasicAtomicPointer<int> argumentTypes;
        ushort method_offset;
        ushort method_relative;
        ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
        ~Connection();
        int method() const { return method_offset + method_relative; }
    };

    // ConnectionList is a singly-linked list
    struct ConnectionList {
        ConnectionList() : first(0), last(0) {}
        Connection *first;
        Connection *last;
    };

    struct Sender
    {
        QObject *sender;
        int signal;
        int ref;
    };

    QObjectPrivate(int version = QObjectPrivateVersion);
    virtual ~QObjectPrivate();
    void deleteChildren();

    void setParent_helper(QObject *);
    void moveToThread_helper();
    void setThreadData_helper(QThreadData *currentData, QThreadData *targetData);
    void _q_reregisterTimers(void *pointer);

    bool isSender(const QObject *receiver, const char *signal) const;
    QObjectList receiverList(const char *signal) const;
    QObjectList senderList() const;

    void addConnection(int signal, Connection *c);
    void cleanConnectionLists();

    static inline Sender *setCurrentSender(QObject *receiver,
                                    Sender *sender);

    static inline void resetCurrentSender(QObject *receiver,
                                   Sender *currentSender,
                                   Sender *previousSender);

    static void clearGuards(QObject *);

    static QObjectPrivate *get(QObject *o) {
        return o->d_func();
    }

    int signalIndex(const char *signalName) const;
    inline bool isSignalConnected(uint signalIdx) const;

    // To allow arbitrary objects to call connectNotify()/disconnectNotify() without making
    // the API public in QObject. This is used by QDeclarativeNotifierEndpoint.
    inline void connectNotify(const char *signal);
    inline void disconnectNotify(const char *signal);

    static inline void signalSignature(const QMetaMethod &signal,
                                       QVarLengthArray<char> *result);
public:
    QString objectName;
    ExtraData *extraData;    // extra data set by the user
    QThreadData *threadData; // id of the thread that owns the object
    QObjectConnectionListVector *connectionLists;//Linked list vector container
    Connection *senders;     // linked list of connections connected to this object
    Sender *currentSender;   // object currently activating the object
    mutable quint32 connectedSignals[2];
    // preserve binary compatibility with code compiled without Qt 3 support
    // keeping the binary layout stable helps the Qt Creator debugger
    void *unused;

    QList<QPointer<QObject> > eventFilters;
    union {
        QObject *currentChildBeingDeleted;
        QAbstractDeclarativeData *declarativeData; //extra data used by the declarative module
    };

    // these objects are all used to indicate that a QObject was deleted
    // plus QPointer, which keeps a separate list
    QAtomicPointer<QtSharedPointer::ExternalRefCountData> sharedRefcount;
};

Each QObject object has a linked list container QObjectConnectionListVector * connectionLists: associate each signal with a linked list of QObjectPrivate::Connection.

The QObject::connect function is implemented as follows:

bool QObject::connect(const QObject *sender, const char *signal,
                      const QObject *receiver, const char *method,
                      Qt::ConnectionType type)
{
    {
        const void *cbdata[] = { sender, signal, receiver, method, &type };
        if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
            return true;
    }

    if (type == Qt::AutoCompatConnection) {
        type = Qt::AutoConnection;
    }

    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 false;
    }

    QByteArray tmp_signal_name;

    if (!check_signal_macro(sender, signal, "connect", "bind"))
        return false;
    const QMetaObject *smeta = sender->metaObject();
    const char *signal_arg = signal;
    ++signal; //skip code
    //Find the relative index of the signal in the meta object of the sender object
    int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, false);
    if (signal_index < 0)
    {
        // check for normalized signatures
        tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
        signal = tmp_signal_name.constData() + 1;
        smeta = sender->metaObject();
        signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, false);
    }

    if (signal_index < 0)
    {
        // re-use tmp_signal_name and signal from above
        smeta = sender->metaObject();
        signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal, true);
    }

    if (signal_index < 0) {
        err_method_notfound(sender, signal_arg, "connect");
        err_info_about_objects("connect", sender, receiver);
        return false;
    }

    signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);
    int signalOffset, methodOffset;
    computeOffsets(smeta, &signalOffset, &methodOffset);
    int signal_absolute_index = signal_index + methodOffset;
    signal_index += signalOffset;

    QByteArray tmp_method_name;
    int membcode = extract_code(method);

    if (!check_method_code(membcode, receiver, method, "connect"))
        return false;

    const char *method_arg = method;
    ++method; // skip code

    const QMetaObject *rmeta = receiver->metaObject();
    //Find the relative index of the slot function in the meta object of the recipient object
    int method_index_relative = -1;
    switch (membcode) {
    case QSLOT_CODE:
        method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, false);
        break;
    case QSIGNAL_CODE:
        method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, false);
        break;
    }

    if (method_index_relative < 0) {
        // check for normalized methods
        tmp_method_name = QMetaObject::normalizedSignature(method);
        method = tmp_method_name.constData();

        // rmeta may have been modified above
        rmeta = receiver->metaObject();
        switch (membcode) {
        case QSLOT_CODE:
            method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, false);
            if (method_index_relative < 0)
                method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(&rmeta, method, true);
            break;
        case QSIGNAL_CODE:
            method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, false);

            if (method_index_relative < 0)
                method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(&rmeta, method, true);
            break;
        }
    }

    if (method_index_relative < 0) {
        err_method_notfound(receiver, method_arg, "connect");
        err_info_about_objects("connect", sender, receiver);
        return false;
    }

    //Check whether the connection parameters match
    if (!QMetaObject::checkConnectArgs(signal, method))
    {
        qWarning("QObject::connect: Incompatible sender/receiver arguments"
                 "\n        %s::%s --> %s::%s",
                 sender->metaObject()->className(), signal,
                 receiver->metaObject()->className(), method);
        return false;
    }

    int *types = 0;
    if ((type == Qt::QueuedConnection)
            && !(types = queuedConnectionTypes(smeta->method(signal_absolute_index).parameterTypes())))
        return false;
    //Call QMetaObjectPrivate::connect to connect the signal to the slot
    if (!QMetaObjectPrivate::connect(sender, signal_index, receiver, method_index_relative, rmeta ,type, types))
        return false;
    const_cast<QObject*>(sender)->connectNotify(signal - 1);
    return true;
}

QObject:: the main function of the connect function is to find the relative index of the slot function in the meta object of the recipient object, find the relative index of the slot function in the meta object of the recipient object, and finally call QMetaObjectPrivate::connect to connect the signal to the slot. The meta objects of QObject and its derived class objects have a QObjectConnectionListVector connection list container when they are created. The function of QObject::connect is to add a new connection to the connection list of the corresponding signal of the connection list container of the meta object attached to the signal sender (a signal may connect multiple slot functions).

Each QObject and its derived class objects have a QObjectConnectionListVector *connectionLists connection linked list container. Take the index of the signal as the index of the container, and associate each signal with a QObjectPrivate::ConnectionList linked list. At the same time, a slot function connected in the QObjectPrivate::ConnectionList linked list may be one of the slot function linked lists of the recipient object. The linked list of each recipient object is as follows:

The prev pointer to senderList is a pointer to a pointer. This is because it does not really point to the previous node, but to the next pointer in the previous node. This pointer is only used when the connection is destroyed and cannot be traversed backwards. It allows no special handling for the first element.

The ConnectionList stored in the container is as follows:

  struct ConnectionList {
        ConnectionList() : first(0), last(0) {}
        Connection *first;//First node
        Connection *last;//Last node
    };

Each ConnectionList type element is a two-way linked list that holds all connections of the signal. The Connection type and structure are as follows:

struct Connection
{
    QObject *sender;//sender 
    QObject *receiver;//recipient
    StaticMetaCallFunction callFunction;//Slot function called
    // The next pointer for the singly-linked ConnectionList
    Connection *nextConnectionList;
    //senders linked list
    Connection *next;
    Connection **prev;
    QBasicAtomicPointer<int> argumentTypes;
    ushort method_offset;
    ushort method_relative;
    ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
    ~Connection();
    int method() const { return method_offset + method_relative; }
};

The source code of QMetaObjectPrivate::connect function is as follows:
//Add a new connection to the connection list of the corresponding signal in the connection list container of the signal sender, in which the index of the connection list added by the connection is the index of the signal

bool QMetaObjectPrivate::connect(const QObject *sender, int signal_index,
                                 const QObject *receiver, int method_index,
                                 const QMetaObject *rmeta, int type, int *types)
{
    QObject *s = const_cast<QObject *>(sender);
    QObject *r = const_cast<QObject *>(receiver);
    int method_offset = rmeta ? rmeta->methodOffset() : 0;
    //Find the callback function pointer QT in the metadata string of the meta object_ static_ metacall
    QObjectPrivate::StaticMetaCallFunction callFunction =
        (rmeta && QMetaObjectPrivate::get(rmeta)->revision >= 6 && rmeta->d.extradata)
        ? reinterpret_cast<const QMetaObjectExtraData *>(rmeta->d.extradata)->static_metacall : 0;
    QOrderedMutexLocker locker(signalSlotLock(sender),
                               signalSlotLock(receiver));

    //If the connection type is Qt::UniqueConnection
    if (type & Qt::UniqueConnection)
    {
        QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;
        if (connectionLists && connectionLists->count() > signal_index)
        {
            //Obtain the connection of the signal according to the signal index
            const QObjectPrivate::Connection *c2 =
                (*connectionLists)[signal_index].first;
            int method_index_absolute = method_index + method_offset;
            while (c2)
            {   //If the receiver of the signal is the same and the slot function is the same, that is, the same connection already exists
                if (c2->receiver == receiver && c2->method() == method_index_absolute)
                    return false;//Direct return,
                c2 = c2->nextConnectionList;//Next signal connection
            }
        }
        type &= Qt::UniqueConnection - 1;
    }

    //Create a new connection
    QObjectPrivate::Connection *c = new QObjectPrivate::Connection;

    //Set the properties of the connection
    c->sender = s;
    c->receiver = r;
    c->method_relative = method_index;
    c->method_offset = method_offset;
    c->connectionType = type;
    c->argumentTypes = types;
    c->nextConnectionList = 0;
    c->callFunction = callFunction;//Set the callback function pointer to qt_static_metacall

    QT_TRY
    {   //Add the connection to the connection list corresponding to the corresponding signal in the connection list container of the sender
        QObjectPrivate::get(s)->addConnection(signal_index, c);
    } QT_CATCH(...) {
        delete c;
        QT_RETHROW;
    }

    c->prev = &(QObjectPrivate::get(r)->senders);
    c->next = *c->prev;
    *c->prev = c;
    if (c->next)
        c->next->prev = &c->next;

    QObjectPrivate *const sender_d = QObjectPrivate::get(s);
    if (signal_index < 0)
    {
        sender_d->connectedSignals[0] = sender_d->connectedSignals[1] = ~0;
    }
    else if (signal_index < (int)sizeof(sender_d->connectedSignals) * 8)
    {
        sender_d->connectedSignals[signal_index >> 5] |= (1 << (signal_index & 0x1f));
    }

    return true;
}

9. Signal transmission

When using emit to transmit signals, the signal function implemented by MOC is actually called, and the QMetaObject::activate() function is called inside the signal function.

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

//Implementation of SIGNAL # 1# scoreChanged SIGNAL
void Object::scoreChanged(int _t1)
{
    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

void QMetaObject::activate(QObject *sender, const QMetaObject *m,
                           int local_signal_index,void **argv)
{
    int signalOffset;
    int methodOffset;
    computeOffsets(m, &signalOffset, &methodOffset);

    int signal_index = signalOffset + local_signal_index;

    if (!sender->d_func()->isSignalConnected(signal_index))
        return; //If the transmitted signal has no slot connection, it returns directly

    if (sender->d_func()->blockSig)
        return;//If blocked, return directly

    int signal_absolute_index = methodOffset + local_signal_index;

    void *empty_argv[] = { 0 };
    if (qt_signal_spy_callback_set.signal_begin_callback != 0)
    {
        qt_signal_spy_callback_set.signal_begin_callback(sender, signal_absolute_index,
                                                         argv ? argv : empty_argv);
    }

    Qt::HANDLE currentThreadId = QThread::currentThreadId();

    QMutexLocker locker(signalSlotLock(sender));
    //Get the connection list container of the sender
    QObjectConnectionListVector *connectionLists = sender->d_func()->connectionLists;
    if (!connectionLists)
    {
        locker.unlock();
        if (qt_signal_spy_callback_set.signal_end_callback != 0)
            qt_signal_spy_callback_set.signal_end_callback(sender, signal_absolute_index);
        return;
    }

    ++connectionLists->inUse;

    //Use the signal index as the index from the sender's connection list container to obtain the corresponding connection list
    const QObjectPrivate::ConnectionList *list;
    if (signal_index < connectionLists->count())
        list = &connectionLists->at(signal_index);
    else
        list = &connectionLists->allsignals;
    do {
        //Ask for the first connection in the connection list of the transmitted signal
        QObjectPrivate::Connection *c = list->first;
        if (!c) continue;//If the connection is empty, 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
        {
            if (!c->receiver)
                continue;//If the recipient of the connection is empty, continue

            QObject * const receiver = c->receiver;
            const bool receiverInSameThread = currentThreadId == receiver->d_func()->threadData->threadId;

            // determine if this connection should be sent immediately or
            // put into the event queue
            if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                    || (c->connectionType == Qt::QueuedConnection))
            {
                queued_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv);
                continue;
#ifndef QT_NO_THREAD
            }
            //Blocking queue connection type
            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;
                QCoreApplication::postEvent(receiver, new QMetaCallEvent(c->method_offset, c->method_relative,
                                                                         c->callFunction,

                                                                         sender, signal_absolute_index,

                                                                         0, 0,

                                                                         argv ? argv : empty_argv,

                                                                         &semaphore));
                semaphore.acquire();
                locker.relock();
                continue;
#endif
            }

            QObjectPrivate::Sender currentSender;
            QObjectPrivate::Sender *previousSender = 0;
            if (receiverInSameThread)
            {
                currentSender.sender = sender;
                currentSender.signal = signal_absolute_index;
                currentSender.ref = 1;
                previousSender = QObjectPrivate::setCurrentSender(receiver, ¤tSender);
            }

            //Gets the callback function pointer of the connection
            const QObjectPrivate::StaticMetaCallFunction callFunction = c->callFunction;
            const int method_relative = c->method_relative;
            //If the offset of the connected method is less than the offset of the method of the recipient's meta object
            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();

                if (qt_signal_spy_callback_set.slot_begin_callback != 0)

                    qt_signal_spy_callback_set.slot_begin_callback(receiver, c->method(), argv ? argv : empty_argv);

                //QT is called according to the receiver's method offset, receiver and other parameters_ static_ Metacall callback function

                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, c->method());

                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);
                }

                //Call the metacall of the sending meta object according to the receiver, the receiver's method index and other parameters

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

            if (receiverInSameThread)
                QObjectPrivate::resetCurrentSender(receiver, ¤tSender, previousSender);

            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));

    --connectionLists->inUse;
    Q_ASSERT(connectionLists->inUse >= 0);
    if (connectionLists->orphaned)
    {
        if (!connectionLists->inUse)
            delete connectionLists;
    }
    else if (connectionLists->dirty)
    {
        sender->d_func()->cleanConnectionLists();
    }

    locker.unlock();

    if (qt_signal_spy_callback_set.signal_end_callback != 0)
        qt_signal_spy_callback_set.signal_end_callback(sender, signal_absolute_index);

}

QT was called internally in the metacall function_ Metacall function.

int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)
{
    if (QMetaObject *mo = object->d_ptr->metaObject)
        return static_cast<QAbstractDynamicMetaObject*>(mo)->metaCall(cl, idx, argv);
    else
        return object->qt_metacall(cl, idx, argv);
}

int Object::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 < 4)
            qt_static_metacall(this, _c, _id, _a);
        _id -= 4;
    }
#ifndef QT_NO_PROPERTIES
      else if (_c == QMetaObject::ReadProperty) 
    {
        void *_v = _a[0];
        switch (_id) {
        case 0: *reinterpret_cast< int*>(_v) = age(); break;
        case 1: *reinterpret_cast< int*>(_v) = score(); break;
        case 2: *reinterpret_cast< Level*>(_v) = level(); break;
        }
        _id -= 3;
    } 
    else if (_c == QMetaObject::WriteProperty) 
    {
        void *_v = _a[0];
        switch (_id) {
        case 0: setAge(*reinterpret_cast< int*>(_v)); break;
        case 1: setScore(*reinterpret_cast< int*>(_v)); break;
        case 2: setLevel(*reinterpret_cast< Level*>(_v)); break;
        }
        _id -= 3;
    } else if (_c == QMetaObject::ResetProperty) {
        _id -= 3;
    } else if (_c == QMetaObject::QueryPropertyDesignable) {
        _id -= 3;
    } else if (_c == QMetaObject::QueryPropertyScriptable) {
        _id -= 3;
    } else if (_c == QMetaObject::QueryPropertyStored) {
        _id -= 3;
    } else if (_c == QMetaObject::QueryPropertyEditable) {
        _id -= 3;
    } else if (_c == QMetaObject::QueryPropertyUser) {
        _id -= 3;
    }

#endif // QT_NO_PROPERTIES
    return _id;
}

qt_ QT was called internally in the metacall function_ static_ Metacall function.

10. Call of slot function

The slot function finally passes QT_ static_ The metacall function calls the corresponding slot function according to the parameters.

void Object::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) 
    {
        Q_ASSERT(staticMetaObject.cast(_o));
        Object *_t = static_cast<Object *>(_o);
        switch (_id) {
        case 0: _t->ageChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 1: _t->scoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 2: _t->onAgeChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 3: _t->onScoreChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        default: ;
        }
    }
}

11. Function call process analysis

Debug breakpoints inside the onAgeChanged(int age) slot function.

The obtained function call stack is as follows:

Function call stack analysis:

 Object::qt_ The metacall function internally calls the Object::setAge function, the setAge function internally calls the Object::ageChanged signal function, the ageChanged signal function internally calls the QMetaObject::activate function, and the activate function internally calls Object::qt_static_metacall function, final QT_ static_ The slot function onAgeChanged is called internally in the metacall function.

Therefore, in this case, when ob setProperty("age", QVariant(30)); When setting the property, the call of QMetaProperty::Write function is triggered, and then the MOC implemented by MOC is called_ Object. Object:: QT in cpp file_ metacall,qt_metacall internally calls setAge function. setAge function internally sends signal ageChanged, that is, it calls Object::ageChanged signal function. Object::ageChanged function internally calls QMetaObject::activate function of meta object of object object, and object:: QT is internally called in activate function_ static_ Metacall function, final qt_static_metacall internally implements the call to the slot function onAgeChanged.

In this example, the signal and slot are in the same thread, and the connection type is direct connection. Therefore, it belongs to synchronous call and is the simplest call type. The QMetaObject::activate function actually connects the linked list container according to the signal in the meta Object of the Object object to find the corresponding signal: qt_static_metacall callback function, and then callback.

3, Standard C + + implementation of signal slot

1. Simulation Implementation of Qt meta object

Implementation of Object class:

#ifndef OBJECT_H
#define OBJECT_H

#include<map>
#include <iostream>
#include <cstring>
using namespace std;

//Macro definition
#define SLOT(a)     #a
#define SIGNAL(a)   #a
#define cpp_slots
#define cpp_signals protected
#define cpp_emit

class Object;
//Meta object system is responsible for collecting signals and slot names
struct MetaObject
{
    //Signal group
    const char * signal;
    //Slot group
    const char * slot;
    //Activate a signal and idx is the signal index
    static void active(Object * sender, int idx);
};

//Connected object information
struct Connection
{
    Object * receiver;//Receiver of signal
    int method;//Slot function index
};

//Save signal index and connection object mapping
typedef std::multimap<int, Connection> ConnectionMap;
typedef std::multimap<int, Connection>::iterator ConnectionMapIt;

//The index lookup function of signal and slot returns the index of signal or slot
static int find_string(const char * str, const char * substr)
{
    if (strlen(str) < strlen(substr))
        return -1;
    int idx = 0;
    int len = strlen(substr);
    bool start = true;
    const char * pos = str;
    while (*pos)
    {
        if (start && !strncmp(pos, substr, len) && pos[len] == '\n')
            return idx;

        start = false;
        if (*pos == '/n')
        {
            idx++;
            start = true;
        }
        pos++;
    }

    return -1;
}

class Object
{
    static MetaObject meta;//Static meta object declaration
    void metacall(int idx);//Declare meta method call function
public:
    Object()
    {
    }
    //Establish connection
    static void cpp_connect(Object* sender, const char* sig, Object* receiver, const char* slt)
    {
        cout << "connecting a signal to slot..." << endl;
        //Check whether signals and slots exist from the meta object data table
        int sig_idx = find_string(sender->meta.signal, sig);
        int slt_idx = find_string(receiver->meta.slot, slt);
        //If no signal or slot is found
        if (sig_idx == -1 || slt_idx == -1)
        {
            perror("signal or slot not found!");
        }
        else
        {
            //Create a connection to store the index of the receiver and slot function
            Connection c = { receiver, slt_idx };
            cout << "add a signal index and an Connection of receiver to sender's Connection map..." << endl;
            //The index of the signal and the information of the receiver are stored in the map container of the signal transmitter
            sender->connections.insert(std::pair<int, Connection>(sig_idx, c));
            cout << "connected success." << endl;
        }
    }

    void emitSignal()//Public test function, send a signal
    {
        cout << "emiting a signal..." << endl;
        cpp_emit valueChanged();
    }

cpp_signals:
    void valueChanged();//Signal declaration
public cpp_slots:
    void onValueChanged()//Slot function
    {
        cout << "Value Changed."<< endl;
    }
    friend class MetaObject;
private:
    ConnectionMap connections;//Connection key value pair
};

#endif // OBJECT_H

moc_Object.cpp implementation:

#include "Object.h"


//Name of signal
static const char signalNames[] = "valueChanged\n";

//Name of the slot
static const char slotNames[] = "onValueChanged\n";

//Filling of static meta objects
MetaObject Object::meta = { signalNames, slotNames };

//The implementation of meta method calling function calls back the slot function according to the connected index
void Object::metacall(int idx)
{
    switch (idx) {
    case 0:
        onValueChanged();
        break;
    default:
        break;
    };
}

//Realization of signal
void Object::valueChanged()
{
    MetaObject::active(this, 0);
}

//Activation signal
void MetaObject::active(Object* sender, int idx)
{
    ConnectionMapIt it;
    std::pair<ConnectionMapIt, ConnectionMapIt> ret;
    ret = sender->connections.equal_range(idx);
    for (it = ret.first; it != ret.second; ++it)
    {
        Connection c = (*it).second;
        c.receiver->metacall(c.method);//Call meta method based on index
    }
}

2. Analog use of signal slot

Main.cpp file:

#include <iostream>
#include "Object.h"

using namespace std;

int main(int argc, char *argv[])
{
    char p[32] = SLOT(Object);
    cout << "cur_value: " << p << endl;
    Object obj1, obj2;

    //Connecting signal and slot
    Object::cpp_connect(&obj1, SLOT(valueChanged), &obj2, SIGNAL(onValueChanged));

    //Send a signal for testing
    obj1.emitSignal();

    getchar();

    return 0;
}

4, Open source implementation of signal slot

1,sigslot

Sigslot is a very refined C + + implementation of signal slot. The author is Sarah Thompson. Sigslot implementation has only one header file sigslot h. Cross platform and thread safe. In WebRTC, sigslot H is its basic event processing framework, which is used in message notification and response processing of multiple modules.

sigslot library official website:

sigslot - C++ Signal/Slot Library

The use examples of siglot are as follows:

#include "sigslot.h"
#include <string>
#include <stdio.h>
#include <iostream>
#include <windows.h>

using namespace sigslot;

using namespace std;

class CSender
{
public:
    sigslot::signal2<string, int> m_pfnsigDanger;

    void Panic()
    {
        static int nVal = 0;
        char szVal[20] = { 0 };
        sprintf_s(szVal,20, "help--%d", nVal);
        m_pfnsigDanger(szVal, nVal++);
    }
};

class CReceiver :public sigslot::has_slots<>
{
public:
    void OnDanger(string strMsg, int nVal)
    {
        //printf("%s ==> %d", strMsg.c_str(), nVal);
        cout << strMsg.c_str() << " ==> " << nVal << endl;
    }
};

int main()
{
    CSender sender;
    CReceiver recever;
    cout << "create object ok..." << endl;  
    sender.m_pfnsigDanger.connect(&recever, &CReceiver::OnDanger); 
    cout << "connect succ!" << endl;
    while (1)
    {
        cout << "in while..." << endl;
        sender.Panic();
        Sleep(2000);
        cout << "end of sleep" << endl;
    }

    return 0;
}

If sigslot is used in Qt project h,sigslot. The name of the emit function in h will conflict with the emit macro in Qt. There are two ways to modify it. One is to change sigslot H's emit is changed to other names. The second is in Add definitions + = Qt in pro file_ NO_ Emit, disable Qt's emit macro.

2,Boost.Signals

Boost.Signals implements the signals/slots mode, signals are transmitted, and slots receive the signal.

#include <iostream>
#include "boost/signals.hpp"

void firstSlot() {
  std::cout << "void firstSlot()";
}

class secondSlot {
public:
  void operator()() const {
    std::cout <<
      "void secondSlot::operator()() const ";
  }
};

int main() 
{
  boost::signal<void ()> sig;
  sig.connect(&firstSlot);
  sig.connect(secondSlot());

  std::cout << "Emitting a signal... ";
  sig();
}

The execution order of slot functions is random, and the call order can be controlled by grouping parameters.

sig.connect(1,&firstSlot);

sig.connect(2,secondSlot());

3. Difference between Qt signal slot implementation and Boost signal slot implementation

Boost.Signals

Qt Signals and Slots

A signal is an object

Signals can only be member functions

Signaling is similar to a function call

Signaling is similar to a function call. Qt provides an emit keyword to complete this operation

Signals can be global, local, or member objects

Signals can only be member functions

Any code that can access the signal object can send a signal

Only the owner of the signal can send a signal

A slot is any function or function object that can be called

Slots are specially designed member functions

You can have a return value, which can be used in multiple slots

no return value

Synchronous

Synchronized or queued

Non thread safe

Thread safe, can be used across threads

When and only when the slot is traceable and the slot is destroyed, the connection is automatically disconnected

When a slot is destroyed, the connection is automatically disconnected (because all slots are traceable)

Type safety (compiler check)

Type safety (check during operation)

The parameter list must be identical

The slot can ignore redundant parameters in the signal

Signals and slots can be templates

Signals and slots cannot be templates

C + + direct implementation

Through the meta object generated by moc (moc and meta object system are directly implemented by C + +)

There is no introspection mechanism

It can be found through introspection

Can be called through a meta object

Connections can be automatically inferred from resource files

Topics: Qt