您的位置:首页 > 编程语言 > Qt开发

【QT】深入qt信号与槽实现原理

2017-07-01 05:19 543 查看

1、先上示例代码

先上示例代码直观地感受一下qt信号与槽的用法,后面再详细解释。通过QtCreator创建一个Qt Widget工程(没有创建ui文件,其它选项为默认值),工程名为SS,最后在SS目录下会生成5个文件:main.cpp、mainwindow.cpp、mainwindow.h、SS.pro和SS.pro.user,然后对这几个文件稍作修改,最终的源码如下。

SS.pro——

QT += core gui
QT += widgets

TARGET = SS
TEMPLATE = app

SOURCES += main.cpp\
mainwindow.cpp

HEADERS  += mainwindow.h


SS.pro.user——

这是一个xml文件,保存了SS工程在QtCreator中的相关配置信息,不是我们关注的对象。

main.cpp——

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();

return a.exec();
}


mainwindow.h——

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow(QWidget *parent = 0);
~MainWindow();

protected:
void mousePressEvent(QMouseEvent *event);

//Q_SIGNALS:
signals:
void mousePressed();

//private Q_SLOTS:
private slots:
void onMousePressed();
};

#endif // MAINWINDOW_H


mainwindow.cpp——

#include "mainwindow.h"

#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//    connect(this, &MainWindow::mousePressed, this, &MainWindow::onMousePressed);
connect(this, SIGNAL(mousePressed()), this, SLOT(onMousePressed()));

setGeometry(100, 100, 360, 360);
}

MainWindow::~MainWindow()
{
disconnect(this, SIGNAL(mousePressed()), this, SLOT(onMousePressed()));
}

void MainWindow::mousePressEvent(QMouseEvent *event)
{
Q_UNUSED(event)

emit mousePressed(); // Q_EMIT
}

void MainWindow::onMousePressed()
{
qDebug() << "[SLOT] MainWindow::onMousePressed";
}


接着,通过QtCreator编译SS工程,在形如build-SS-XXX-XXX的目录下产生编译结果,共6个文件:Makefile、SS、main.o、mainwindow.o、moc_mainwindow.o和moc_mainwindow.cpp,且不管这些文件是如何生成的,有什么作用。

最后,通过QtCreator运行SS工程,在弹出的窗口上按下鼠标就会输出“[SLOT] MainWindow::onMousePressed”,这个log就是通过信号与槽实现的。

2、信号与槽简介

信号(signal)与槽(slot)是qt的一大特色,由元对象系统(meta object system)提供,用于对象间的通信,类似的还有借助于函数指针的回调机制,理论上,信号与槽比回调的反应速度要慢,但前者用起来更灵活。下面以SS工程为例,简单介绍一下信号与槽的用法。SS.pro为工程文件,main.cpp文件实现了必需的main函数,这两个文件不作更多解释,重点在于mainwindow.h和mainwindow.cpp。

使用信号与槽,首先,类必须直接或间接继承自QObject,如示例中的MainWindow继承自QMainWindow,而QMainWindow间接继承自QObject;然后,在类入口处使用O_OBJECT宏,这是必须的;接着,使用signals或Q_SIGNALS声明信号,如示例中的mousePressed,信号类似于成员函数,只不过其返回类型一般为void,但可以有参数,而且只有声明不需定义,使用private、protected或public slots或Q_SLOTS声明槽并定义槽,如示例中的onMousePressed,槽就是个普通的成员函数,只不过声明时多了个slots或Q_SLOTS而已;最后使用connect连接信号与槽,信号与信号也可以连接,当信号发送时,就会触发与之连接的槽,使用disconnect断开连接,两者连接时它们的参数列表必须相同,示例中在构造函数中connect,析构函数中disconnect,重写了虚函数mousePressEvent,当有鼠标按下事件时就会调到这个函数,函数中通过emit发送mousePressed信号,进而触发与之连接的onMousePressed槽,输出log。connect和disconnect有多个重载函数,这里不作详细介绍,其中connect连接的信号与槽可以通过取地址符直接取对应的地址,或者使用SIGNAL与SLOT进行包装,但后者更好用。

3、编译过程分析(Qt5)

