QT源码剖析-QT对象通信机制信号槽的绑定具体实现

2023-11-09

本文详细介绍QT核心机制之一 信号和槽。

我们在此根据Qt源代码一步一步探究其信号槽的实现过程。

核心知识点:

模板元编程技术、 Qt moc预编译机制、 QObject类。

目录

1. QObject类介绍:

2: 相关助手类介绍:

2.1 类型(函数指针)萃取模板元函数。

2.2 函数调用器FunctorCall元函数

3. 信号槽需要Qt moc预编译器的支持

4. 信号槽绑定入口: QObject类的静态模板方法(connect方法)

4.1 从入口开始谈起,先看看QObject类的connect 静态模板方法对于信号和槽绑定的声明及其实现。

5. 信号发送(调用 emit)

6. 总结


1. QObject类介绍:

QObject类是整个Qt框架对象的核心模型,此类提供了大量Qt特性。包括对象的无缝通信机制信号槽、反射、动态属性、定时、对象树生命周期管理等。

2: 相关助手类介绍:

信号槽的本质是两个对象的函数地址映射,下面对信号槽实现过程中的一些助手类进行介绍。

2.1 类型(函数指针)萃取模板元函数。

注:下文中会把类型萃取模板元函数称之为类型萃取器。

FunctionPointer萃取器声明部分(Qt 信号槽绑定过程中会大量使用此萃取器来提取信号槽中的相关类型)

FunctionPointer偏特化版本之一:非CV的成员函数指针类型偏特化版本。

文中仅贴出此偏特化版本。为保证篇幅不至于太长,其他偏特化版本不做介绍基本大同小异。

从图中可以看出,FunctionPointer非CV类的成员函数指针类型偏特化版本主要做以下事情。

a. 提取类成员函数指针类型所属的类类型。b. 提取类成员函数指针类型函数形参参数包类型(形参列表打包到List模板元函数中)。

c. 提取类成员函数指针返回值类型。d. 重定义类成员函数指针类型。

e. 提取类成员函数指针类型的形参数量和是否为类的成员函数枚举值。

f. 提供一个此函数指针类型的分发调用静态方法。

2.2 函数调用器FunctorCall元函数

看看2.1 节萃取器会发现类型萃取包装的call分发函数会转而调用此元函数。(包装主要原因是解信号形参数量参数包)

此元函数的作用是调用指定对象o的成员函数f, 并解包信号函数形参数量参数包。

先记得这两个助手类,信号槽绑定与信号发射过程会大量使用到。

3. 信号槽需要Qt moc预编译器的支持

此处不对Qt moc预编译机制做详细介绍,只是谈谈它做了哪些事。 Qt moc详细实现原理单独博文在介绍。

首先我们看看moc预编译器对包含Q_OBJECT宏的类做了哪些事件。

举个栗子 定义一个TestObject类:

 

图中源码代码中Q_OBJECT宏必须添加,Qt moc预编译器根据此标识在编译前来生成moc_xx.cpp 实现文件。

编译后产生如下文件:

moc_testobject.cpp

void TestObject::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast<TestObject *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->sig_void(); break;
        case 1: _t->sig_int((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 2: _t->sig_char((*reinterpret_cast< char(*)>(_a[1]))); break;
        case 3: _t->sig_qstring((*reinterpret_cast< QString(*)>(_a[1]))); break;
        case 4: _t->sig_int_char((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< char(*)>(_a[2]))); break;
        case 5: _t->slot1(); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        {
            using _t = void (TestObject::*)();
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_void)) {
                *result = 0;
                return;
            }
        }
        {
            using _t = void (TestObject::*)(int );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_int)) {
                *result = 1;
                return;
            }
        }
        {
            using _t = void (TestObject::*)(char );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_char)) {
                *result = 2;
                return;
            }
        }
        {
            using _t = void (TestObject::*)(QString );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_qstring)) {
                *result = 3;
                return;
            }
        }
        {
            using _t = void (TestObject::*)(int , char );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&TestObject::sig_int_char)) {
                *result = 4;
                return;
            }
        }
    }
}

QT_INIT_METAOBJECT const QMetaObject TestObject::staticMetaObject = { {
    QMetaObject::SuperData::link<QObject::staticMetaObject>(),
    qt_meta_stringdata_TestObject.data,
    qt_meta_data_TestObject,
    qt_static_metacall,
    nullptr,
    nullptr
} };


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

void *TestObject::qt_metacast(const char *_clname)
{
    if (!_clname) return nullptr;
    if (!strcmp(_clname, qt_meta_stringdata_TestObject.stringdata0))
        return static_cast<void*>(this);
    return QObject::qt_metacast(_clname);
}

int TestObject::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 < 6)
            qt_static_metacall(this, _c, _id, _a);
        _id -= 6;
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
        if (_id < 6)
            *reinterpret_cast<int*>(_a[0]) = -1;
        _id -= 6;
    }
    return _id;
}

// SIGNAL 0
void TestObject::sig_void()
{
    QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}

// SIGNAL 1
void TestObject::sig_int(int _t1)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

// SIGNAL 2
void TestObject::sig_char(char _t1)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
    QMetaObject::activate(this, &staticMetaObject, 2, _a);
}

// SIGNAL 3
void TestObject::sig_qstring(QString _t1)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
    QMetaObject::activate(this, &staticMetaObject, 3, _a);
}

