【Qt编程】- 信号槽

http://woboq.com/blog/how-qt-signals-slots-work.html

http://ntcore.com/files/qtrev.htm

http://qt-project.org/doc/qtcreator-2.6/creator-targets.html

2018.7 补充:

信号槽机制有2种实现方式:

1)直接连接,同一个线程的信号和槽是同步关联执行的。

2)队列执行,这个机制的本质是跨线程通信的消息队列模型。比如,a线程发送一个signal给b线程的slot,如果此时b线程(当然肯定是需要声明QOBJECT的)正在忙于一个runloop(), 而这个runloop里面没有执行Thread::exec()方法(这个方法是用来fetch消息队列里面的消息的进而执行的),那么这个slot不会被执行。所以你看,信号槽机制和windows的windowproc是不是也很类似,需要有消息队列来驱动windowproc,windowsproc看起来是消息与处理过程的映射(switch/case),但是背后还是靠最原始的线程间通信模型之一的:消息队列。

 

第一个例子 非qt项目的纯c语言项目

 

main.c

 

#include <stdio.h>

int main(void)
{
    printf("Hello World!\n");
    return 0;
}


qtc.pro 项目文件

 

 

TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt

SOURCES += main.c


ctrl+B 生成项目,ctrl+R 运行项目

 

 

第二个例子 非qt项目的c++项目

 

main.cpp

 

#include <iostream>

using namespace std;

int main()
{
    cout << "Hello World!" << endl;
    return 0;
}

 

 

qtcpp.pro项目文件

 

TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
CONFIG -= qt

SOURCES += main.cpp

 


可以看出来几乎和上面一样:

 

 

 

但是这个时候,我要添加一个Counter类,来测试moc的功能

 

qtcpp.pro里面多了一行

 

HEADERS += \
    Counter.h


Counter.h的代码和signal&slot的经典例子的Counter几乎一样

 

 

#ifndef COUNTER_H
#define COUNTER_H

#include <QObject>

class Counter : public QObject{
    Q_OBJECT
public:
    Counter(){m_value = 0;}
    ~Counter(){} int value() const {return m_value;}
public slots:
    void setValue(int v){
        if (v != m_value){
            m_value = v;
            emit valueChanged(v);
        }
    }
signals:
    void valueChanged(int v);
private:
    int m_value;
};

#endif // COUNTER_H


修改qtcpp.pro告知使用moc生成moc代码和添加qt头文件路径

 

 

TEMPLATE = app
CONFIG += console
CONFIG += moc
CONFIG -= app_bundle
CONFIG += qt

SOURCES += main.cpp

HEADERS += \
    Counter.h

 

15:39:37: 为项目qtcpp执行步骤 ...
15:39:37: 配置没有改变, 跳过 qmake 步骤。
15:39:38: 正在启动 "C:\Qt\qtcreator-3.1.1\bin\jom.exe" 

	C:\Qt\qtcreator-3.1.1\bin\jom.exe -f Makefile.Debug
	C:\Qt\4.8.6\bin\moc.exe -DUNICODE -DWIN32 -DQT_DLL -DQT_GUI_LIB -DQT_CORE_LIB -DQT_HAVE_MMX -DQT_HAVE_3DNOW -DQT_HAVE_SSE -DQT_HAVE_MMXEXT -DQT_HAVE_SSE2 -DQT_THREAD_SUPPORT -I"..\..\4.8.6\include\QtCore" -I"..\..\4.8.6\include\QtGui" -I"..\..\4.8.6\include" -I"..\..\4.8.6\include\ActiveQt" -I"debug" -I"..\qtcpp" -I"." -I"..\..\4.8.6\mkspecs\win32-msvc2008" -D_MSC_VER=1500 -DWIN32 ..\qtcpp\Counter.h -o debug\moc_Counter.cpp
	cl -c -nologo -Zm200 -Zc:wchar_t- -Zi -MDd -GR -EHsc -W3 -w34100 -w34189 -DUNICODE -DWIN32 -DQT_DLL -DQT_GUI_LIB -DQT_CORE_LIB -DQT_HAVE_MMX -DQT_HAVE_3DNOW -DQT_HAVE_SSE -DQT_HAVE_MMXEXT -DQT_HAVE_SSE2 -DQT_THREAD_SUPPORT -I"..\..\4.8.6\include\QtCore" -I"..\..\4.8.6\include\QtGui" -I"..\..\4.8.6\include" -I"..\..\4.8.6\include\ActiveQt" -I"debug" -I"..\qtcpp" -I"." -I"..\..\4.8.6\mkspecs\win32-msvc2008" -Fodebug\ @C:\Users\ADMINI~1\AppData\Local\Temp\moc_Counter.obj.10572.156.jom
moc_Counter.cpp
	link /LIBPATH:"c:\Qt\4.8.6\lib" /NOLOGO /DYNAMICBASE /NXCOMPAT /DEBUG /SUBSYSTEM:CONSOLE "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" /MANIFEST /MANIFESTFILE:"debug\qtcpp.intermediate.manifest" /OUT:debug\qtcpp.exe @C:\Users\ADMINI~1\AppData\Local\Temp\qtcpp.exe.10572.1139.jom
