Qt信号与槽源码调用原理分析

一.MOC工具

moc是什么

moc全称Mete-Object-Compiler也就是“元对象编译器”,在bin/moc.exe。

moc的作用

Qt程序在进行标准编译流程之前先调用moc进行预编译对含有Q_OBJECT类工程源码进行解析,生成类的moc_xxx.cpp文件。然后再开始进行一般的编译流程。

image-20241108144617255

二.MOC生成的MOC_xxx.cpp文件

SIGNAL与SLOT宏

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

SIGNAL与SLOT宏会利用预编译器将一些参数转化成字符串,并且在前面添加上编码。

Q_OBJECT宏

先看我们qt程序用到信号和槽就必须有的Q_OBJECT宏

image-20241108145220465

忽略警告信息我们关注主要成员,178、179、180、181、185行依次为

  • 静态元数据

  • 返回静态元数据对象指针函数

  • 子类向父类转换函数

  • 调用槽函数

  • 实际调用槽函数

先看和Q_OBJECT宏有关的实现,我们在debug目录下找到该类的moc_xxx.cpp文件找到实现,我们来关注一下重要的东西

staticMetaObject对象信息

QT_INIT_METAOBJECT const QMetaObject HttpMgr::staticMetaObject = { {
    &QObject::staticMetaObject,
    qt_meta_stringdata_HttpMgr.data,
    qt_meta_data_HttpMgr,
    qt_static_metacall,
    nullptr,
    nullptr
} };

在connect的源码里的发送者的源对象找到是一个私有数据

image-20241108162436477

由此看来staticMetaObject是信号发送者对应的私有数据

metaObject()函数实现

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

这段代码通过 QObject::d_ptr 指针检查是否有动态元对象,如果有则返回动态元对象,否则返回静态元对象,就是返回元对象的

qt_metacast(const char *)函数实现

void *HttpMgr::qt_metacast(const char *_clname)
{
    if (!_clname) return nullptr;
    if (!strcmp(_clname, qt_meta_stringdata_HttpMgr.stringdata0))
        return static_cast<void*>(this);
    if (!strcmp(_clname, "Singleton<HttpMgr>"))
        return static_cast< Singleton<HttpMgr>*>(this);
    if (!strcmp(_clname, "std::enable_shared_from_this<HttpMgr>"))
        return static_cast< std::enable_shared_from_this<HttpMgr>*>(this);
    return QObject::qt_metacast(_clname);
}

作用为返回父类对象Q_OBJECT

qt_metacall(QMetaObject::Call, int, void **)函数实现

                                    //枚举        id       二级指针
int HttpMgr::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 < 5)
            //频繁与5对比,5代码信号和槽的总数
            qt_static_metacall(this, _c, _id, _a);
        _id -= 5;
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
        if (_id < 5)
            *reinterpret_cast<int*>(_a[0]) = -1;
        _id -= 5;
    }
    return _id;
}

主要调用qt_static_metacall对信号跟槽进行实际调用,并重新获取了信号或者槽的相对id

qt_static_metacall**(QObject o, QMetaObject::Call _c, int _id, void **a)函数实现

                  //基类指针           枚举         id为信号和槽的声明顺序                                       
void HttpMgr::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        HttpMgr *_t = static_cast<HttpMgr *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->sig_http_fisish((*reinterpret_cast< ReqId(*)>(_a[1])),(*reinterpret_cast< QString(*)>(_a[2])),(*reinterpret_cast< ErrorCodes(*)>(_a[3])),(*reinterpret_cast< Modules(*)>(_a[4]))); break;
        case 1: _t->sig_res_mod_finish((*reinterpret_cast< ReqId(*)>(_a[1])),(*reinterpret_cast< QString(*)>(_a[2])),(*reinterpret_cast< ErrorCodes(*)>(_a[3]))); break;
        case 2: _t->sig_reset_mod_finish((*reinterpret_cast< ReqId(*)>(_a[1])),(*reinterpret_cast< QString(*)>(_a[2])),(*reinterpret_cast< ErrorCodes(*)>(_a[3]))); break;
        case 3: _t->sig_login_mod_finish((*reinterpret_cast< ReqId(*)>(_a[1])),(*reinterpret_cast< QString(*)>(_a[2])),(*reinterpret_cast< ErrorCodes(*)>(_a[3]))); break;
        case 4: _t->slot_http_finish((*reinterpret_cast< ReqId(*)>(_a[1])),(*reinterpret_cast< QString(*)>(_a[2])),(*reinterpret_cast< ErrorCodes(*)>(_a[3])),(*reinterpret_cast< Modules(*)>(_a[4]))); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        {
            using _t = void (HttpMgr::*)(ReqId , QString , ErrorCodes , Modules );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&HttpMgr::sig_http_fisish)) {
                *result = 0;
                return;
            }
        }
        {
            using _t = void (HttpMgr::*)(ReqId , QString , ErrorCodes );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&HttpMgr::sig_res_mod_finish)) {
                *result = 1;
                return;
            }
        }
        {
            using _t = void (HttpMgr::*)(ReqId , QString , ErrorCodes );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&HttpMgr::sig_reset_mod_finish)) {
                *result = 2;
                return;
            }
        }
        {
            using _t = void (HttpMgr::*)(ReqId , QString , ErrorCodes );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&HttpMgr::sig_login_mod_finish)) {
                *result = 3;
                return;
            }
        }
    }
}

