Qt源码学习之信号槽
Qt源码版本
Qt 5.12.0
moc文件
Qt在编译之前会分析源文件,当发现包含了Q_OBJECT宏,则会生成另外一个标准的C++源文件(包含Q_OBJECT宏实现的代码,文件名为moc_+原文件名),该文件同样进入编译系统,最终被链接到二进制代码中去。此时,Qt将自己增加的扩展转换成了标准的C++文件,moc全称是Meta-Object Compiler,也就是“元对象编译器”。
Q_OBJECT的宏定义
位置:qobjectdefs.h
1 /* qmake ignore Q_OBJECT */ 2 #define Q_OBJECT \ 3 public: \ 4 QT_WARNING_PUSH \ 5 Q_OBJECT_NO_OVERRIDE_WARNING \ 6 static const QMetaObject staticMetaObject; \ 7 virtual const QMetaObject *metaObject() const; \ 8 virtual void *qt_metacast(const char *); \ 9 virtual int qt_metacall(QMetaObject::Call, int, void **); \ 10 QT_TR_FUNCTIONS \ 11 private: \ 12 Q_OBJECT_NO_ATTRIBUTES_WARNING \ 13 Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \ 14 QT_WARNING_POP \ 15 struct QPrivateSignal {}; \ 16 QT_ANNOTATE_CLASS(qt_qobject, "")
Moc文件分析
测试代码
1 #include <QObject> 2 class CTestMoc : public QObject 3 { 4 Q_OBJECT 5 public: 6 CTestMoc(){} 7 ~CTestMoc(){} 8 signals: 9 void Test1(); 10 void Test2(int iTemp); 11 private slots: 12 void OnTest1(){} 13 void OnTest2(int iTemp){} 14 };
Moc代码
测试代码编译之后,生成moc_ctestmoc.cpp文件,包含几个重要的数据结构。
第一个结构
1 struct qt_meta_stringdata_CTestMoc_t { 2 QByteArrayData data[7]; 3 char stringdata0[44]; 4 };
data字段是一个由数组组成的数组,数组大小取决于信号/槽个数。这个数组在调用QObject::connect时用来匹配信号名或槽名。
stringdata存放的是字符资源,存放全部的信号名、槽名、类名。
1 static const qt_meta_stringdata_CTestMoc_t qt_meta_stringdata_CTestMoc = { 2 { 3 QT_MOC_LITERAL(0, 0, 8), // "CTestMoc" 4 QT_MOC_LITERAL(1, 9, 5), // "Test1" 5 QT_MOC_LITERAL(2, 15, 0), // "" 6 QT_MOC_LITERAL(3, 16, 5), // "Test2" 7 QT_MOC_LITERAL(4, 22, 5), // "iTemp" 8 QT_MOC_LITERAL(5, 28, 7), // "OnTest1" 9 QT_MOC_LITERAL(6, 36, 7) // "OnTest2" 10 11 }, 12 "CTestMoc\0Test1\0\0Test2\0iTemp\0OnTest1\0" 13 "OnTest2" 14 };
qt_meta_stringdata_CTestMoc_t是一个qt_meta_stringdata_CTestMoc的实例
QT_MOC_LITERAL(0, 0, 8),这个宏生成一个byte数组,第一参数是索引,可以看到索引是由 0-6共7个组成,对应的是data字段的长度7;第二个参数是在stringdata字段中的开始位置;第三个参数是长度。QT_MOC_LITERAL(0, 0, 8) 索引是0, 开始位置是0,长度是8,对应的字符是"CTestMoc",后面的以此类推。
第二个结构
1 static const uint qt_meta_data_CTestMoc[] = { 2 3 // content: 4 8, // revision 5 0, // classname 6 0, 0, // classinfo 7 4, 14, // methods 8 0, 0, // properties 9 0, 0, // enums/sets 10 0, 0, // constructors 11 0, // flags 12 2, // signalCount 13 14 // signals: name, argc, parameters, tag, flags 15 1, 0, 34, 2, 0x06 /* Public */, 16 3, 1, 35, 2, 0x06 /* Public */, 17 18 // slots: name, argc, parameters, tag, flags 19 5, 0, 38, 2, 0x08 /* Private */, 20 6, 1, 39, 2, 0x08 /* Private */, 21 22 // signals: parameters 23 QMetaType::Void, 24 QMetaType::Void, QMetaType::Int, 4, 25 26 // slots: parameters 27 QMetaType::Void, 28 QMetaType::Void, QMetaType::Int, 4, 29 30 0 // eod 31 };
该数组的前14个uint 描述的是元对象的私有信息,定义在qmetaobject_p.h文件的QMetaObjectPrivate结构体当中;这个结构体中“4, 14, // methods”描述的是信号/槽的个数和在表中的偏移量,即14个uint之后是信号/槽的信息。
从qt_meta_data_CTestMoc这个表中可以看到每描述一个信号或槽需要5个uint。
例如,从表的第14个uint开始描述的信号信息
// signals: name, argc, parameters, tag, flags
1, 0, 34, 2, 0x06,
3, 1, 35, 2, 0x06,
name:对应的是qt_meta_stringdata_CTestMoc 索引,例如1 对应的是Test1
argc:参数个数
parameters : 参数的在qt_meta_data_CTestMoc这个表中的索引位置。
// signals: parameters
QMetaType::Void,
QMetaType::Void, QMetaType::Int, 4,
void 是信号的返回值,QMetaType::Int是参数类型, 4 是参数名,在qt_meta_stringdata_CTestMoc中的索引值。
tag:这个字段的数值对应的是qt_meta_stringdata_CTestMoc 索引,在这个moc文件里对应的是一个空字符串,具体怎么用,在源代码里没看懂。
flags:是一个特征值,是在 enum MethodFlags 枚举中定义。
1 enum MethodFlags { 2 AccessPrivate = 0x00, 3 AccessProtected = 0x01, 4 AccessPublic = 0x02, 5 AccessMask = 0x03, //mask 6 MethodMethod = 0x00, 7 MethodSignal = 0x04, 8 MethodSlot = 0x08, 9 MethodConstructor = 0x0c, 10 MethodTypeMask = 0x0c, 11 MethodCompatibility = 0x10, 12 MethodCloned = 0x20, 13 MethodScriptable = 0x40, 14 MethodRevisioned = 0x80 15 };
可以看到,槽对应的是MethodSlot 0x08, 信号对应的是MethodSignal 和AccessPublic 相或。
第三部分
1 void CTestMoc::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) 2 { 3 if (_c == QMetaObject::InvokeMetaMethod) { 4 CTestMoc *_t = static_cast<CTestMoc *>(_o); 5 Q_UNUSED(_t) 6 switch (_id) { 7 case 0: _t->Test1(); break; 8 case 1: _t->Test2((*reinterpret_cast< int(*)>(_a[1]))); break; 9 case 2: _t->OnTest1(); break; 10 case 3: _t->OnTest2((*reinterpret_cast< int(*)>(_a[1]))); break; 11 default: ; 12 } 13 } else if (_c == QMetaObject::IndexOfMethod) { 14 int *result = reinterpret_cast<int *>(_a[0]); 15 { 16 using _t = void (CTestMoc::*)(); 17 if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&CTestMoc::Test1)) { 18 *result = 0; 19 return; 20 } 21 } 22 { 23 using _t = void (CTestMoc::*)(int ); 24 if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&CTestMoc::Test2)) { 25 *result = 1; 26 return; 27 } 28 } 29 } 30 }
qt_metacall方法通过索引调用其它内部方法。Qt动态机制不采用指针,而由索引实现。实际调用方法的工作由编译器实现。这使得信号和槽的机制执行效率比较高。
参数由一个指向指针数组的指针进行传递,并在调用方法时进行适当的转换。使用指针是将不同类型的参数放在一个数组的唯一办法。参数索引从1开始,因为0号代表函数返回值。
第四部分
1 QT_INIT_METAOBJECT const QMetaObject CTestMoc::staticMetaObject = { { 2 &QObject::staticMetaObject, 3 qt_meta_stringdata_CTestMoc.data, 4 qt_meta_data_CTestMoc, 5 qt_static_metacall, 6 nullptr, 7 nullptr 8 } };
这个静态变量保存了moc文件的信号/槽的调用索引信息。
在信号/槽绑定的时候,就是通过这些信息建立绑定关系。
第五部分
1 // SIGNAL 0 2 void CTestMoc::Test1() 3 { 4 QMetaObject::activate(this, &staticMetaObject, 0, nullptr); 5 } 6 7 // SIGNAL 1 8 void CTestMoc::Test2(int _t1) 9 { 10 void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; 11 QMetaObject::activate(this, &staticMetaObject, 1, _a); 12 }
从以上代码可见,信号即函数。
结论
- Qt的信号/槽的调用不是通过指针方式调用的,而是通过索引方式来调用的。
- 信号也是一个函数。
- emit某个信号,就相当于调用这个信号connect时所关联的槽函数。
- 所谓信号槽,实际就是观察者模式。
注意事项
- 信号与反应槽的定义是在类中实现的。那么,非类成员的函数,比如说一个全局函数可不可以也这样做呢?答案是不行,只有是自身定义了信号的类或其子类才可以发出该种信号。
- 一个对象的不同信号可以连接至不同的对象。当一个信号被释放时,与之连接的反应槽将被立刻执行,就象是在程序中直接调用该函数一样。信号的释放过程是阻塞的,这意味着只有当反应槽执行完毕后该信号释放过程才返回。如果一个信号与多个反应槽连接,则这些反应槽将被顺序执行,排序过程则是任意的。因此如果程序中对这些反应槽的先后执行次序有严格要求的,应特别注意。
- 使用信号时还应注意:信号的定义过程是在类的定义过程即头文件中实现的。为了中间编译工具moc的正常运行,不要在源文件(.cpp)中定义信号,同时信号本身不应返回任何数据类型,即是空值(void)。如果你要设计一个通用的类或控件,则在信号或反应槽的参数中应尽可能使用常规数据以增加通用性。如代码中valueChanged的参数为int型,如果它使用了特殊类型如QRangeControl::Range,那么这种信号只能与RangeControl中的反应槽连接。如前所述,反应槽也是常规函数,与未定义slots的用户函数在执行上没有任何区别。
- 但在程序中不可把信号与常规函数连接在一起,否则信号的释放不会引起对应函数的执行。要命的是中间编译程序moc并不会对此种情况报错,C++编译程序更不会报错。初学者比较容易忽略这一点,往往是程序编好了没有错误,逻辑上也正确,但运行时就是不按自己的意愿出现结果,这时候应检查一下是不是这方面的疏忽。
- 信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。
- SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串。
测试Moc全部代码
1 /**************************************************************************** 2 ** Meta object code from reading C++ file 'ctestmoc.h' 3 ** 4 ** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.0) 5 ** 6 ** WARNING! All changes made in this file will be lost! 7 *****************************************************************************/ 8 9 #include "../CTestMoc/ctestmoc.h" 10 #include <QtCore/qbytearray.h> 11 #include <QtCore/qmetatype.h> 12 #if !defined(Q_MOC_OUTPUT_REVISION) 13 #error "The header file 'ctestmoc.h' doesn't include <QObject>." 14 #elif Q_MOC_OUTPUT_REVISION != 67 15 #error "This file was generated using the moc from 5.12.0. It" 16 #error "cannot be used with the include files from this version of Qt." 17 #error "(The moc has changed too much.)" 18 #endif 19 20 QT_BEGIN_MOC_NAMESPACE 21 QT_WARNING_PUSH 22 QT_WARNING_DISABLE_DEPRECATED 23 struct qt_meta_stringdata_CTestMoc_t { 24 QByteArrayData data[7]; 25 char stringdata0[44]; 26 }; 27 #define QT_MOC_LITERAL(idx, ofs, len) \ 28 Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ 29 qptrdiff(offsetof(qt_meta_stringdata_CTestMoc_t, stringdata0) + ofs \ 30 - idx * sizeof(QByteArrayData)) \ 31 ) 32 static const qt_meta_stringdata_CTestMoc_t qt_meta_stringdata_CTestMoc = { 33 { 34 QT_MOC_LITERAL(0, 0, 8), // "CTestMoc" 35 QT_MOC_LITERAL(1, 9, 5), // "Test1" 36 QT_MOC_LITERAL(2, 15, 0), // "" 37 QT_MOC_LITERAL(3, 16, 5), // "Test2" 38 QT_MOC_LITERAL(4, 22, 5), // "iTemp" 39 QT_MOC_LITERAL(5, 28, 7), // "OnTest1" 40 QT_MOC_LITERAL(6, 36, 7) // "OnTest2" 41 42 }, 43 "CTestMoc\0Test1\0\0Test2\0iTemp\0OnTest1\0" 44 "OnTest2" 45 }; 46 #undef QT_MOC_LITERAL 47 48 static const uint qt_meta_data_CTestMoc[] = { 49 50 // content: 51 8, // revision 52 0, // classname 53 0, 0, // classinfo 54 4, 14, // methods 55 0, 0, // properties 56 0, 0, // enums/sets 57 0, 0, // constructors 58 0, // flags 59 2, // signalCount 60 61 // signals: name, argc, parameters, tag, flags 62 1, 0, 34, 2, 0x06 /* Public */, 63 3, 1, 35, 2, 0x06 /* Public */, 64 65 // slots: name, argc, parameters, tag, flags 66 5, 0, 38, 2, 0x08 /* Private */, 67 6, 1, 39, 2, 0x08 /* Private */, 68 69 // signals: parameters 70 QMetaType::Void, 71 QMetaType::Void, QMetaType::Int, 4, 72 73 // slots: parameters 74 QMetaType::Void, 75 QMetaType::Void, QMetaType::Int, 4, 76 77 0 // eod 78 }; 79 80 void CTestMoc::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) 81 { 82 if (_c == QMetaObject::InvokeMetaMethod) { 83 CTestMoc *_t = static_cast<CTestMoc *>(_o); 84 Q_UNUSED(_t) 85 switch (_id) { 86 case 0: _t->Test1(); break; 87 case 1: _t->Test2((*reinterpret_cast< int(*)>(_a[1]))); break; 88 case 2: _t->OnTest1(); break; 89 case 3: _t->OnTest2((*reinterpret_cast< int(*)>(_a[1]))); break; 90 default: ; 91 } 92 } else if (_c == QMetaObject::IndexOfMethod) { 93 int *result = reinterpret_cast<int *>(_a[0]); 94 { 95 using _t = void (CTestMoc::*)(); 96 if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&CTestMoc::Test1)) { 97 *result = 0; 98 return; 99 } 100 } 101 { 102 using _t = void (CTestMoc::*)(int ); 103 if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&CTestMoc::Test2)) { 104 *result = 1; 105 return; 106 } 107 } 108 } 109 } 110 111 QT_INIT_METAOBJECT const QMetaObject CTestMoc::staticMetaObject = { { 112 &QObject::staticMetaObject, 113 qt_meta_stringdata_CTestMoc.data, 114 qt_meta_data_CTestMoc, 115 qt_static_metacall, 116 nullptr, 117 nullptr 118 } }; 119 120 121 const QMetaObject *CTestMoc::metaObject() const 122 { 123 return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject; 124 } 125 126 void *CTestMoc::qt_metacast(const char *_clname) 127 { 128 if (!_clname) return nullptr; 129 if (!strcmp(_clname, qt_meta_stringdata_CTestMoc.stringdata0)) 130 return static_cast<void*>(this); 131 return QObject::qt_metacast(_clname); 132 } 133 134 int CTestMoc::qt_metacall(QMetaObject::Call _c, int _id, void **_a) 135 { 136 _id = QObject::qt_metacall(_c, _id, _a); 137 if (_id < 0) 138 return _id; 139 if (_c == QMetaObject::InvokeMetaMethod) { 140 if (_id < 4) 141 qt_static_metacall(this, _c, _id, _a); 142 _id -= 4; 143 } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) { 144 if (_id < 4) 145 *reinterpret_cast<int*>(_a[0]) = -1; 146 _id -= 4; 147 } 148 return _id; 149 } 150 151 // SIGNAL 0 152 void CTestMoc::Test1() 153 { 154 QMetaObject::activate(this, &staticMetaObject, 0, nullptr); 155 } 156 157 // SIGNAL 1 158 void CTestMoc::Test2(int _t1) 159 { 160 void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) }; 161 QMetaObject::activate(this, &staticMetaObject, 1, _a); 162 } 163 QT_WARNING_POP 164 QT_END_MOC_NAMESPACE