美文网首页
QT信号槽实现原理

QT信号槽实现原理

作者: 上官宏竹 | 来源:发表于2021-06-21 10:48 被阅读0次

    先放一个简单的信号槽的实现,如下:
    mainwindow.h

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    
    namespace Ui {
    class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
        void sendMysignal(int a);
    
    signals:
        void mysignal(int);
    
    public slots:
        void onmysignal(int);
    private:
        Ui::MainWindow *ui;
    };
    
    #endif // MAINWINDOW_H
    

    mainwindow.cpp

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include <QDebug>
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        connect(this, &MainWindow::mysignal, this, &MainWindow::onmysignal) ;
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::onmysignal(int a)
    {
        qDebug()<<"onmysignal a: "<<a;
    }
    
    void MainWindow::sendMysignal(int a)
    {
        qDebug()<<"emit mysignal a: "<<a;
        emit mysignal(a);
    }
    

    由于头文件中有Q_OBJECT,则在编译后会生成一个moc_mainwindow.cpp文件,该文件由moc生成。

    Q_OBJECT

    先看下Q_OBJECT宏的定义

    /* qmake ignore Q_OBJECT */
    #define Q_OBJECT \
    public: \
        QT_WARNING_PUSH \
        Q_OBJECT_NO_OVERRIDE_WARNING \
        static const QMetaObject staticMetaObject; \          // 常量 QMetaObject 对象
        virtual const QMetaObject *metaObject() const; \    // 用于不同的 class 返回自己的 staticMetaObject
        virtual void *qt_metacast(const char *); \                  // 用于转换
        virtual int qt_metacall(QMetaObject::Call, int, void **); \  // 作用是查表,调用函数  
        QT_TR_FUNCTIONS \      // 和i18n相关
    private: \
        Q_OBJECT_NO_ATTRIBUTES_WARNING \
        Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
        QT_WARNING_POP \
        struct QPrivateSignal {}; \
        QT_ANNOTATE_CLASS(qt_qobject, "")
    

    metaObject接口

    用于不同的 class 返回自己的 staticMetaObject。如果QObject实例的metaObject 不为空,则调用QObjectData::dynamicMetaObject(),否则返回自己的staticMetaObject地址。

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

    qt_metacast接口

    根据传入的类名,返回对应的实例。

    void *MainWindow::qt_metacast(const char *_clname)
    {
        if (!_clname) return nullptr;
        // 如果传入了自己,即"MainWindow",则返回this指针
        if (!strcmp(_clname, qt_meta_stringdata_MainWindow.stringdata0))
            return static_cast<void*>(this);
        // 否则一层一层往基类调用比较,QMainWindow类也实现了Q_OBJECT,包括它上面的基本,这样一直比较到顶的QObject
        return QMainWindow::qt_metacast(_clname);
    }
    

    元对象中的索引

    在每一个QMetaObject对象中,槽、信号以及其它的对象可调用函数都会分配一个从0开始的索引。索引是有顺序的,信号在第一位,槽在第二位,最后是其它函数。这个索引在内部被称为相对索引,不包含父对象的索引位。为了实现包含在继承链中其它函数的索引,在相对索引的基础上添加一个偏移量,得到绝对索引。绝对索引是在公开API中使用的索引,由QMetaObject::indexOf(Signal, Slot, Method) 类似的函数返回。
    多个信号槽对,会依次递增序号比如:

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

    qt_metacall接口

    根据Id处理。当信号发射时,QT会掉用qt_metacall,并把QMetaObject::Call设置为QMetaObject::InvokeMetaMethod。该函数同样处理属性系统,根据传入的不同的QMetaObject::Call来处理。
    第二个参数是一个索引,用于唯一标识当前对象继承的类的层次结构中的信号或槽。最后一个参数是一个数组,其中包含指向参数的指针,其后是指向应放置返回值的位置(如果有)的指针,对于信号槽机制,代表则传入的参数列表。

    int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
    {
        _id = QMainWindow::qt_metacall(_c, _id, _a);  // 由于可能存在调用qt_metacall是调用基类的qt_metacall原对象,故先执行基类接口
        if (_id < 0)
            return _id;  // 表示qt_metacall已由基类处理完毕
        if (_c == QMetaObject::InvokeMetaMethod) {
            if (_id < 2)
                qt_static_metacall(this, _c, _id, _a);  // 处理信号槽
            _id -= 2;
        } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
            if (_id < 2)
                *reinterpret_cast<int*>(_a[0]) = -1;
            _id -= 2;
        }
        return _id;
    }
    enum QMetaObject::Call {
        InvokeMetaMethod,
        ReadProperty,
        WriteProperty,
        ResetProperty,
        QueryPropertyDesignable,
        QueryPropertyScriptable,
        QueryPropertyStored,
        QueryPropertyEditable,
        QueryPropertyUser,
        CreateInstance,
        IndexOfMethod,
        RegisterPropertyMetaType,
        RegisterMethodArgumentMetaType
    };
    

    qt_static_metacall私有接口

    void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
    {
        if (_c == QMetaObject::InvokeMetaMethod) {
            auto *_t = static_cast<MainWindow *>(_o);
            Q_UNUSED(_t)
            switch (_id) {
            case 0: _t->mysignal((*reinterpret_cast< int(*)>(_a[1]))); break;      // 调用信号函数,传入参数,该函数也由moc生成
            case 1: _t->onmysignal((*reinterpret_cast< int(*)>(_a[1]))); break;  // 调用槽函数,传入参数,该函数为我们自己实现
            default: ;
            }
        } else if (_c == QMetaObject::IndexOfMethod) {
            int *result = reinterpret_cast<int *>(_a[0]);
            {
                using _t = void (MainWindow::*)(int );
                if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::mysignal)) {
                    *result = 0;
                    return;
                }
            }
        }
    }
    void MainWindow::mysignal(int _t1)
    {
        void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
        QMetaObject::activate(this, &staticMetaObject, 0, _a);  // 0是当前对象内部信号的相对索引
    }
    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);
    }
    

    相关文章

      网友评论

          本文标题:QT信号槽实现原理

          本文链接:https://www.haomeiwen.com/subject/yfxjlltx.html