实现了对信号槽的调用,以及单独对信号的调用和对当前类对象的属性进行读写以及重置工作

qt_meta_stringdata_HttpMgr元数据表

struct qt_meta_stringdata_HttpMgr_t {
    QByteArrayData data[14];
    char stringdata0[143];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_HttpMgr_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_HttpMgr_t qt_meta_stringdata_HttpMgr = {
    {
QT_MOC_LITERAL(0, 0, 7), // "HttpMgr"
QT_MOC_LITERAL(1, 8, 15), // "sig_http_fisish"
QT_MOC_LITERAL(2, 24, 0), // ""
QT_MOC_LITERAL(3, 25, 5), // "ReqId"
QT_MOC_LITERAL(4, 31, 2), // "id"
QT_MOC_LITERAL(5, 34, 3), // "res"
QT_MOC_LITERAL(6, 38, 10), // "ErrorCodes"
QT_MOC_LITERAL(7, 49, 3), // "err"
QT_MOC_LITERAL(8, 53, 7), // "Modules"
QT_MOC_LITERAL(9, 61, 3), // "mod"
QT_MOC_LITERAL(10, 65, 18), // "sig_res_mod_finish"
QT_MOC_LITERAL(11, 84, 20), // "sig_reset_mod_finish"
QT_MOC_LITERAL(12, 105, 20), // "sig_login_mod_finish"
QT_MOC_LITERAL(13, 126, 16) // "slot_http_finish"

    },
    "HttpMgr\0sig_http_fisish\0\0ReqId\0id\0res\0"
    "ErrorCodes\0err\0Modules\0mod\0"
    "sig_res_mod_finish\0sig_reset_mod_finish\0"
    "sig_login_mod_finish\0slot_http_finish"
};

记录了该类的所有类信息

qt_meta_data_HttpMgr[]元数据索引表

static const uint qt_meta_data_HttpMgr[] = {

 // content:
       8,       // revision
       0,       // classname
       0,    0, // classinfo
       5,   14, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       0,       // flags
       4,       // signalCount

 // signals: name, argc, parameters, tag, flags
       1,    4,   39,    2, 0x06 /* Public */,
      10,    3,   48,    2, 0x06 /* Public */,
      11,    3,   55,    2, 0x06 /* Public */,
      12,    3,   62,    2, 0x06 /* Public */,

 // slots: name, argc, parameters, tag, flags
      13,    4,   69,    2, 0x08 /* Private */,

 // signals: parameters
    QMetaType::Void, 0x80000000 | 3, QMetaType::QString, 0x80000000 | 6, 0x80000000 | 8,    4,    5,    7,    9,
    QMetaType::Void, 0x80000000 | 3, QMetaType::QString, 0x80000000 | 6,    4,    5,    7,
    QMetaType::Void, 0x80000000 | 3, QMetaType::QString, 0x80000000 | 6,    4,    5,    7,
    QMetaType::Void, 0x80000000 | 3, QMetaType::QString, 0x80000000 | 6,    4,    5,    7,

 // slots: parameters
    QMetaType::Void, 0x80000000 | 3, QMetaType::QString, 0x80000000 | 6, 0x80000000 | 8,    4,    5,    7,    9,

       0        // eod
};

记录相应的索引

信号具体实现

image-20241110154152946

我们定义的信号由moc在此实现,以上就是moc_xxx.cpp的所有内容

总结

Q_OBJECT宏主要为这个类对象生成相应的元对象,保存了类中的所有信息,同时类转换为基类对象,也实现了部分槽函数的调用,总的来说就是提供了一些声明,而具体的实现是moc来生成的moc_xxx.cpp。

moc帮我们首先实现了信号的部分逻辑和实现以及为我们的类对象创建了其对应的静态元对象和获取方法并且实现了部分槽函数的调用逻辑。

image-20241108154835465

三.Connect链接

当我们使用connect进行信号和槽的连接后,qt源码帮我们实现了具体信号槽的存储逻辑、链接工作、以及触发机制

三种链接方式

标准链接

