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 }

  从以上代码可见,信号即函数。

结论

  1. Qt的信号/槽的调用不是通过指针方式调用的,而是通过索引方式来调用的。
  2. 信号也是一个函数。
  3. emit某个信号,就相当于调用这个信号connect时所关联的槽函数。
  4. 所谓信号槽,实际就是观察者模式。

注意事项

  • 信号与反应槽的定义是在类中实现的。那么,非类成员的函数,比如说一个全局函数可不可以也这样做呢?答案是不行,只有是自身定义了信号的类或其子类才可以发出该种信号。
  • 一个对象的不同信号可以连接至不同的对象。当一个信号被释放时,与之连接的反应槽将被立刻执行,就象是在程序中直接调用该函数一样。信号的释放过程是阻塞的,这意味着只有当反应槽执行完毕后该信号释放过程才返回。如果一个信号与多个反应槽连接,则这些反应槽将被顺序执行,排序过程则是任意的。因此如果程序中对这些反应槽的先后执行次序有严格要求的,应特别注意。
  • 使用信号时还应注意:信号的定义过程是在类的定义过程即头文件中实现的。为了中间编译工具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

 

posted @ 2019-03-17 22:16  冯耀耀  阅读(1120)  评论(0编辑  收藏  举报