栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

QT信号槽实现原理源码

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

QT信号槽实现原理源码

一 、使用场景

我们使用QT的UI控件时,常用到触发控件的操作。
比如点击一个按钮,就进行一个什么操作。ui文件可以右击控件–转到槽来生成一个槽函数,很方便。
选择槽函数后,头文件里会多一个槽函数。
这是QT的 QObject函数的信号与槽功能,使得控件的点击为一个信号,点击后触发槽函数进行操作。
我们自己也可以在QObject类里写上Q_OBJECT,signals和slots来实现信号槽。
它实现的是
观察者模式:通知依赖关系,一个对象目标的状态发生改变,所有依赖对象( 观察者对象)都将得到通知。
观察者模式通过面向对象技术,弱化关系,形成稳定依赖,实现软件体系结构的松耦合。

QT的预处理-------MOC元对象编译器

信号槽需要使用QT的MOC实现,需要在类里写Q_OBJECT宏才能实现。
Qt程序在交由标准编译器(例如MSVC)编译之前,先使用moc(MOC, 元对象编译器 the Meta Object Compiler)分析cpp头文件;如果它发现在一个头文件中包含了Q_OBJECT宏,则会生成另外一个cpp源文件(moc_文件名.cpp),该cpp源文件中包含了Q_OBJECT宏的实现、运行时信息(反射)等。因此Qt程序的完整编译过程为moc->预处理->编译->链接

二 、信号与槽实现及源码

我们定义一个发送信号的sender发送者
发送信号operate(),

和一个接收槽函数的接收者receiver

信号 :当按钮(对象)改变状态时,信号就emit,对象只负责发送,不负责接收方的处理。
槽: 接收到信号,不关心信号。
信号与槽如何连接:
原来QT是通过QObject::connect静态函数建立连接;其中sender与receiver是指向对象的指针,分别代表了被观察者和 观察者。
signal与method分别通过SIGNAL()与SLOT()宏来进行转换 (类型是const char*)
他们通过 connect(this, &myclass::operate, worker, &Worker::doWork);绑定信号和槽函数。

宏定义 -moc文件

QObject类型有 signals slots Q_OBJECT emit SIGNAL SLOT 等信号槽相关的宏定义。

CTRL键+鼠标点击这些宏,进入qobjecdefs.h文件,这里定义了这些宏。


信号 moc预处理

首先 emit 信号 发送了什么?(注:emit是个空的宏)
moc_xxx.cpp文件中看到
发送者的信号函数里有 QMetaObject::activate

// SIGNAL 0
void Worker::resultReady(const QString & _t1)
{
    void *_a[] = { nullptr, const_cast(reinterpret_cast(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

其定义在

qobjectdefs.h文件里

    // internal index-based signal activation
    static void activate(QObject *sender, int signal_index, void **argv);
    static void activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv);
    static void activate(QObject *sender, int signal_offset, int local_signal_index, void **argv);
槽moc预处理

**槽函数呢 **在moc_xxx文件里 找到槽函数相关的函数 后面会讲如何和信号绑定 的如何触发 的。

void Worker::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->resultReady((*reinterpret_cast< const QString(*)>(_a[1]))); break;
        case 1: _t->doWork((*reinterpret_cast< const QString(*)>(_a[1]))); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast(_a[0]);
        {
            using _t = void (Worker::*)(const QString & );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&Worker::resultReady)) {
                *result = 0;
                return;
            }
        }
    }
}

发送者和接收者都继承QObject类,
重写了QObject::qt_metacall 调用了qt_static_metacall()
qt_static_metacall会将槽函数调用

int Worker::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
        if (_id < 2)
            qt_static_metacall(this, _c, _id, _a);
        _id -= 2;
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
        if (_id < 2)
            *reinterpret_cast(_a[0]) = -1;
        _id -= 2;
    }
    return _id;
}

QMetaObject 内容- -

QObject里有 静态成员变量
static const QMetaObject staticQtMetaObject;

moc文件里看出来预处理对应的worker 的QMetaObject 的成员变量是

QT_INIT_METAOBJECT const QMetaObject Worker::staticMetaObject = { {
    &QObject::staticMetaObject,
    qt_meta_stringdata_Worker.data,
    qt_meta_data_Worker,
    qt_static_metacall,
    nullptr,
    nullptr
} };

其中成员 qt_meta_stringdata_Worker.data,为

static const qt_meta_stringdata_Worker_t qt_meta_stringdata_Worker = {
    {
QT_MOC_LITERAL(0, 0, 6), // "Worker"
QT_MOC_LITERAL(1, 7, 11), // "resultReady"
QT_MOC_LITERAL(2, 19, 0), // ""
QT_MOC_LITERAL(3, 20, 7), // "result1"
QT_MOC_LITERAL(4, 28, 6), // "doWork"
QT_MOC_LITERAL(5, 35, 9) // "parameter"

    },
    "WorkerresultReadyresult1doWork"
    "parameter"
};

可以看出是Worker类的名称、信号名称、槽函数名称及参数等字符串。

connect有什么呢?
点击进去
connect函数是QObject类的成员函数 返回值是QMetaObject::Connection

    static QMetaObject::Connection connect(const QObject *sender, const char *signal,
                        const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);

    static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,
                        const QObject *receiver, const QMetaMethod &method,
                        Qt::ConnectionType type = Qt::AutoConnection);

    inline QMetaObject::Connection connect(const QObject *sender, const char *signal,
                        const char *member, Qt::ConnectionType type = Qt::AutoConnection) const;

所以QObject 里实现connect函数
QMetaObject 实现Connection 类是connect的返回类型