// SIGNAL 4
void TestObject::sig_int_char(int _t1, char _t2)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))), const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t2))) };
    QMetaObject::activate(this, &staticMetaObject, 4, _a);
}

 

看到这个文件我想会打消这样一个疑问:为什么声明了信号函数自己没有实现编译器确没有报错?

这是因为Qt moc预编译器在编译器介入之前已经帮你做好了这件事。

moc_testobject.cpp 还定义了一些函数如qt_static_metacall、metaobject、qt_metacast、qt_metacall 为什么定义这些函数?

我们接着来看TestObject类声明中的Q_OBJECT宏内容:

: O_OBJECT 宏声明了与moc_testobject.cpp中完全匹配的函数声明部分。

可以看出moc预编译器会在编译时期检查类的Q_OBJECT宏自动生成实现文件。

这套机制为QT实现反射与信号槽提供了支持。

4. 信号槽绑定入口: QObject类的静态模板方法(connect方法)

4.1 从入口开始谈起,先看看QObject类的connect 静态模板方法对于信号和槽绑定的声明及其实现。

我们使用下面示例demo来开始进入Qt 信号槽的绑定的过程。

绑定信号过程与调用信号代码:

首先connect方法作为信号与槽的绑定入口函数,形参与特定类型无关。符合通用基础库的一般设计与使用流程。

 函数形参解析:

connect方法模板类型Func1、Func2由实参(信号函数地址与槽函数地址)自动推导。

parameter1: FuntionPoniter 萃取器获取Func1成员函数指针所在的类类型对象指针。(信号所在的对象)

parameter2:Func1信号成员函数指针

parameter3: FuntionPoniter 萃取器获取Func2成员函数指针所在的类类型对象指针。(槽函数所在的对象)

parameter4: Func2槽成员函数指针。

parameter5: 信号槽连接类型. (5种类型的信号槽连接区别可以自行查看文档,此处主要介绍信号槽绑定实现过程)

函数实现解析:

该函数主要是做了一些类型校验性工作,对信号成员函数指针与槽成员函数指针的形参数量、形参类型、返回值类型是否符合一致性要求以及类型是否匹配做检查。

槽函数地址交给QSlotObject对象, 具体的连接丢给connectImpl去实现。

至于检查规则有兴趣可以查看CheckCompatibleArguments与AreArgumentsCompatible模板元函数。

在调用connectImpl方法时传递的第四个实参为QSQlotObject对象,此对象包装了槽函数地址。QSQlotObject函数包装器的实现:

Connect函数在调用connectImpl方法对QSQlotObject模板类实例化时可以推导出 QSQlotObject模板参数类型。

Func对应槽成员函数指针类型, Args对应List类型( 函数形参类型列表,形参类型链表),R对应信号返回值类型。

QSlotObject 函数包装器实现比较简单,将自己的impl静态方法地址交由父类来管理。 本身保存槽成员函数指针。

1.3.2 connectImpl 信号槽绑定实现函数。

函数解析:

先对函数形参做个简要介绍, 相信看了图中函数的声明也基本能明白了。

分别为信号发送对象、指向信号函数地址指针、 响应对象、指向槽函数地址的指针、槽函数包装器对象、连接类型、类型数组、发送者元对象。

此函数主要作用是根据信号所在类的元对象找出信号索引位置(向上查找)。

我们可以继续深挖看看Qt是怎么去查找信号索引的。

senderMetaObject->static_metacall(QMetaObject::IndexOfMethod, 0, args);

args实参构造了一个int(待返回的信号索引 OUT)和信号函数地址(IN)的数组进行传入。

static_metacall实现为:

QMetaObject的唯一成员属性d的声明为:

到这里我们就需要知道的在什么地方怎样构造的QMetaObject的对象。

TestObject类声明中的Q_OBJECT宏展开后的第一条语句展为静态常量变量QMetaObject的声明(翻阅上文Q_OBJECT宏声明),

我们看看在moc_testobject.cpp中是怎么实例化这个对象的。

所以最终senderMetaObject调用static_metacall其实也就是对TestObject的qt_static_metacall的调用。

qt_static_metacall的定义在moc_testobject.cpp文件中:

可以回头看看qt_static_metacall函数的实现过程。

这里函数所做的事情基本就是根据传入进来的信号函数地址与本类中的所有信号函数地址进行匹配来获取需要绑定信号的索引了。

qt是默认按照信号函数声明的顺序从0开始给信号函数进行编号的。

最后得到的索引还需累加父类中所有信号的总数。

好了,信号索引的查找就介绍到这里。 我们继续来看看QObject的connectImpl函数对QObjectPrivate::connectImp的调用。

说到Qt中XXPrivate的对象需要说明的是Qt对类基本使用Pimpl惯用法。

Pimpl惯用法解释:

如果你曾经与过多的编译次数斗争过,你会对Pimpl(Pointer to implementation)惯用法很熟悉。

凭借这样一种技巧,你可以将一个类数据成员替换成一个指向包含具体实现的类或结构体的指针,

并将放在主类(primary class)的数据成员们移动到实现类去(implementation class), 而这些数据成员的访问将通过指针间接访问.(Effective Modern C++ item22 条款的解释)