上面提到了编译结果,有两个文件比较奇怪,moc_mainwindow.cpp和moc_mainwindow.o。首先,通过qmake及其默认配置和SS.pro生成Makefile,然后,通过这个Makefile继续编译。接下来,使用g++编译main.cpp生成main.o,使用g++编译mainwindow.cpp生成mainwindow.o,使用元对象编译器moc编译mainwindow.h生成moc_mainwindow.cpp,使用g++编译moc_mainwindow.cpp生成moc_mainwindow.o,最后使用g++链接main.o、mainwindow.o和moc_mainwindow.cpp和生成SS,over。重点在于moc,我们来看一下由moc生成的moc_mainwindow.cpp是如何保存信号与槽相关信息的。

qt_meta_stringdata_MainWindow变量——

struct qt_meta_stringdata_MainWindow_t {
QByteArrayData data[4];
char stringdata[40];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_MainWindow_t, stringdata) + ofs \
- idx * sizeof(QByteArrayData)) \
)
static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {
{
QT_MOC_LITERAL(0, 0, 10), // "MainWindow"
QT_MOC_LITERAL(1, 11, 12), // "mousePressed"
QT_MOC_LITERAL(2, 24, 0), // ""
QT_MOC_LITERAL(3, 25, 14) // "onMousePressed"

},
"MainWindow\0mousePressed\0\0onMousePressed"
};
#undef QT_MOC_LITERAL


qt_meta_stringdata_MainWindow是一个只读的静态变量,变量名中qt_meta_stringdata_为固定字段,MainWindow为对应的类名。qt_meta_stringdata_MainWindow的类型为qt_meta_stringdata_MainWindow_t,是个结构体,有两个数组成员,每个数组的长度是都是动态的。数组data有4个元素,元素排列顺序为当前类、第一个信号、占位符、其它信号、其它槽,信号在槽前面,信号和槽各自的顺序以声明的顺序排列,示例中MainWindow有1个信号和1个槽,所以加上类和占位符共4个元素,至少有1个信号或槽时后面就有1个占位符,否则只有当前类1个元素;每个元素都使用了QT_MOC_LITERAL参数宏,第一个参数表示元素索引,第二个参数表示元素在stringdata中的偏移量,第三个参数表示元素对应的字符串长度,实际上就是对QByteArrayData进行初始化,详细分析见下面的“QByteArrayData初始化”。stringdata是个字符数组,长度与data数组中的元素有关,顺序保存了data数组中各元素对应的字符串表示,即类名、信号名和槽名,占位符不占据任何长度,各个字段之间以空(null)字符分隔,示例中这个值为
"MainWindow\0mousePressed\0\0onMousePressed"


QByteArrayData初始化——

下面深挖QByteArrayData结构及初始化方式,顺便学习下C++强大的模板用法,如下层层展开的代码所示。

// 1. QByteArrayData is QArrayData with 5 data members
typedef QArrayData QByteArrayData;
struct Q_CORE_EXPORT QArrayData
{
QtPrivate::RefCount ref; // see below
int size; // int
uint alloc : 31; // unsigned int with 31 bits
uint capacityReserved : 1; // unsignet int with 1 bit
qptrdiff offset; // in bytes from beginning of header // see below
// others ...
};
// 2. What is QtPrivate::RefCount
namespace QtPrivate
{
class RefCount
{
public:
// others ...
QBasicAtomicInt atomic; // typedef QBasicAtomicInteger<int> QBasicAtomicInt;
};
}
// 2.1 QBasicAtomicInteger
template <typename T>
class QBasicAtomicInteger
{
public:
typedef QAtomicOps<T> Ops;
typename Ops::Type _q_value; // >> ref of QArrayData stored here (type is int) <<
// others ...
};
// 2.2 QAtomicOps
template <typename T> struct QAtomicOps : QBasicAtomicOps<sizeof(T)>
{
typedef T Type;
};
// 2.3 QBasicAtomicOps
template <int size> struct QBasicAtomicOps : QGenericAtomicOps<QBasicAtomicOps<size> >
{
// something ...
};
// 2.4 QGenericAtomicOps
template <typename BaseClass> struct QGenericAtomicOps
{
// something ...
};
// 3. What is qptrdiff
typedef QIntegerForSizeof<void*>::Signed qptrdiff; // qint32(int - 32 bit signed) or qint64(long long - 64 bit signed)
template <class T> struct QIntegerForSizeof: QIntegerForSize<sizeof(T)> { };
template <int> struct QIntegerForSize;
template <>    struct QIntegerForSize<1> { typedef quint8  Unsigned; typedef qint8  Signed; };
template <>    struct QIntegerForSize<2> { typedef quint16 Unsigned; typedef qint16 Signed; };
template <>    struct QIntegerForSize<4> { typedef quint32 Unsigned; typedef qint32 Signed; };
template <>    struct QIntegerForSize<8> { typedef quint64 Unsigned; typedef qint64 Signed; };
// 4. What is QT_MOC_LITERAL
#define QT_MOC_LITERAL(idx, ofs, len) \
Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
qptrdiff(offsetof(qt_meta_stringdata_MainWindow_t, stringdata) + ofs \
- idx * sizeof(QByteArrayData)) \
)
// 4.1 macro definations
#define Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset) \
Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset)
#define Q_STATIC_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(size, offset) \
{ Q_REFCOUNT_INITIALIZE_STATIC, size, 0, 0, offset } \
#define Q_REFCOUNT_INITIALIZE_STATIC { Q_BASIC_ATOMIC_INITIALIZER(-1) }
#  define Q_BASIC_ATOMIC_INITIALIZER(a) { (a) }
// 4.2 What is offsetof
// offsetof from sqlite3.c
#ifndef offsetof
#define offsetof(STRUCTURE,FIELD) ((int)((char*)&((STRUCTURE*)0)->FIELD))
#endif
// offsetof from stddef.h
#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)


