就我个人来理解,信号槽机制与Windows下消息机制类似,消息机制是基于回调函数,Qt中用信号与槽来代替函数指针,使程序更安全简洁。信号和槽机制是 Qt 的核心机制,可以让编程人员将互不相关的对象绑定在一起,实现对象之间的通信。当对象改变其状态时,信号就由该对象发射 (emit) 出去,而且对象只负责发送信号,它不知道另一端是谁在接收这个信号。这样就做到了真正的信息封装,能确保对象被当作一个真正的软件组件来使用。槽用于接收信号,而且槽只是普通的对象成员函数。一个槽并不知道是否有任何信号与自己相连接。而且对象并不了解具体的通信机制。
所有从 QObject 或其子类 ( 例如 Qwidget ) 派生的类都能够包含信号和槽。因为信号与槽的连接是通过 QObject 的 connect() 成员函数来实现的。
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
其中 sender 与 receiver 是指向对象的指针,SIGNAL() 与 SLOT() 是转换信号与槽的宏。
从Qobject(QObject.h)源码中可看到QObject::connect的定义是这样的:
[cpp] view plaincopy
1. static bool connect(const QObject *sender, const char *signal,
2. const QObject *receiver, const char *member, Qt::ConnectionType =
3. #ifdef qdoc
4. Qt::AutoConnection
5. #else
6. #ifdef QT3_SUPPORT
7. Qt::AutoCompatConnection
8. #else
9. Qt::AutoConnection
10. #endif
11. #endif
12. );
13. inline bool connect(const QObject *sender, const char *signal,
14. const char *member, Qt::ConnectionType type =
15. #ifdef qdoc
16. Qt::AutoConnection
17. #else
18. #ifdef QT3_SUPPORT
19. Qt::AutoCompatConnection
20. #else
21. Qt::AutoConnection
22. #endif
23. #endif
24. ) const;
其中第二个connect的实现其实只有一句话:
[cpp] view plaincopy
1. { return connect(asender, asignal, this, amember, atype); }
所以对于connect函数的学习其实就是研究第一个connect函数。
我们在使用connect函数的时候一般是这样调用的:
[cpp] view plaincopy
1. connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));
这里用到了两个宏:SIGNAL() 和SLOT();通过connect声明可以知道这两个宏最后倒是得到一个const char*类型。
在qobjectdefs.h中可以看到SIGNAL() 和SLOT()的宏定义:
[cpp] view plaincopy
1. #ifndef QT_NO_DEBUG
2. # define QLOCATION "\0"__FILE__":"QTOSTRING(__LINE__)
3. # define METHOD(a) qFlagLocation("0"#a QLOCATION)
4. # define SLOT(a) qFlagLocation("1"#a QLOCATION)
5. # define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
6. #else
7. # define METHOD(a) "0"#a
8. # define SLOT(a) "1"#a
9. # define SIGNAL(a) "2"#a
10. #endif
所以这两个宏的作用就是把函数名转换为字符串并且在前面加上标识符。
比如:SIGNAL(read())展开后就是"2read()";同理SLOT(read())展开后就是"1read()"。
[cpp] view plaincopy
1. connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));
2. 实际上就是connect(sender,“2signal()”,receiver,“1slot())”;
搞明白了实际的参数就可以来看connect的真正实现过程了,在QObject.cpp文件中可以找到connect的实现代码。
[cpp] view plaincopy
1. bool QObject::connect(const QObject *sender, const char *signal,
2. const QObject *receiver, const char *method,
3. Qt::ConnectionType type)
4. {
5. {
6. const void *cbdata[] = { sender, signal, receiver, method, &type };
7. if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
8. return true;
9. }
10.
11. if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
12. qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
13. sender ? sender->metaObject()->className() : "(null)",
14. (signal && *signal) ? signal+1 : "(null)",
15. receiver ? receiver->metaObject()->className() : "(null)",
16. (method && *method) ? method+1 : "(null)");
17. return false;
18. }
19. QByteArray tmp_signal_name;
20.
21. if (!check_signal_macro(sender, signal, "connect", "bind"))
22. return false;
23. const QMetaObject *smeta = sender->metaObject();
24. const char *signal_arg = signal;
25. ++signal; //skip code
26. int signal_index = smeta->indexOfSignal(signal);
27. if (signal_index < 0) {
28. // check for normalized signatures
29. tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
30. signal = tmp_signal_name.constData() + 1;
31.
32. signal_index = smeta->indexOfSignal(signal);
33. if (signal_index < 0) {
34. err_method_notfound(sender, signal_arg, "connect");
35. err_info_about_objects("connect", sender, receiver);
36. return false;
37. }
38. }
39.
40. QByteArray tmp_method_name;
41. int membcode = extract_code(method);
42.
43. if (!check_method_code(membcode, receiver, method, "connect"))
44. return false;
45. const char *method_arg = method;
46. ++method; // skip code
47.
48. const QMetaObject *rmeta = receiver->metaObject();
49. int method_index = -1;
50. switch (membcode) {
51. case QSLOT_CODE:
52. method_index = rmeta->indexOfSlot(method);
53. break;
54. case QSIGNAL_CODE:
55. method_index = rmeta->indexOfSignal(method);
56. break;
57. }
58. if (method_index < 0) {
59. // check for normalized methods
60. tmp_method_name = QMetaObject::normalizedSignature(method);
61. method = tmp_method_name.constData();
62. switch (membcode) {
63. case QSLOT_CODE:
64. method_index = rmeta->indexOfSlot(method);
65. break;
66. case QSIGNAL_CODE:
67. method_index = rmeta->indexOfSignal(method);
68. break;
69. }
70. }
71.
72. if (method_index < 0) {
73. err_method_notfound(receiver, method_arg, "connect");
74. err_info_about_objects("connect", sender, receiver);
75. return false;
76. }
77. if (!QMetaObject::checkConnectArgs(signal, method)) {
78. qWarning("QObject::connect: Incompatible sender/receiver arguments"
79. "\n %s::%s --> %s::%s",
80. sender->metaObject()->className(), signal,
81. receiver->metaObject()->className(), method);
82. return false;
83. }
84.
85. int *types = 0;
86. if ((type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
87. && !(types = queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
88. return false;
89.
90. QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
91. const_cast<QObject*>(sender)->connectNotify(signal - 1);
92. return true;
93. }
上面是去除了debug代码的connect实现。
[cpp] view plaincopy
1. const void *cbdata[] = { sender, signal, receiver, method, &type };
2. if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
3. return true;
判断连接是否已经建立。
QInternal::ConnectCallback在qglobal.cpp中实现。
[cpp] view plaincopy
1. bool QInternal::activateCallbacks(Callback cb, void **parameters)
2. {
3. Q_ASSERT_X(cb >= 0, "QInternal::activateCallback()", "Callback id must be a valid id");
4.
5. QInternal_CallBackTable *cbt = global_callback_table();
6. if (cbt && cb < cbt->callbacks.size()) {
7. QList<qInternalCallback> callbacks = cbt->callbacks[cb];
8. bool ret = false;
9. for (int i=0; i<callbacks.size(); ++i)
10. ret |= (callbacks.at(i))(parameters);
11. return ret;
12. }
13. return false;
14. }
QInternal_CallBackTable 定义为(qglobal.cpp)
[cpp] view plaincopy
1. struct QInternal_CallBackTable {
2. QVector<QList<qInternalCallback> > callbacks;
3. };
qInternalCallback定义为(qnamespace.h)
[cpp] view plaincopy
1. typedef bool (*qInternalCallback)(void **);这是一个函数指针 返回值是bool,只有一个参数为void**。这个指针在调用registerCallback加入列表。
[cpp] view plaincopy
1. if (!check_signal_macro(sender, signal, "connect", "bind"))
2. return false;
判断signal是否合法。
在QObject.cpp文件中可以找到check_signal_macro的实现
[cpp] view plaincopy
1. static bool check_signal_macro(const QObject *sender, const char *signal,
2. const char *func, const char *op)
3. {
4. int sigcode = extract_code(signal);
5. if (sigcode != QSIGNAL_CODE) {
6. if (sigcode == QSLOT_CODE)
7. qWarning("Object::%s: Attempt to %s non-signal %s::%s",
8. func, op, sender->metaObject()->className(), signal+1);
9. else
10. qWarning("Object::%s: Use the SIGNAL macro to %s %s::%s",
11. func, op, sender->metaObject()->className(), signal);
12. return false;
13. }
14. return true;
15. }
extract的实现也在QObject中,它就是去字符串第一个字符,并且只取低2位的值。
[cpp] view plaincopy
1. static int extract_code(const char *member)
2. {
3. // extract code, ensure QMETHOD_CODE <= code <= QSIGNAL_CODE
4. return (((int)(*member) - '0') & 0x3);
5. }
这里又有两个宏:QSIGNAL_CODE 和QSLOT_CODE。它们也是在qobjectdefs.h文件中定义的。
[cpp] view plaincopy
1. #ifdef QT3_SUPPORT
2. #define METHOD_CODE 0 // member type codes
3. #define SLOT_CODE 1
4. #define SIGNAL_CODE 2
5. #endif
这个定义与之前的SIGNAL和SLOT的定义是对应的。
[cpp] view plaincopy
1. const QMetaObject *smeta = sender->metaObject();
2. const char *signal_arg = signal;
3. ++signal; //skip code
4. int signal_index = smeta->indexOfSignal(signal);
5. if (signal_index < 0) {
6. // check for normalized signatures
7. tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
8. signal = tmp_signal_name.constData() + 1;
9.
10. signal_index = smeta->indexOfSignal(signal);
11. if (signal_index < 0) {
12. err_method_notfound(sender, signal_arg, "connect");
13. err_info_about_objects("connect", sender, receiver);
14. return false;
15. }
16. }
获取signal的索引。
metaObject()是在moc_name.cpp文件中生成的。
[cpp] view plaincopy
1. return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
其中staticMetaObject也是在moc文件中定义的
[cpp] view plaincopy
1. const QMetaObject MainWindow::staticMetaObject = {
2. { &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow,
3. qt_meta_data_MainWindow, 0 }
4. };
qt_meta_stringdata_MainWindow(具体名字和类名有关)就是staticconstchar[]类型。它记录了全部的signals和slots等的函数名、返回值和参数表的信息。
qt_meta_data_MainWindow(具体名字和类名有关)是staticconstuint[]类型。它记录了每一个函数的函数名、返回值和参数表在qt_meta_stringdata_MainWindow中的索引。同时它还记录了每一个函数的类型具体在qmetaobject.cpp文件中定义。
[cpp] view plaincopy
1. enum MethodFlags {
2. AccessPrivate = 0x00,
3. AccessProtected = 0x01,
4. AccessPublic = 0x02,
5. AccessMask = 0x03, //mask
6.
7. MethodMethod = 0x00,
8. MethodSignal = 0x04,
9. MethodSlot = 0x08,
10. MethodConstructor = 0x0c,
11. MethodTypeMask = 0x0c,
12.
13. MethodCompatibility = 0x10,
14. MethodCloned = 0x20,
15. MethodScriptable = 0x40
16. };
indexOfSignal(signal);的实现在qmetaobject.cpp中。其主要作用是利用qt_meta_stringdata_MainWindow 和qt_meta_data_MainWindow查找已经定义了的signal并返回索引。
[cpp] view plaincopy
1. QByteArray tmp_method_name;
2. int membcode = extract_code(method);
3.
4. if (!check_method_code(membcode, receiver, method, "connect"))
5. return false;
6. const char *method_arg = method;
7. ++method; // skip code
8.
9. const QMetaObject *rmeta = receiver->metaObject();
10. int method_index = -1;
11. switch (membcode) {
12. case QSLOT_CODE:
13. method_index = rmeta->indexOfSlot(method);
14. break;
15. case QSIGNAL_CODE:
16. method_index = rmeta->indexOfSignal(method);
17. break;
18. }
19. if (method_index < 0) {
20. // check for normalized methods
21. tmp_method_name = QMetaObject::normalizedSignature(method);
22. method = tmp_method_name.constData();
23. switch (membcode) {
24. case QSLOT_CODE:
25. method_index = rmeta->indexOfSlot(method);
26. break;
27. case QSIGNAL_CODE:
28. method_index = rmeta->indexOfSignal(method);
29. break;
30. }
31. }
32.
33. if (method_index < 0) {
34. err_method_notfound(receiver, method_arg, "connect");
35. err_info_about_objects("connect", sender, receiver);
36. return false;
37. }
校验method并且查找它的索引。过程与signal类似。
[cpp] view plaincopy
1. if (!QMetaObject::checkConnectArgs(signal, method)) {
2. qWarning("QObject::connect: Incompatible sender/receiver arguments"
3. "\n %s::%s --> %s::%s",
4. sender->metaObject()->className(), signal,
5. receiver->metaObject()->className(), method);
6. return false;
7. }
判断signal和method是否兼容,checkConnectArgs函数的在qmetaObject.cpp文件中实现。这个函数校验了signal和method的参数。当两者的参数一致或method参数比signal参数少(method与signal前几个参数一致)的时候返回true,其它返回false。
[cpp] view plaincopy
1. int *types = 0;
2. if ((type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
3. && !(types = queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
4. return false;
如果是以发消息的方式执行method就需要对参数类型进行判断。queuedConnectionTypes在QObject.cpp实现。实际上是在QMetatype.cpp中定义了一个
static conststruct { constchar * typeName;int type;} types[];在这里记录了全部类型和名称如({"void",QMetaType::Void});Void在Qmetatype.h中定义。
[cpp] view plaincopy
1. QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
调用QMetaObject的connect函数,再次不详细写出。
[cpp] view plaincopy
1. const_cast<QObject*>(sender)->connectNotify(signal - 1);
最后调用虚函数connectNotify表示connect已经执行完成。