个人认为这样做还有一个优点是主类对象和成员属性类对象内存分离,当主类发生拷贝构造时只是一个成员implementation class的地址拷贝这样拷贝构造的成本是极低的当然要注意浅拷贝问题(会不会高于移动构造效率呢?(#^.^#))。

这里QObjectPrivate就是对应QObject(主类)的implementation class。

这里继续探究QObjectPrivate::connectImp函数的实现:

QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int signal_index,
                                             const QObject *receiver, void **slot,
                                             QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
                                             const int *types, const QMetaObject *senderMetaObject)
{
    if (!sender || !receiver || !slotObj || !senderMetaObject) {
        const char *senderString = sender ? sender->metaObject()->className()
                                          : senderMetaObject ? senderMetaObject->className()
                                          : "Unknown";
        const char *receiverString = receiver ? receiver->metaObject()->className()
                                              : "Unknown";
        qWarning("QObject::connect(%s, %s): invalid null parameter", senderString, receiverString);
        if (slotObj)
            slotObj->destroyIfLastRef();
        return QMetaObject::Connection();
    }

    QObject *s = const_cast<QObject *>(sender);
    QObject *r = const_cast<QObject *>(receiver);

    QOrderedMutexLocker locker(signalSlotLock(sender),
                               signalSlotLock(receiver));

    if (type & Qt::UniqueConnection && slot && QObjectPrivate::get(s)->connections.loadRelaxed()) {
        QObjectPrivate::ConnectionData *connections = QObjectPrivate::get(s)->connections.loadRelaxed();
        if (connections->signalVectorCount() > signal_index) {
            const QObjectPrivate::Connection *c2 = connections->signalVector.loadRelaxed()->at(signal_index).first.loadRelaxed();

            while (c2) {
                if (c2->receiver.loadRelaxed() == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) {
                    slotObj->destroyIfLastRef();
                    return QMetaObject::Connection();
                }
                c2 = c2->nextConnectionList.loadRelaxed();
            }
        }
        type = static_cast<Qt::ConnectionType>(type ^ Qt::UniqueConnection);
    }

    std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};
    c->sender = s;
    c->signal_index = signal_index;
    QThreadData *td = r->d_func()->threadData;
    td->ref();
    c->receiverThreadData.storeRelaxed(td);
    c->receiver.storeRelaxed(r);
    c->slotObj = slotObj;
    c->connectionType = type;
    c->isSlotObject = true;
    if (types) {
        c->argumentTypes.storeRelaxed(types);
        c->ownArgumentTypes = false;
    }

    QObjectPrivate::get(s)->addConnection(signal_index, c.get());
    QMetaObject::Connection ret(c.release());
    locker.unlock();

    QMetaMethod method = QMetaObjectPrivate::signal(senderMetaObject, signal_index);
    Q_ASSERT(method.isValid());
    s->connectNotify(method);

    return ret;
}

函数实现关键代码段解析:

此函数基本就是信号和槽绑定过程中最底层函数了,函数最终返回的是一个QMetaObject::Connection连接对象。

函数形参到这里就不介绍,基本与上一层级的调用一致。 

函数开始处对参数有效性做检查。函数实现过程中是加锁了的, 可以得到信号槽的绑定是线程安全的。

函数中构造了QObjectPrivate::Connection独占共享指针(注意区分与QMetaObject::Connection不同)记录了信号地址与槽地址以及所在对象指针、连接类型、信号函数地址索引等信息。

接着获取信号函数所在对象的Private对象(implementation class)对QObjectPrivate的addConnection函数进行调用。 

addConnection函数形参为信号索引和QObjectPrivate::Connection指针。

addConnection函数实现为:

void QObjectPrivate::addConnection(int signal, Connection *c)
{
    Q_ASSERT(c->sender == q_ptr);
    ensureConnectionData();
    ConnectionData *cd = connections.loadRelaxed();
    cd->resizeSignalVector(signal + 1);

    ConnectionList &connectionList = cd->connectionsForSignal(signal);
    if (connectionList.last.loadRelaxed()) {
        Q_ASSERT(connectionList.last.loadRelaxed()->receiver.loadRelaxed());
        connectionList.last.loadRelaxed()->nextConnectionList.storeRelaxed(c);
    } else {
        connectionList.first.storeRelaxed(c);
    }
    c->id = ++cd->currentConnectionId;
    c->prevConnectionList = connectionList.last.loadRelaxed();
    connectionList.last.storeRelaxed(c);

    QObjectPrivate *rd = QObjectPrivate::get(c->receiver.loadRelaxed());
    rd->ensureConnectionData();

    c->prev = &(rd->connections.loadRelaxed()->senders);
    c->next = *c->prev;
    *c->prev = c;
    if (c->next)
        c->next->prev = &c->next;
}

此函数即为信号槽绑定关系的缓存部分了,介绍此函数前有必要贴下QObjectPrivate对于信号槽绑定的缓存数据结构进行介绍下:

由上图可知QObjectPrivate内部定义了多个用于存储信号槽绑定结构的内嵌类,类关系结构如上图所示。

上面QObjectPrivate::connectImp 函数中即构造了图中Connect对象并对相关属性进行了赋值最后传入到addConnect

SignVector: 信号向量 继承ConnectionOrSignalVector

Connection: 连接向量 继承ConnectionOrSignalVector

由它们的共同父类 ConnectionOrSignalVector 连接或信号向量可知他们都是分别存储信号与连接的容器。

也得知一个对象对于一个信号的多次绑定以及多个信号的绑定都是基于链表来存储的。

对照着此类关系图这里我们在一步步分段解析addConnection函数 。