qt_meta_data_MainWindow变量——

static const uint qt_meta_data_MainWindow[] = {

// content:
7,       // revision 7 is Qt 5
0,       // classname
0,    0, // classinfo count and data
2,   14, // methods count and data
0,    0, // properties count and data
0,    0, // enums/sets count and data
0,    0, // constructors count and data
0,       // flags since revision 3
1,       // signalCount since revision 4

// signals: name, argc, parameters, tag, flags
1,    0,   24,    2, 0x06 /* Public */,

// slots: name, argc, parameters, tag, flags
3,    0,   25,    2, 0x08 /* Private */,

// signals: parameters
QMetaType::Void,

// slots: parameters
QMetaType::Void,

0        // eod
};


qt_meta_data_MainWindow变量中数据类型为unit,有几个关键的地方,content中methods为2表示共有2个信号和槽,signalCount为1表示共有1个信号,接着是信号和槽的相关信息,最后一个元素为0标记结束。

4、有用的宏

示例中的宏Q_OBJECT、signals、Q_SIGNALS、slots、Q_SLOTS、emit等是非常有用的,在头文件qobjectdefs.h中定义,根据是否为moc编译而分为两个版本,源码如下。

// The following macros are our "extensions" to C++
// They are used, strictly speaking, only by the moc.
#ifndef Q_MOC_RUN
#ifndef QT_NO_META_MACROS
# if defined(QT_NO_KEYWORDS)
#  define QT_NO_EMIT
# else
#   ifndef QT_NO_SIGNALS_SLOTS_KEYWORDS
#     define slots
#     define signals public
#   endif
# endif
# define Q_SLOTS
# define Q_SIGNALS public
# define Q_EMIT
#ifndef QT_NO_EMIT
# define emit
#endif
// others ...
#endif // QT_NO_META_MACROS
/* qmake ignore Q_OBJECT */
#define Q_OBJECT \
public: \
Q_OBJECT_CHECK \
static const QMetaObject staticMetaObject; \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
QT_TR_FUNCTIONS \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
private: \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
struct QPrivateSignal {};
#else // Q_MOC_RUN
#define slots slots
#define signals signals
#define Q_SLOTS Q_SLOTS
#define Q_SIGNALS Q_SIGNALS
#define Q_OBJECT Q_OBJECT
#endif //Q_MOC_RUN


关键在于上面的Q_OBJECT,其中声明的函数由moc编译时实现,另外还实现了信号,前面提到了信号只声明不定义,其实信号也是函数,只不过由moc实现,示例中的moc_mainwindow.cpp相关源码及分析如下。

