先上示例代码直观地感受一下 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 就是通过信号与槽实现的。
信号(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 进行包装,但后者更好用。
上面提到了编译结果,有两个文件比较奇怪,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 标记结束。
示例中的宏 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
使用信号前,首先要进行 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();
- }
发送信号时,实际上是调用了 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);
- }
简单来说,信号与槽的关键就是 Qt 的元对象系统,通过 moc 编译隐藏了具体的实现细节,这些内容可以在 moc_xxx.cpp 中查看。
来源: http://blog.csdn.net/ieearth/article/details/74025072