1. 由于前面内容偏多,首先再次确认下当前QObjectPrivate对象属于发送者对象(QObject)的implementation class 对象。

2. 函数中ensureConnectionData函数功能为创建或者获取QObjectPrivate connections成员属性。(见上类UML图 QObjectPrivate类属性)

3. cd->resizeSignalVector(signal + 1) 构造或重置(扩容)SignalVertor对象。(此SignalVertor对象会按照8的倍数分配ConnectionList需要的存储空间加自己本身大小)

构造分配SignalVertor对象源码为:

        void resizeSignalVector(uint size) {
            SignalVector *vector = this->signalVector.loadRelaxed();
            if (vector && vector->allocated > size)
                return;
            size = (size + 7) & ~7;
            SignalVector *newVector = reinterpret_cast<SignalVector *>(malloc(sizeof(SignalVector) + (size + 1) * sizeof(ConnectionList)));
            int start = -1;
            if (vector) {
                memcpy(newVector, vector, sizeof(SignalVector) + (vector->allocated + 1) * sizeof(ConnectionList));
                start = vector->count();
            }
            for (int i = start; i < int(size); ++i)
                newVector->at(i) = ConnectionList();
            newVector->next = nullptr;
            newVector->allocated = size;

            signalVector.storeRelaxed(newVector);
            if (vector) {
                vector->nextInOrphanList = orphaned.loadRelaxed();
                orphaned.storeRelaxed(ConnectionOrSignalVector::fromSignalVector(vector));
            }
        }

4.  ConnectionList &connectionList = cd->connectionsForSignal(signal) 获取当前信号索引所在的ConnectionList对象。

我们在上文中知道每个信号发送对象的sign_index 都是唯一的包括父类,所以在QObjectPirvate对象connects成员属性signalVertor容器中都存在一份永久对应的ConnectionList对象。

5. 

    ConnectionList &connectionList = cd->connectionsForSignal(signal);
    if (connectionList.last.loadRelaxed()) {
        Q_ASSERT(connectionList.last.loadRelaxed()->receiver.loadRelaxed());
        connectionList.last.loadRelaxed()->nextConnectionList.storeRelaxed(c);
    } else {
        connectionList.first.storeRelaxed(c);
    }

获取当前信号索引所对应的ConnectionList对象并把实参c(QObjectPrivate::Connection)对象赋值给ConnectionList对象的first或者last的nextConnectionList属性(至于给谁就看该信号是不是第一次绑定了 ConnectList 的first给到第一次信号槽绑定形成的Connection对象, 后续last会给到最后一次绑定的Connection对象, Connection内部形成一个双向Connection链表)

6. 

    c->id = ++cd->currentConnectionId;
    c->prevConnectionList = connectionList.last.loadRelaxed();
    connectionList.last.storeRelaxed(c);

对当前连接对象c的连接id进行累加计数,并把上次连接到该信号的Connection绑定到此连接对象prevConnectionList中,形成一个链表。

结合5、6我们知道当前连接对象需要绑定到上一个连接对象Connection prevConnectionList上, 当前连接对象的prevConnectionList 需绑定到上一个连接对象形成一个双向链表。

7.

    QObjectPrivate *rd = QObjectPrivate::get(c->receiver.loadRelaxed());
    rd->ensureConnectionData();

    c->prev = &(rd->connections.loadRelaxed()->senders);
    c->next = *c->prev;
    *c->prev = c;
    if (c->next)
        c->next->prev = &c->next;

此处为addConnection函数结尾部分,获取信号接收对象的QObjectPrivate对象。

 

文字解释确实比较绕,还是上一个最终信号槽绑定连接对象在内存中的布局图。

QObjectPrivate成员属性ConnectionData signalVector* 如图中所示: 指向resizeSignalVector创建的内存块。(connectionList 内存块默认是8的倍数,图中并不准确)

每个信号按照索引序号分配好ConnectList固定内存块。

图中示例了第0个信号被绑定了3次。 图中ConnectList内存块只存在first 和last Connection。  中间last ... 表示中间态。新绑定槽函数都会更新last。

所以first指向第一个Connection对象,绑定第二个槽函数时, last指向第二个Connection对象,图中虚线表示。 当绑定第3个槽函数时,last继续更新指向第三个Connection对象。

Connection对象链表样式如图中下半部分显示。

Connect方法最终方法一个QObject::Connect对象。 QObject::Connect包含了QObjectPrivate::Connection指针。

5. 信号发送(调用)

示例代码中: emit to->sig_int(100);  其实emit写不写无所有,本身就是一个空宏。 只不过方面于字面上的理解而已。

对于信号的调用则是直接进入由Qt moc帮我们生成的实现文件中:

// SIGNAL 1
void TestObject::sig_int(int _t1)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

当发送sig_int信号时则进如上述代码段,首先构造_a void* 数组实参,取所有sig_int 参数地址放入到_a数组,数据0为位置始终用nullptr填充(至于原因我们后面再看)

接着继续调用QMetaObject::activate函数,形成分别为发送信号的对象地址、发送信号对象的QMetaObject对象地址、信号索引、信号参数数组。

我们继续观察QMetaObject::activate 实现代码:

void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
                           void **argv)
{
    int signal_index = local_signal_index + QMetaObjectPrivate::signalOffset(m);

    if (Q_UNLIKELY(qt_signal_spy_callback_set.loadRelaxed()))
        doActivate<true>(sender, signal_index, argv);
    else
        doActivate<false>(sender, signal_index, argv);
}