class Q_CORE_EXPORT QMetaObject::Connection {
    void *d_ptr; //QObjectPrivate::Connection*
    explicit Connection(void *data) : d_ptr(data) {  }
    friend class QObject;
    friend class QObjectPrivate;
    friend struct QMetaObject;
    bool isConnected_helper() const;
public:
    ~Connection();
    Connection();
    Connection(const Connection &other);
    Connection &operator=(const Connection &other);
#ifdef Q_QDOC
    operator bool() const;
#else
    typedef void *Connection::*RestrictedBool;
    operator RestrictedBool() const { return d_ptr && isConnected_helper() ? &Connection::d_ptr : nullptr; }
#endif

    Connection(Connection &&o) Q_DECL_NOTHROW : d_ptr(o.d_ptr) { o.d_ptr = nullptr; }
    Connection &operator=(Connection &&other) Q_DECL_NOTHROW
    { qSwap(d_ptr, other.d_ptr); return *this; }
};
三 、接下来把这些函数连接起来 思路:
  1. 定义两个类: sender(被观察者)和recver(观察者) 均继承于QObject

  2. recver(观察者),定义对某个处理函数比如按钮触发后处理函数。槽函数

  3. sender(被观察者),定义一个数据结构,保持观察者对哪个事件id感兴趣,使用map建立对应关系。并调用(运行时)槽函数。

  4. connect函数在QObject中可由 sender(被观察者)或recver(观察者) 调用

定义一个QObject类,有以下内容:
  1. qt_metacall调用观察者的槽函数用 虚函数重写形式实现 动态绑定实现 观察者模式的关键 在moc分析时将槽函数信号的信息写入 qt_static_metacall函数里 ,此函数在qt_metacall函数动态绑定时调用
  2. 成员变量 QMetaObject staticQtMetaObject;
    QMetaObject 类实现moc, 有以下内容
    关键数据信号和槽的信息都被分别存在此staticQtMetaObject变量里 ,
    相关的宏定义
  3. 数据结构 存放观察者和被观察者信号绑定。std::multimap
  4. connect函数 将观察者和被观察者绑定 利用了上一步数据结构std::multimap进行存储。
对应代码
struct Connection
{
    QObject * receiver;
    int methodID;
};

class QObject
{
public:
...
    static void  connect(Object*, const char*, Object*, const char*);
...
private:
    std::multimap connections;
 protected:
       static const QMetaObject staticQtMetaObject;
       **int  qt_metacall(QMetaObject::Call _c, int _id, void **_a);** //观察者会对此函数进行重写
    }

QMetaObject类内容:

1、 QMetaObject 里有active(发送者的信号函数里有 QMetaObject::activate,active是被信号函数emit时调用的)
2、 信号和槽函数对应的名称 const char * sig_names;

class QObject;
struct QMetaObject
{
   const char * sig_names;//信号名称 简化处理 具体内容是 qt_meta_stringdata_XXX.data
   const char * slts_names;//槽名称  简化处理 具体内容是 qt_meta_stringdata_XXX.data

   static void active(QObject * sender, int idx);
};
因为sender(被观察者)需要实现2件事

1 、添加观察者和感兴趣的事件id到容器map 中

程序运行时,connect借助两个字符串,即可将信号与槽的关联建立起来,那么,它是如果做到的呢?

void QObject::connect(Object* sender, const char* sig, Object* receiver, const char* slt)
{
    int sig_idx = find_string(sender->meta.sig_names, sig);//查找信号名在不在 
    int slt_idx = find_string(receiver->meta.slts_names, slt);//查找槽函数在不在
    if (sig_idx == -1 || slt_idx == -1) {
        perror("signal or slot not found!");
    } else {
        Connection c = {receiver, slt_idx};//将receiver sender和对应槽函数id 关联
        **sender->connections.insert(std::pair(sig_idx, c));**
    }
}

2 、通知事件函数执行逻辑:首先遍历map容器,有没有感兴趣的id
若有,则代表一系列观察者,对这个事件感兴趣,再次遍历观察者列表,让其执行相应的槽函数。
Active实现如下:

void QMetaObject::active(Object* sender, int idx)
{
    ConnectionMapIt it;
    std::pair ret;
    ret = sender->connections.equal_range(idx);
    for (it=ret.first; it!=ret.second; ++it) {
        Connection c = (*it).second;
       c.receiver->qt_metacall(-,c.methodID,-); //索引---槽函数--调用接收槽函数 
    }
}
接下来到观察者这里 调用槽函数

直接调用槽函数我们都知道了,就一个普通函数
可现在通过索引调用了,那么我们必须定义一个接口函数
槽的索引和槽的调用关联起来
前面active通过receiver->qt_metacall调用,所以需要用到 C++的多态,动态多态 重写 虚函数QObject::qt_metacall,

int Worker::qt_metacall(QMetaObject::Call _c, int _id, void **_a)//重写了虚函数 QObject::qt_metacall 实现了运行时调用
{
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
        if (_id < 2)
            qt_static_metacall(this, _c, _id, _a);
        _id -= 2;
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
        if (_id < 2)
            *reinterpret_cast(_a[0]) = -1;
        _id -= 2;
    }
    return _id;
}

然后查找槽函数的索引找到槽函数,调用槽函数

void Worker::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->resultReady((*reinterpret_cast< const QString(*)>(_a[1]))); break;
        case 1: _t->doWork((*reinterpret_cast< const QString(*)>(_a[1]))); break;// 调用具体操作的槽函数 
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast(_a[0]);
        {
            using _t = void (Worker::*)(const QString & );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&Worker::resultReady)) {
                *result = 0;
                return;
            }
        }
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/836461.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号