// qt_static_metacall函数从其名字来看是一个调用函数的方法
// 参数_c值为InvokeMetaMethod时说明将调用函数
// 然后根据参数_id值去调用对应的信号或槽
// 参数_c值为IndexOfMethod时通过成员指针对信号地址进行检查
// 返回值为信号对应的_id
void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
if (_c == QMetaObject::InvokeMetaMethod) {
MainWindow *_t = static_cast<MainWindow *>(_o);
switch (_id) {
case 0: _t->mousePressed(); break;
case 1: _t->onMousePressed(); break;
default: ;
}
} else if (_c == QMetaObject::IndexOfMethod) {
int *result = reinterpret_cast<int *>(_a[0]);
void **func = reinterpret_cast<void **>(_a[1]);
{
typedef void (MainWindow::*_t)();
if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&MainWindow::mousePressed)) {
*result = 0;
}
}
}
Q_UNUSED(_a);
}
// staticMetaObject变量保存了所有的元数据
const QMetaObject MainWindow::staticMetaObject = {
{ &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow.data,
qt_meta_data_MainWindow,  qt_static_metacall, Q_NULLPTR, Q_NULLPTR}
};
// metaObject函数用于获取QMetaObject
const QMetaObject *MainWindow::metaObject() const
{
return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}
// qt_metacast函数用于提取参数_clname对应类的信号与槽的名字
// 因为qt_meta_stringdata_MainWindow.stringdata的第一个数据段保存的是类名
// 所以可以通过strcmp进行类名比较
void *MainWindow::qt_metacast(const char *_clname)
{
if (!_clname) return Q_NULLPTR;
if (!strcmp(_clname, qt_meta_stringdata_MainWindow.stringdata))
return static_cast<void*>(const_cast< MainWindow*>(this));
return QMainWindow::qt_metacast(_clname);
}
// qt_metacall函数根据参数_id及_c执行不同的动作
// 当_id<2且-c==InvokeMetaMethod时
// 执行前面介绍的qt_static_metacall
// 这里的数字2表示的是信号和槽的总数为2
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) {
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;
}
// SIGNAL 0
// 发送信号其实调用的就是这个信号函数
// 信号函数由moc通过QMetaObject::activate实现
// 第一个参数为当前对象指针this
// 第二个参数为上面介绍的staticMetaObject
// 第三个参数为从0开始的信号索引
// 第四个参数为空指针NULL
void MainWindow::mousePressed()
{
QMetaObject::activate(this, &staticMetaObject, 0, Q_NULLPTR);
}


示例中的connect函数用到了SIGNAL与SLOT宏,它们分debug和非debug两个版本,非debug版本就是在参数前面添加一个数字,信号为2,槽为1,源码如下。

// qglobal.h
/* These two macros makes it possible to turn the builtin line expander into a
* string literal. */
#define QT_STRINGIFY2(x) #x
#define QT_STRINGIFY(x) QT_STRINGIFY2(x)
// qobjectdefs.h
Q_CORE_EXPORT const char *qFlagLocation(const char *method);

#ifndef QT_NO_META_MACROS
#ifndef QT_NO_DEBUG
# define QLOCATION "\0" __FILE__ ":" QT_STRINGIFY(__LINE__)
# ifndef QT_NO_KEYWORDS
#  define METHOD(a)   qFlagLocation("0"#a QLOCATION)
# endif
# define SLOT(a)     qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a)   qFlagLocation("2"#a QLOCATION)
#else
# ifndef QT_NO_KEYWORDS
#  define METHOD(a)   "0"#a
# endif
# define SLOT(a)     "1"#a
# define SIGNAL(a)   "2"#a
#endif

#define QMETHOD_CODE  0                        // member type codes
#define QSLOT_CODE    1
#define QSIGNAL_CODE  2
#endif // QT_NO_META_MACROS


5、connect

使用信号前,首先要进行connect,示例中的connect代码如下。

connect(this, SIGNAL(mousePressed()), this, SLOT(onMousePressed()));


connect有多个重载函数,下面以示例中的用法为例展开说明,源码如下。

QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type)
{
// 先判断函数参数是否有效
if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
sender ? sender->metaObject()->className() : "(null)",
(signal && *signal) ? signal+1 : "(null)",
receiver ? receiver->metaObject()->className() : "(null)",
(method && *method) ? method+1 : "(null)");
return QMetaObject::Connection(0);
}
QByteArray tmp_signal_name;