此函数唯一做的事情是获取信号最终索引(包含父类的信号偏移)

紧接着交由doActivate去做。

doActivate实现代码:

template <bool callbacks_enabled>
void doActivate(QObject *sender, int signal_index, void **argv)
{
    QObjectPrivate *sp = QObjectPrivate::get(sender);

    if (sp->blockSig)
        return;

    Q_TRACE_SCOPE(QMetaObject_activate, sender, signal_index);

    if (sp->isDeclarativeSignalConnected(signal_index)
            && QAbstractDeclarativeData::signalEmitted) {
        Q_TRACE_SCOPE(QMetaObject_activate_declarative_signal, sender, signal_index);
        QAbstractDeclarativeData::signalEmitted(sp->declarativeData, sender,
                                                signal_index, argv);
    }

    const QSignalSpyCallbackSet *signal_spy_set = callbacks_enabled ? qt_signal_spy_callback_set.loadAcquire() : nullptr;

    void *empty_argv[] = { nullptr };
    if (!argv)
        argv = empty_argv;

    if (!sp->maybeSignalConnected(signal_index)) {
        // The possible declarative connection is done, and nothing else is connected
        if (callbacks_enabled && signal_spy_set->signal_begin_callback != nullptr)
            signal_spy_set->signal_begin_callback(sender, signal_index, argv);
        if (callbacks_enabled && signal_spy_set->signal_end_callback != nullptr)
            signal_spy_set->signal_end_callback(sender, signal_index);
        return;
    }

    if (callbacks_enabled && signal_spy_set->signal_begin_callback != nullptr)
        signal_spy_set->signal_begin_callback(sender, signal_index, argv);

    bool senderDeleted = false;
    {
    Q_ASSERT(sp->connections.loadAcquire());
    QObjectPrivate::ConnectionDataPointer connections(sp->connections.loadRelaxed());
    QObjectPrivate::SignalVector *signalVector = connections->signalVector.loadRelaxed();

    const QObjectPrivate::ConnectionList *list;
    if (signal_index < signalVector->count())
        list = &signalVector->at(signal_index);
    else
        list = &signalVector->at(-1);

    Qt::HANDLE currentThreadId = QThread::currentThreadId();
    bool inSenderThread = currentThreadId == QObjectPrivate::get(sender)->threadData.loadRelaxed()->threadId.loadRelaxed();

    // We need to check against the highest connection id to ensure that signals added
    // during the signal emission are not emitted in this emission.
    uint highestConnectionId = connections->currentConnectionId.loadRelaxed();
    do {
        QObjectPrivate::Connection *c = list->first.loadRelaxed();
        if (!c)
            continue;

        do {
            QObject * const receiver = c->receiver.loadRelaxed();
            if (!receiver)
                continue;

            QThreadData *td = c->receiverThreadData.loadRelaxed();
            if (!td)
                continue;

            bool receiverInSameThread;
            if (inSenderThread) {
                receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();
            } else {
                // need to lock before reading the threadId, because moveToThread() could interfere
                QMutexLocker lock(signalSlotLock(receiver));
                receiverInSameThread = currentThreadId == td->threadId.loadRelaxed();
            }


            // 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_index, c, argv);
                continue;
#if QT_CONFIG(thread)
            } else if (c->connectionType == Qt::BlockingQueuedConnection) {
                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;
                {
                    QBasicMutexLocker locker(signalSlotLock(sender));
                    if (!c->receiver.loadAcquire())
                        continue;
                    QMetaCallEvent *ev = c->isSlotObject ?
                        new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore) :
                        new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction,
                                           sender, signal_index, argv, &semaphore);
                    QCoreApplication::postEvent(receiver, ev);
                }
                semaphore.acquire();
                continue;
#endif
            }

            QObjectPrivate::Sender senderData(receiverInSameThread ? receiver : nullptr, sender, signal_index);

            if (c->isSlotObject) {
                c->slotObj->ref();

                struct Deleter {
                    void operator()(QtPrivate::QSlotObjectBase *slot) const {
                        if (slot) slot->destroyIfLastRef();
                    }
                };
                const std::unique_ptr<QtPrivate::QSlotObjectBase, Deleter> obj{c->slotObj};

                {
                    Q_TRACE_SCOPE(QMetaObject_activate_slot_functor, obj.get());
                    obj->call(receiver, argv);
                }
            } else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
                //we compare the vtable to make sure we are not in the destructor of the object.
                const int method_relative = c->method_relative;
                const auto callFunction = c->callFunction;
                const int methodIndex = (Q_HAS_TRACEPOINTS || callbacks_enabled) ? c->method() : 0;
                if (callbacks_enabled && signal_spy_set->slot_begin_callback != nullptr)
                    signal_spy_set->slot_begin_callback(receiver, methodIndex, argv);

                {
                    Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, methodIndex);
                    callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv);
                }

                if (callbacks_enabled && signal_spy_set->slot_end_callback != nullptr)
                    signal_spy_set->slot_end_callback(receiver, methodIndex);
            } else {
                const int method = c->method_relative + c->method_offset;

                if (callbacks_enabled && signal_spy_set->slot_begin_callback != nullptr) {
                    signal_spy_set->slot_begin_callback(receiver, method, argv);
                }

                {
                    Q_TRACE_SCOPE(QMetaObject_activate_slot, receiver, method);
                    QMetaObject::metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv);
                }

                if (callbacks_enabled && signal_spy_set->slot_end_callback != nullptr)
                    signal_spy_set->slot_end_callback(receiver, method);
            }
        } while ((c = c->nextConnectionList.loadRelaxed()) != nullptr && c->id <= highestConnectionId);

    } while (list != &signalVector->at(-1) &&
        //start over for all signals;
        ((list = &signalVector->at(-1)), true));

        if (connections->currentConnectionId.loadRelaxed() == 0)
            senderDeleted = true;
    }
    if (!senderDeleted) {
        sp->connections.loadRelaxed()->cleanOrphanedConnections(sender);

        if (callbacks_enabled && signal_spy_set->signal_end_callback != nullptr)
            signal_spy_set->signal_end_callback(sender, signal_index);
    }
}