LINK : debug\qtcpp.exe not found or not built by the last incremental link; performing full link
	mt.exe -nologo -manifest "debug\qtcpp.intermediate.manifest" -outputresource:debug\qtcpp.exe;1
15:39:39: 进程"C:\Qt\qtcreator-3.1.1\bin\jom.exe"正常退出。
15:39:39: Elapsed time: 00:02.


修改一下main.cpp使用signal和slot

 

 

#include <iostream>

using namespace std;

#include "Counter.h"

int main()
{
    Counter a, b;

    QObject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));

    a.setValue(12);
    cout << "a.value=" << a.value() << ", b.value="<<b.value()<<endl;

    a.setValue(24);
    cout << "a.value=" << a.value() << ", b.value="<<b.value()<<endl;

    cout << "Hello World!" << endl;
    return 0;
}


先看看moc生成的元对象代码:

 

 

/****************************************************************************
** Meta object code from reading C++ file 'Counter.h'
**
** Created by: The Qt Meta Object Compiler version 63 (Qt 4.8.6)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/

#include "../../qtcpp/Counter.h"
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'Counter.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 63
#error "This file was generated using the moc from 4.8.6. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif

QT_BEGIN_MOC_NAMESPACE
static const uint qt_meta_data_Counter[] = {

 // content:
       6,       // revision
       0,       // classname
       0,    0, // classinfo
       2,   14, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       0,       // flags
       1,       // signalCount

 // signals: signature, parameters, type, tag, flags
      11,    9,    8,    8, 0x05,

 // slots: signature, parameters, type, tag, flags
      29,    9,    8,    8, 0x0a,

       0        // eod
};

static const char qt_meta_stringdata_Counter[] = {
    "Counter\0\0v\0valueChanged(int)\0setValue(int)\0"
};

void Counter::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        Q_ASSERT(staticMetaObject.cast(_o));
        Counter *_t = static_cast<Counter *>(_o);
        switch (_id) {
        case 0: _t->valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 1: _t->setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
        default: ;
        }
    }
}

const QMetaObjectExtraData Counter::staticMetaObjectExtraData = {
    0,  qt_static_metacall 
};

const QMetaObject Counter::staticMetaObject = {
    { &QObject::staticMetaObject, qt_meta_stringdata_Counter,
      qt_meta_data_Counter, &staticMetaObjectExtraData }
};

#ifdef Q_NO_DATA_RELOCATION
const QMetaObject &Counter::getStaticMetaObject() { return staticMetaObject; }
#endif //Q_NO_DATA_RELOCATION

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

void *Counter::qt_metacast(const char *_clname)
{
    if (!_clname) return 0;
    if (!strcmp(_clname, qt_meta_stringdata_Counter))
        return static_cast<void*>(const_cast< Counter*>(this));
    return QObject::qt_metacast(_clname);
}

int Counter::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;
    }
    return _id;
}

// SIGNAL 0
void Counter::valueChanged(int _t1)
{
    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
QT_END_MOC_NAMESPACE

 

 

 

注意qt_meta_data_Counterqt_meta_stringdata_Counter,一个是索引一个字符串表,大概可以在运行时知道方法和对象的名字。

头文件里面声明的signal也被翻译成了一个函数,里面会去调用

QMetaObject::activate()

 

按F5开始用cdb调试一把看看这些逻辑都是怎么起作用的。

 

跟进到src/corelib/kernel/qobject.cpp

 

跟进到

 

bool QMetaObjectPrivate::connect(const QObject *sender, int signal_index,
                                 const QObject *receiver, int method_index,
                                 const QMetaObject *rmeta, int type, int *types)
{


创建一个新的connection添加到列表

 

 

    QObjectPrivate::Connection *c = new QObjectPrivate::Connection;
    c->sender = s;
    c->receiver = r;
    c->method_relative = method_index;
    c->method_offset = method_offset;
    c->connectionType = type;
    c->argumentTypes = types;
    c->nextConnectionList = 0;
    c->callFunction = callFunction;

    QT_TRY {
        QObjectPrivate::get(s)->addConnection(signal_index, c);
    } QT_CATCH(...) {
        delete c;
        QT_RETHROW;
    }


注册完毕,接下来调用setValue() 触发一个a对象的signal,而这个a对象的signal是如何触发b对象的slot的呢?

 

 

 

跟进到

 

void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
                           void **argv)
{


直接在setValue里面F9断点一下,看看调用堆栈就知道了。

 

原来QMetaObject::activate()会搜索啊,搜索到符合条件的connection,开始调用signal的receiver的slot

 

                QT_TRY {
                    callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);
                } QT_CATCH(...) {


栈的第二层

 

 

void Counter::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        Q_ASSERT(staticMetaObject.cast(_o));
        Counter *_t = static_cast<Counter *>(_o);
        switch (_id) {
        case 0: _t->valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 1: _t->setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
        default: ;
        }
    }
}

 

 

最终的slot调用,b对象的

 

我注意到,b对象里面也会emit一个signal,valueChanged, 但是,呵呵 ,这个b对象的signal,我可没有实现做connect到任何其他的slot。

一个坏主意,留给大家思考,如果我把b的signal在连接到a的slot,那是不是就死循环了

 

运行结果:


 

 

这一节主要是调试signal和slot基本原理的过程,下一节,按部就班的生成一个标准的GUI例子。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值