// 检查信号对应的宏SIGNAL是否正确使用
// SIGNAL在信号前面添加了数字2
// check_signal_macro通过这个数字2进行检查
// 是否正确使用了SIGNAL
if (!check_signal_macro(sender, signal, "connect", "bind"))
return QMetaObject::Connection(0);
const QMetaObject *smeta = sender->metaObject();
const char *signal_arg = signal;
++signal; // 跳过SIGNAL宏中的数字2
QArgumentTypeArray signalTypes;
Q_ASSERT(QMetaObjectPrivate::get(smeta)->revision >= 7); // moc设置了revision为7
// 提取信号名signalName和参数列表signalTypes
// decodeMethodSignature函数使用了strchr函数定位左、右括号在signal字符串中的位置
QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
// 提取信号索引signal_index
// 从当前类到父类查找signalName对应的索引
// 失败时返回-1
int signal_index = QMetaObjectPrivate::indexOfSignalRelative(
&smeta, signalName, signalTypes.size(), signalTypes.constData());
if (signal_index < 0) {
// check for normalized signatures
tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
signal = tmp_signal_name.constData() + 1;

signalTypes.clear();
signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
smeta = sender->metaObject();
signal_index = QMetaObjectPrivate::indexOfSignalRelative(
&smeta, signalName, signalTypes.size(), signalTypes.constData());
}
if (signal_index < 0) {
err_method_notfound(sender, signal_arg, "connect");
err_info_about_objects("connect", sender, receiver);
return QMetaObject::Connection(0);
}
signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);
signal_index += QMetaObjectPrivate::signalOffset(smeta);

// 同理下面获取槽的名字和索引
// 因为信号可以连接到槽和另外一个信号
// 所以对槽进行处理时还要判断是否为信号
QByteArray tmp_method_name;
int membcode = extract_code(method);

if (!check_method_code(membcode, receiver, method, "connect"))
return QMetaObject::Connection(0);
const char *method_arg = method;
++method; // skip code

QByteArray methodName;
QArgumentTypeArray methodTypes;
const QMetaObject *rmeta = receiver->metaObject();
int method_index_relative = -1;
Q_ASSERT(QMetaObjectPrivate::get(rmeta)->revision >= 7);
switch (membcode) {
case QSLOT_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
case QSIGNAL_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
}
if (method_index_relative < 0) {
// check for normalized methods
tmp_method_name = QMetaObject::normalizedSignature(method);
method = tmp_method_name.constData();

methodTypes.clear();
methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
// rmeta may have been modified above
rmeta = receiver->metaObject();
switch (membcode) {
case QSLOT_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
case QSIGNAL_CODE:
method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
&rmeta, methodName, methodTypes.size(), methodTypes.constData());
break;
}
}

if (method_index_relative < 0) {
err_method_notfound(receiver, method_arg, "connect");
err_info_about_objects("connect", sender, receiver);
return QMetaObject::Connection(0);
}
// 检查信号与槽的参数列表是否一致
if (!QMetaObjectPrivate::checkConnectArgs(signalTypes.size(), signalTypes.constData(),
methodTypes.size(), methodTypes.constData())) {
qWarning("QObject::connect: Incompatible sender/receiver arguments"
"\n        %s::%s --> %s::%s",
sender->metaObject()->className(), signal,
receiver->metaObject()->className(), method);
return QMetaObject::Connection(0);
}
// 对connect的类型进行处理
int *types = 0;
if ((type == Qt::QueuedConnection)
&& !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) {
return QMetaObject::Connection(0);
}
// 最后通过QMetaObjectPrivate::connect进行真正的connect
#ifndef QT_NO_DEBUG
QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset());
check_and_warn_compat(smeta, smethod, rmeta, rmethod);
#endif
QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));
return handle;
}


下面是QMetaObjectPrivate::connect的源码实现。

QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,
int signal_index, const QMetaObject *smeta,
const QObject *receiver, int method_index,
const QMetaObject *rmeta, int type, int *types)
{
// sender和receiver去const
QObject *s = const_cast<QObject *>(sender);
QObject *r = const_cast<QObject *>(receiver);

// 获取receiver中method的偏移量
// 因为其method_index是个相对值
int method_offset = rmeta ? rmeta->methodOffset() : 0;
Q_ASSERT(!rmeta || QMetaObjectPrivate::get(rmeta)->revision >= 6);
QObjectPrivate::StaticMetaCallFunction callFunction =
rmeta ? rmeta->d.static_metacall : 0;

// 对sender和receiver上锁(mutex pool)
QOrderedMutexLocker locker(signalSlotLock(sender),
signalSlotLock(receiver));

// type为Qt::UniqueConnection时作特殊处理
// 确保connect的唯一性
if (type & Qt::UniqueConnection) {
QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;
if (connectionLists && connectionLists->count() > signal_index) {
const QObjectPrivate::Connection *c2 =
(*connectionLists)[signal_index].first;

int method_index_absolute = method_index + method_offset;

while (c2) {
if (!c2->isSlotObject && c2->receiver == receiver && c2->method() == method_index_absolute)
return 0;
c2 = c2->nextConnectionList;
}
}
type &= Qt::UniqueConnection - 1;
}
// 最后是真正的connect对象QObjectPrivate::Connection实例化
// 存储了所有的connect信息
// addConnection最终保存了这个connect操作
QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
c->sender = s;
c->signal_index = signal_index;
c->receiver = r;
c->method_relative = method_index;
c->method_offset = method_offset;
c->connectionType = type;
c->isSlotObject = false;
c->argumentTypes.store(types);
c->nextConnectionList = 0;
c->callFunction = callFunction;

QObjectPrivate::get(s)->addConnection(signal_index, c.data());
// 解锁
locker.unlock();
// connect成功后还会调用一次connectNotify函数
// connectNotify是个虚函数
// 我们可以重写connectNotify在connenct成功后进行额外的相关操作
QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
if (smethod.isValid())
s->connectNotify(smethod);

return c.take();
}


6、activate

发送信号时,实际上是调用了QMetaObject::activate函数,这是Qt用于内部实现的函数,开发者无法直接使用这个函数。

// 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);


activate最终是通过上面的最后一个函数实现的,参数分别为信号发送者对象指针、信号在元对象数据结构中的偏移量及信号索引、信号参数,可以想象,这个函数就是在前面添加的connect列表中查找并调用这个信号连接的槽或者信号,源码实现如下。

void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
{
// 信号在元对象数据结构中的实际索引
int signal_index = signalOffset + local_signal_index;
// 判断信号是否已经connect
// 判断是否注册了信号监听回调函数(用于QTest)
if (!sender->d_func()->isSignalConnected(signal_index)
&& !qt_signal_spy_callback_set.signal_begin_callback
&& !qt_signal_spy_callback_set.signal_end_callback) {
return; // nothing connected to these signals, and no spy
}
// 判断信号是否被block
if (sender->d_func()->blockSig)
return;
// 用于QTest
if (sender->d_func()->declarativeData && QAbstractDeclarativeData::signalEmitted)
QAbstractDeclarativeData::signalEmitted(sender->d_func()->declarativeData, sender,
signal_index, argv);
// 用于QTest begin
void *empty_argv[] = { 0 };
if (qt_signal_spy_callback_set.signal_begin_callback != 0) {
qt_signal_spy_callback_set.signal_begin_callback(sender, signal_index,
argv ? argv : empty_argv);
}
// HANDLE句柄即当前的线程id
// unix平台上通过pthread_self获取
Qt::HANDLE currentThreadId = QThread::currentThreadId();

{
// 上锁(多线程、异步)
QMutexLocker locker(signalSlotLock(sender));
struct ConnectionListsRef {
QObjectConnectionListVector *connectionLists;
ConnectionListsRef(QObjectConnectionListVector *connectionLists) : connectionLists(connectionLists)
{
if (connectionLists)
++connectionLists->inUse;
}
~ConnectionListsRef()
{
if (!connectionLists)
return;

--connectionLists->inUse;
Q_ASSERT(connectionLists->inUse >= 0);
if (connectionLists->orphaned) {
if (!connectionLists->inUse)
delete connectionLists;
}
}

QObjectConnectionListVector *operator->() const { return connectionLists; }
};
ConnectionListsRef connectionLists = sender->d_func()->connectionLists;
// connectionLists为空时unlock后直接return
if (!connectionLists.connectionLists) {
locker.unlock();
// 用于QTest end
if (qt_signal_spy_callback_set.signal_end_callback != 0)
qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);
return;
}
// 获取connect列表
const QObjectPrivate::ConnectionList *list;
if (signal_index < connectionLists->count())
list = &connectionLists->at(signal_index);
else
list = &connectionLists->allsignals;

do {
QObjectPrivate::Connection *c = list->first;
// 循环取得一个非空的Connection
if (!c) continue;
// We need to check against last here to ensure that signals added
// during the signal emission are not emitted in this emission.
QObjectPrivate::Connection *last = list->last;

do {
// 查找有效的receiver
if (!c->receiver)
continue;

QObject * const receiver = c->receiver;
// 判断当前线程与receiver线程是否一致
const bool receiverInSameThread = currentThreadId == receiver->d_func()->threadData->threadId;

// 根据connect类型及receiverInSameThread进行不同的处理
// 立即执行queued_activate或者放入消息队列postEvent等待后续处理
if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
|| (c->connectionType == Qt::QueuedConnection)) {
queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker);
continue;
#ifndef QT_NO_THREAD
} else if (c->connectionType == Qt::BlockingQueuedConnection) {
locker.unlock();
if (receiverInSameThread) {
qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: "
"Sender is %s(%p), receiver is %s(%p)",
sender->metaObject()->className(), sender,
receiver->metaObject()->className(), receiver);
}
// 多线程时势必要用到同步机制(锁、信号量)
QSemaphore semaphore;
QMetaCallEvent *ev = c->isSlotObject ?
new QMetaCallEvent(c->slotObj, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore) :
new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore);
QCoreApplication::postEvent(receiver, ev);
semaphore.acquire();
locker.relock();
continue;
#endif
}

