Qt中信号与槽的实现

Qt中信号与槽的实现

今天和朋友聊到关于Qt中类间通信是如何实现的,所以打算对源对象编译器处理过的代码做个简单的分析,了解下信号和槽到底是什么样子。

为了了解元对象编译器都做了些什么,先创建一个简单最简单的GUI程序。打开程序的debug目录,看到了moc_mainwindow.cpp文件,这就是Qt为我们做的事情了。

/********************************************************************

** Meta object code from reading C++ file 'mainwindow.h'

**

** Created: Sat Oct 17 23:38:12 2009

**      by: The Qt Meta Object Compiler version 62 (Qt 4.6.0)

**

** WARNING! All changes made in this file will be lost!

********************************************************************/

可以看到关于该文件的一些介绍,接下来看下面的定义:

#include "../mainwindow.h"

#if !defined(Q_MOC_OUTPUT_REVISION)

#error "The header file 'mainwindow.h' doesn't include <QObject>."

#elif Q_MOC_OUTPUT_REVISION != 62

#error "This file was generated using the moc from 4.6.0. It"

#error "cannot be used with the include files from this version of Qt."

#error "(The moc has changed too much.)"

#endif

首先看到的是,判断Q_MOC_OUTPUT_REVISION是否被定义,从而确定,该类是否继承自QObject。那么这个宏是在什么地方定义的呢?看下QObject的代码,就能找到了:

#ifndef Q_MOC_OUTPUT_REVISION

#define Q_MOC_OUTPUT_REVISION 62

#endif

Qt4.6源代码的56行位置。

接下来是对宏编译器版本的判定,接着往下看:

static const uint qt_meta_data_MainWindow[] = {

 // content:

       4,       // revision

       0,       // classname

       0,    0, // classinfo

       0,    0, // methods

       0,    0, // properties

       0,    0, // enums/sets

       0,    0, // constructors

       0,       // flags

       0,       // signalCount

 

       0        // eod

};

该结构体主要存储了类的一些解析的信息,我们可以通过加个信号,看看其改变。

加了一个信号之后,该结构体变成了:

static const uint qt_meta_data_MainWindow[] = {

 

 // content:

       4,       // revision

       0,       // classname

       0,    0, // classinfo

       1,   14, // methods

       0,    0, // properties

       0,    0, // enums/sets

       0,    0, // constructors

       0,       // flags

       1,       // signalCount

 

 // signals: signature, parameters, type, tag, flags

      12,   11,   11,   11, 0x05,

 

       0        // eod

};

变化的几个地方是:1,   14, // methods,这里意思应该是有1个方法,但是14的含义就不清楚了。之后是增加了信号的计数,再往后是对信号的记录,具体的含义,有待研究。

static const char qt_meta_stringdata_MainWindow[] = {

    "MainWindow/0"

};

这个数组存放的应该是需要的字符串信息,当我们增加信号以后,会是:

static const char qt_meta_stringdata_MainWindow[] = {

    "MainWindow/0/0hello()/0"

};

可以看出来,主要是看存放信号和槽名称的,另外就是类名了。

const QMetaObject MainWindow::staticMetaObject = {

    { &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow,

      qt_meta_data_MainWindow, 0 }

};

我们看到这是一个静态变量对象的复制操作,不仔细看还以为是一个方法的实现呢,之前在MainWindow里我们没有定义啊,这个只有函数实现怎么可以呢?还是让我们看看Q_OBJECT的宏吧。

#define Q_OBJECT /

public: /

    Q_OBJECT_CHECK /

    static const QMetaObject staticMetaObject; /

    Q_OBJECT_GETSTATICMETAOBJECT /

    virtual const QMetaObject *metaObject() const; /

    virtual void *qt_metacast(const char *); /

    QT_TR_FUNCTIONS /

    virtual int qt_metacall(QMetaObject::Call, int, void **); /

private:

/* tmake ignore Q_OBJECT */

原来,Q_OBJECT定义了这样一些东西,这样的话,我们的类实际上是这样的:

Class MainWindow: public QMainWindow

{

Public:

      Q_OBJECT_CHECK

      Static const QMetaObject staticMetaObject;

   Q_OBJECT_GETSTATICMETAOBJECT
              virtual const QMetaObject *metaObject() const;

      virtual void *qt_metacast(const char *);

QT_TR_FUNCTIONS

virtual int qt_metacall(QMetaObject::Call, int, void **);

}

这样,我们就看到了之前的那个静态变量:

const QMetaObject MainWindow::staticMetaObject = {

    { &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow,

      qt_meta_data_MainWindow, 0 }

};

这里将之前看到的数据信息传递给了QMetaObject类。

接下来继续看:

const QMetaObject *MainWindow::metaObject() const

{

    return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;

}

这里返回了元对象的指针。

void *MainWindow::qt_metacast(const char *_clname)

{

    if (!_clname) return 0;

    if (!strcmp(_clname, qt_meta_stringdata_MainWindow))

        return static_cast<void*>(const_cast< MainWindow*>(this));

    return QMainWindow::qt_metacast(_clname);

}

这里提供了元对象中的类型转换方式,联想到了C++中的dynamic_cast方法,不过只是猜测。

int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a)

{

    _id = QMainWindow::qt_metacall(_c, _id, _a);

    if (_id < 0)

        return _id;

    if (_c == QMetaObject::InvokeMetaMethod) {

        switch (_id) {

        case 0: hello(); break;

        default: ;

        }

        _id -= 1;

    }

    return _id;

}

这里就是信号和槽被调用的地方了,但是采用switch case的方法,显然速度不是很快,所以尽量在一个文件中写少一点的信号和槽,否则,时间复杂度将是o(n),如果这里采用map或者hash的方式,应该会好很多。但是Qt没有这么做,原因不得而知。

void MainWindow::hello()

{

    QMetaObject::activate(this, &staticMetaObject, 0, 0);

}

实际上,信号也是一个函数。由元对象系统负责对用与之关联的槽。

现在,我们再增加一个槽,看一看。

在数据结构体中,增加了新的关于槽的描述:

// slots: signature, parameters, type, tag, flags

      20,   11,   11,   11, 0x09,

if (_c == QMetaObject::InvokeMetaMethod) {

        switch (_id) {

        case 0: hello(); break;

        case 1: helloworld(); break;

        default: ;

        }

        _id -= 2;

    }

原来信号和槽是放在一起进行判定的。下面将信号和槽连在一起,会发生什么呢?结果是没有变化,那么,可以看出来,元对象编译器,主要产生了信号和槽的调用函数。所有的信息会提交给一个上层类,QMetaObject进行管理,负责调度,信号函数是直接被调用的,而槽的函数则是在信号函数中调用active之后,由元对象系统负责调用。同样的,我们还可以将一个信号和另外一个信号连接在一起,这样的过程,后面的信号也会被以同样的方式触发。

今天就到这里了,明天继续了解信号和槽在QMetaObject中的实现。

 

20091018日星期日 0031

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值