  1. 写法:connect(this,SIGNAL(singnal(int)),this,SLOT(slot(int)));
  2. 原理:将信号和槽的函数名称按照相应规则合成字符串,通过connect解析该字符串,分别获取信号槽的绝对索引,然后将信号槽链接添加到信号容器中。
  3. 注意事项:所有检查都在运行时,通过解析字符串进行,如果字符串拼写错误却编译成功,则创建的是空连接,在运行时报错。
  4. 特性:
    • 不可以调用普通成员函数
    • 支持重载信号和槽函数
  5. 链接类型:AutoConnection自动连接

实现原理:首先计算信号和槽函数的绝对索引,并将信号与槽函数组成相应链接,将此链接存放到信号所对应的私有元对象中的信号槽存储容器中的指定位置,完成存储并发送完成状态。

image-20241112175744802

函数指针

  1. 写法:connect(this,&TestObject::signal,this,&TestObject::slot);
  2. 原理:编译时检查,使用函数指针作为信号槽函数
  3. 注意事项:编译时如果拼写错误无法通过编译
  4. 特性:
    • 可以调用普通成员函数
    • 不支持重载信号函数无法识别
  5. 链接类型:AutoConnection自动连接

实现原理:传入信号与槽的函数指针,在一级调度中分别获取了他们的数据特性并存储到FunctionPointer数据对象中,之后对他们的数据特征进行检查,检查完成之后将信号与槽生成新的链接并生成对应的QSlotObject对象进行存储并发送给二级调度,二级调度对信号与槽进行检查然后获取信号的绝对索引,之后调用下一级调度。三级调度和标准链接步骤差不多。

image-20241112170953595

Lambda

  1. 语法:connect(this,&TestObject::signal,[=](const int & name){});
  2. 原理:编译时检查,使用函数指针作为信号槽函数
  3. 注意事项:编译时如果拼写错误无法通过编译
  4. 特性:
    • 支持槽函数重载
    • 不支持信号重载
  5. 链接类型:DirectCOnnect,且不可手动设置

实现原理:内部实现与函数指针基本等同,唯一区别在一级调度中对于信号槽的链接方式,函数指针使用的是自动链接,Lambda表达式用的是直接链接。然后是对于槽函数提取,函数指针使用的是QSlotObject对象,Lambda表达式使用的是QFuntorSlotObject对象,其他全部相同。

image-20241112172740933

五种链接类型

  • AutoConnecion自动连接:如果信号在接收者所依附的线程内发射,等同于直接连接,如果不同,等同于队列连接(invokeMethodlmpl)
  • DirectConnection直接连接:当信号发送时,槽函数被直接调用,不论槽函数所属对象在哪个线程,槽函数都在发射信号的线程内执行(这种方式不能跨线程传递消息)
  • QueuedConnection队列链接:当控制权回到接收者所依附的线程的事件循环时,槽函数被调用。槽函数在接收者所依附的线程执行(这种方式既可以在线程内传递消息,也能跨线程传递)
  • UniqueConnection独立链接:防止重复链接,如果当前信号和槽已经链接过了,则不在链接;
  • BlockingQueuedConnection阻塞队列链接:和QueuedConnection类似,但是发送消息后阻塞,直到等到关联的slot都被执行(说明他是专门用来多线程传递消息的,而且时阻塞类型)

信号与槽存储

QVector,以当前信号的绝对索引为下标对应一个带头的单向链表,单向链表中存储的是双向链表,双向链表中存储的是当前信号所有的信号槽链接,包括发送者的对象数据以及接收者的对象数据,每当有新的信号槽进行绑定,就会根据当前信号的绝对索引找到其对应的单向链表,然后创建双向链表将信号与槽的相应绑定存储到里面,然后挂载到当前信号的绝对索引对应的单向链表中。

image-20241113112406831

信号的触发

数据对象

image-20241113115330520

信号触发流程

信号的实现

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

emit->相应信号函数->activate()->获取发送者的连接链表容器->从发送者的连接链表容器中,使用信号索引作为索引,获取相应的链接->根据具体类型进行相应处理->将所需数据传入qt_static_metacall函数根据参数调用相应的槽函数。

Object::qt_metacall函数内部调用了Object::setAge函数,setAge内部调用Object::ageChanged信号函数,ageChanged信号函数内部调用了QMetaObject::activate函数,activate函数内部调用Object::qt_static_metacall函数,最终qt_static_metacall函数内部调用了槽函数onAgeChanged。

posted @ 2024-11-15 15:01  桂洛克船长  阅读(201)  评论(0)    收藏  举报