QConnectionSenderSwitcher sw;

if (receiverInSameThread) {
sw.switchSender(receiver, sender, signal_index);
}
// 下面通过三种方法去调用信号连接的槽
const QObjectPrivate::StaticMetaCallFunction callFunction = c->callFunction;
const int method_relative = c->method_relative;
if (c->isSlotObject) {
c->slotObj->ref();
QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);
locker.unlock();
// 方法一 通过call调用receiver中的函数
obj->call(receiver, argv ? argv : empty_argv);

// Make sure the slot object gets destroyed before the mutex is locked again, as the
// destructor of the slot object might also lock a mutex from the signalSlotLock() mutex pool,
// and that would deadlock if the pool happens to return the same mutex.
obj.reset();

locker.relock();
} else if (callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
//we compare the vtable to make sure we are not in the destructor of the object.
locker.unlock();
const int methodIndex = c->method();
if (qt_signal_spy_callback_set.slot_begin_callback != 0)
qt_signal_spy_callback_set.slot_begin_callback(receiver, methodIndex, argv ? argv : empty_argv);
// 方法二 callFunction即moc实现的qt_static_metacall
callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);

if (qt_signal_spy_callback_set.slot_end_callback != 0)
qt_signal_spy_callback_set.slot_end_callback(receiver, methodIndex);
locker.relock();
} else {
const int method = method_relative + c->method_offset;
locker.unlock();

if (qt_signal_spy_callback_set.slot_begin_callback != 0) {
qt_signal_spy_callback_set.slot_begin_callback(receiver,
method,
argv ? argv : empty_argv);
}
// 方法三 通过metacall调用moc实现的qt_matacall
metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);

if (qt_signal_spy_callback_set.slot_end_callback != 0)
qt_signal_spy_callback_set.slot_end_callback(receiver, method);

locker.relock();
}
// orphaned为true时说明connectionLists的所属QObject已经销毁
// 尽管connectionLists是inUse但没有什么意思
// 所以跳出循环
if (connectionLists->orphaned)
break;
} while (c != last && (c = c->nextConnectionList) != 0);

if (connectionLists->orphaned)
break;
} while (list != &connectionLists->allsignals &&
//start over for all signals;
((list = &connectionLists->allsignals), true));

}
// 用于QTest(end)
if (qt_signal_spy_callback_set.signal_end_callback != 0)
qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);
}


7、over

简单来说,信号与槽的关键就是Qt的元对象系统,通过moc编译隐藏了具体的实现细节,这些内容可以在moc_xxx.cpp中查看。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: