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_Counter和qt_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例子。