函数关键代码段解析:

此函数比较长,主要功能相信大家也能猜出来了

1.  根据信号索引检查该信号是否存在连接。

if (!sp->maybeSignalConnected(signal_index)) {
        // The possible declarative connection is done, and nothing else is connected
        if (callbacks_enabled && signal_spy_set->signal_begin_callback != nullptr)
            signal_spy_set->signal_begin_callback(sender, signal_index, argv);
        if (callbacks_enabled && signal_spy_set->signal_end_callback != nullptr)
            signal_spy_set->signal_end_callback(sender, signal_index);
        return;
    }

看看 maybeSignalConnected实现代码。

bool QObjectPrivate::maybeSignalConnected(uint signalIndex) const
{
    ConnectionData *cd = connections.loadRelaxed();
    if (!cd)
        return false;
    SignalVector *signalVector = cd->signalVector.loadRelaxed();
    if (!signalVector)
        return false;

    if (signalVector->at(-1).first.loadAcquire())
        return true;

    if (signalIndex < uint(cd->signalVectorCount())) {
        const QObjectPrivate::Connection *c = signalVector->at(signalIndex).first.loadAcquire();
        return c != nullptr;
    }
    return false;
}

此函数实现过程就可以看出检查过程实际就是判断SignalVertor容器中指定sign_index位置的ConnectionList first是否为nullptr。

2. 取出指定信号索引处的connectionList对象

    QObjectPrivate::ConnectionDataPointer connections(sp->connections.loadRelaxed());
    QObjectPrivate::SignalVector *signalVector = connections->signalVector.loadRelaxed();

    const QObjectPrivate::ConnectionList *list;
    if (signal_index < signalVector->count())
        list = &signalVector->at(signal_index);
    else
        list = &signalVector->at(-1);

3.  循环调用绑定在此信号上的槽函数

4. 示例sample 真正的调用槽函数处

obj即为槽函数包装器器 (QSlotObjectBase) 这些数据都可以在Connection连接对象中找到,绑定过程中有存储。

这里在看看对QSlotObjectBase的调用,传入了接收对象和信号发射时传入的参数。

我们知道这个QSlotObjectBase对象实在QObject::Connect构造的,m_impl对应的实际时子类QSlotObject的静态函数impl函数。

上文有贴过QSlotObject模板类的实现代码。

到了这里回看第一节的助手类发现最终只是由元函数进行了下转发调用 ,最终调用到指定接收对象的指定成员方法。

6. 总结

6.1 本文只对sample代码走向进行了梳理,信号连接类型为AutoConnection且信号槽没有跨线程调用。 对于信号类型的使用和跨线程调用可以结合上文源码流程看看Qt针对于其他分支的处理。

6.2 相信看完本文,也会发现QT信号槽的绑定其实并不是特别复杂。 只是加入了模板元编程、MOC预编译机制、以及内部大量的引用计数和原子对象增加了阅读源码的复杂性。

6.3 看完Qt 信号槽的绑定过程后,是否可以不写Q_OBJECT宏也能让QObject子类来支持信号槽绑定?

6.4 是否可以实现一个对指定对象的信号拦截,来实现无侵入式的代码编写呢 ?

6.5 理解完会发现信号槽的绑定也离不开Qt的反射机制,Qt大部分新的特性都是相互结合使用的。

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QT源码剖析-QT对象通信机制信号槽的绑定具体实现 的相关文章

  • 无法初始化静态QList?

    我收到以下错误 Cube cpp 10 error expected initializer before lt lt token 以下是头文件的重要部分 ifndef CUBE H define CUBE H include
  • 在 QtCreator 中查看数组内容

    调试时是否可以在 Qt Creator 中查看数组的内容 似乎检测到我的数组是一个数组而不是一个指针 此外 我可以点击一个箭头 就像展开一样 但之后什么也没有显示 当我试穿的时候std vector Qt Creator 设法按预期显示内容
  • 如何设置 Xcode 来代替 Qt Creator 工作?

    我不使用 Qt Creator 的 UI 设计功能 对于一个新项目 我想体验一下使用 Xcode 的工作 这将是一个常规的 Qt 项目 使用 C 和 Qt 库开发 就像在 Qt Creator 中一样 我没有使用 OS X 尤其是 Xcod
  • 异步设计中如何知道哪个QNetworkReply属于QNetworkRequest?

    我可以轻松地用 C 进行异步设计 HttpResponseMessage response await httpClient GetAsync InputAddress Text run when request finished And
  • 删除 QComboBox“下拉”动画

    我正在使用 Qt 4 8 并且想在单击 QComboBox 时摆脱 下拉 动画 我也想稍微移动一下 到目前为止 我一直在考虑重新实现 showPopup 和 hidePopup 但不知道如何使其工作 此外 每次我尝试使用 CSS 进行移动或
  • 将项目添加到自定义组件的布局

    我有一个习惯Footer Component我想在 QML 应用程序的不同位置重用它 Rectangle color gold height 50 anchors bottom parent bottom left parent left
  • 获取小部件的背景颜色 - 真的

    我无法获取小部件的实际背景颜色 在我的特殊情况下 我在使用 QTabWidget 中的小部件时遇到问题 这是在Windows7上 因此 经典的小部件有一些灰色背景 而选项卡内的小部件通常用白色背景绘制 I tried def bgcolor
  • 对齐坐标系

    Let s say I have 2 coordinate systems as it is shown in image attached 如何对齐这个坐标系 我知道我需要将第二个坐标系围绕 X 平移 180 度 然后将其平移到第一个坐标
  • cx_freeze:QODBC 驱动程序未加载

    我的 python 应用程序如下所示 test py from PyQt4 import QtCore from PyQt4 import QtGui from PyQt4 import QtSql import sys import at
  • Qt GUI 编程设计

    我正在尝试创建一个 GUI 应用程序 主窗口 一个QMainWindow 包含 9 个固定大小的标签以及主窗口的大小 我尝试在没有 Qt GUI Designer 的情况下以编程方式制作它 该项目构建时没有错误 但我看不到主窗口上显示的任何
  • Qt 计算和比较密码哈希

    目前正在 Qt 中为测验程序构建面向 Web 的身份验证服务 据我了解 在数据库中存储用户密码时 必须对其进行隐藏 以防落入坏人之手 流行的方法似乎是添加的过程Salt https en wikipedia org wiki Salt cr
  • 在 QtCreator 中将 OpenCV 2.3 与 Qt 结合使用

    随着 OpenCV 2 3 版本终于发布 我想在我的系统上编译并安装这个最新版本 由于我经常使用 Qt 和 QtCreator 我当然希望能够在我的 Qt 项目中使用它 我已经尝试了几种方法几个小时 但总是出现错误 第一次尝试 使用WITH
  • QGraphicsView 和 eventFilter

    这个问题已经困扰我两天多了 所以我想我应该问一下 我在Win7上使用Qt 4 5 3 用VC2008编译 我有 MyGraphicsView 继承 QGraphicsView 和 MyFilter 继承 QObject 类 当我将 MyFi
  • Qt程序部署到多平台,如何?

    我是 Qt 编程新手 我想开发一个程序 我想在 Windows Linux ubuntu 和 Mac 上运行 听说Qt支持多平台应用程序开发 但我的问题是 在我部署或编译后 任何 Qt 库都需要在 Ubuntu 中运行这个应用程序吗 如果您
  • 有没有办法在没有 QApplication::exec() 的情况下使用 Qt?

    有没有一种安全的方法可以在不调用 QApplication exec 的情况下使用 Qt 我有许多不同的对象正在对多个资源执行长期进程 至少其中一个正在与 Web 应用程序服务器进行通信 我正在制作一个 GUI 应用程序 提示用户在正确的时
  • 程序意外完成 - QT Creator

    我正在尝试使用 QT Creator 使用 QT 框架开发 GUI 控制台应用程序 我使用的是Windows XP 我安装了QT 4 8 3和mingw 两者均已安装 没有任何错误 然后我安装了QT Creator QT 版本 路径中的 Q
  • 如何在按下托盘图标菜单操作时执行功能?

    int main int argc char argv QApplication oApp argc argv QAction action1 QMenu menu QSystemTrayIcon TrayIcon QIcon favico
  • (如何)我可以抑制未找到包配置文件的警告吗?

    我正在尝试创建一个CMakeLists txt尝试查找的文件Qt5 如果失败 则尝试回退到Qt4安装 该脚本到目前为止有效 但如果出现以下情况我总会收到警告Qt5未安装 注意FindQt5 cmake是由提供Qt5并且仅当以下情况时才可用Q
  • Qt:将拖放委托给子级的最佳方式

    我在 QWidget 上使用拖放 我重新实现了 DragEnterEvent dragLeaveEvent dragMoveEvent 和 dropEvent 效果很好 在我的 QWidget 中 我有其他 QWidget 子级 我希望它们
  • 禁用 QML Slider 的鼠标滚轮

    我希望能够滚动Flickable使用鼠标滚轮 或触摸板上的两根手指 不改变Sliders它可能包含 示例代码及结果应用 import QtQuick 2 7 import QtQuick Window 2 2 import QtQuick

随机推荐

  • Qt-认清信号槽的本质

    目录 放个目录方便预览 这个目录是从博客复制过来的 点击会跳转到博客 简介 猫和老鼠的故事 对象之间的通信机制 尝试一 直接调用 尝试二 回调函数 映射表 观察者模式 Qt的信号 槽 信号 槽简介 信号 槽分两种 信号 槽的实现 元对象编译
  • 数据研发面经——字节跳动

    数据研发面经 字节跳动 1 抽象类与接口 2 多态 3 四种引用 4 锁 并发怎么处理 5 进程和线程的区别 6 shuffle机制 mapreduce流程 7 JVM虚拟机 为什么需要虚拟机 8 内存区域 五部分 栈和堆区别 具体存放的东
  • 2017.10.9 DZY Loves Math VI 失败总结

    一看到love math就知道肯定不会做 首先lcm拆成i j gcd i j 然后就讨论分子和分母 但并没有什么卵用 这个题对比传统反演题 主要不同的是f函数不是很直观 所以如果枚举gcd 那剩下的两个数一定互质 然后就按照gcd 1的反
  • 思科VoIP配置清单(转)

    我配过简单的VoIP 用的是思科的设备 希望对你有用 R1接口为192 168 1 1 R2接口为192 168 1 2 R1 R2直连 并相互各连两部电话 要想实现互相通话 可以做如下配置 其中 5164765 6239560为连接R1的
  • C# 创建Excel并写入内容

    在许多应用程序中 需要将数据导出为Excel表格 以便用户可以轻松地查看和分析数据 在本文中 我们将讨论如何使用C 创建Excel表格 并将数据写入该表格 添加引用 在C 中创建Excel表格 需要使用Microsoft Office In
  • Typora和PicGo-Core搭配使用(解决博客单独上传图片问题)

    前言 本文简单介绍快速上传图片并获取图片 URL 链接的工具 图片存放到Gitee仓库中 在博客网站发布时不必担心图片转存失败问题 解决本地图片在网站需单独上传的难题 将本地图片存储在网络中 图床 并生成URL 联网情况下通过URL链接即可
  • Unity之六:项目实战篇

    文章目录 一 一个简单的实例 二 使用CMake组织项目与Unity 2 1 目录结构 2 2 CMakeLists txt的编写 2 3 使用实例 一 一个简单的实例 一个测试单元是源文件 测试文件和Unity构成的 把他们放在一起进行编
  • 【算法提升】——异或理解,位的运算

    个人主页 努力学习的少年 版权 本文由 努力学习的少年 原创 在CSDN首发 需要转载请联系博主 如果文章对你有帮助 欢迎关注 点赞 收藏 一键三连 和订阅专栏哦 目录 一 只出现一次的数字 1 二 数组中只出现一次的数字2 一 只出现一次
  • localStorage在Safari浏览器无痕模式下失效

    Safari无痕模式是不能使用localStorage的 可以利用这个特性判断用户是否开启无痕模式 并提醒用户关闭无痕模式 if typeof localStorage object try localStorage setItem loc
  • 学习笔记 JavaScript ES6 异步编程Promise

    Promise ES里面对异步操作的第一种方案 学习Promise 让异常操作变得优雅 Promise的精髓在于异步操作的状态管理 一个Promise最基本用法 他的参数是一个方法 这个方法里有两个参数 一个是异步操作执行成功的回调 一个是
  • DC综合脚本中文详细解释

    script for Design Compiler DC综合编译脚本 language TCL 语言说明 Usage 使用说明 1 make sure the lib in the current directory 确保设计库在正确的文
  • Xcode项目设置项中的LLVM

    LLVM是构架编译器
  • html5开发手机打电话发短信功能,html5的高级开发,html5开发大全,html手机电话短信功能详解

    原文地址 http blog csdn net xmtblog article details 32931905 在很多的手机网站上 有打电话和发短信的功能 对于这些功能是如何实现的呢 其实不难 今天我们就用html5来实现他们 简单的让你
  • Angular--官方文档之 Angular CLI

    学习Angular官方文档的时候 参考https angular cn guide quickstart 这个快速开发的文档 对于我这个AngularJs小白在看了Angular菜鸟教程后 只能说可以简单的运用一下 看到一些专业术语 我也是
  • 嵌入式Linux(四)—嵌入式C语言(杂项/数据类型关键字)

    目录 杂项关键字 sizeof Return 数据类型关键字 char 进制 int long short Unsigned signed Float double void 自定义数据类型 Struct Union enum typede
  • cppcheck使用

    cppcheck使用 cppcheck说明 cppcheck能够检查出来的问题 cppcheck使用并生成html结果 生成html结果 cppcheck说明 cppcheck主要用来检查c c 代码的 本文主要讲述cppcheck用命令行
  • Flutter 开发小结

    接触 Flutter 已经有一阵子了 期间记录了很多开发小问题 苦于忙碌没时间整理 最近项目进度步上正轨 借此机会抽出点时间来统一记录这些问题 并分享项目开发中的一点心得以及多平台打包的一些注意事项 希望能对大家有所帮助 UI 组件使用 官
  • Linux 下使用Crontab定时任务同时执行多条定时任务

    Linux 下使用Crontab定时任务同时执行多条定时任务 使用 符连接即可 示例如下 0 6 bea ceos timer bin pb ClosePbManifestTimer sh gt dev null 2 gt 1 bea ce
  • 【高项】质量管理(ITTO)

    过程组 子过程 输入 I 工具和技术 TT 输出 O 规划 1规划质量管理 1 项目章程 2 项目管理计划 需求管理计划 风险管理计划 相关方参与计划 范围基准 3 项目文件 假设日志 需求文件 需求跟踪矩阵 风险登记册 相关方登记册 4
  • QT源码剖析-QT对象通信机制信号槽的绑定具体实现

    本文详细介绍QT核心机制之一 信号和槽 我们在此根据Qt源代码一步一步探究其信号槽的实现过程 核心知识点 模板元编程技术 Qt moc预编译机制 QObject类 目录 1 QObject类介绍 2 相关助手类介绍 2 1 类型 函数指针