QT QObject::connect函数的学习

时间:2024-09-12 15:37:26
从Qobject(QObject.h)源码中可以看到QObject::connect的定义是这样的:
  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的实现其实只有一句话:

  1. { return connect(asender, asignal, this, amember, atype); }

所以对于connect函数的学习其实就是研究第一个connect函数。

我们在使用connect函数的时候一般是这样调用的:

  1. connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));

这里用到了两个宏:SIGNAL() 和SLOT();通过connect声明可以知道这两个宏最后倒是得到一个const char*类型。
在qobjectdefs.h中可以看到SIGNAL() 和SLOT()的宏定义:

  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()"。

  1. connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));
  2. 实际上就是connect(sender,“2signal()”,receiver,“1slot())”;

搞明白了实际的参数就可以来看connect的真正实现过程了,在QObject.cpp文件中可以找到connect的实现代码。

  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. if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
  11. qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
  12. sender ? sender->metaObject()->className() : "(null)",
  13. (signal && *signal) ? signal+1 : "(null)",
  14. receiver ? receiver->metaObject()->className() : "(null)",
  15. (method && *method) ? method+1 : "(null)");
  16. return false;
  17. }
  18. QByteArray tmp_signal_name;
  19. if (!check_signal_macro(sender, signal, "connect", "bind"))
  20. return false;
  21. const QMetaObject *smeta = sender->metaObject();
  22. const char *signal_arg = signal;
  23. ++signal; //skip code
  24. int signal_index = smeta->indexOfSignal(signal);
  25. if (signal_index < 0) {
  26. // check for normalized signatures
  27. tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
  28. signal = tmp_signal_name.constData() + 1;
  29. signal_index = smeta->indexOfSignal(signal);
  30. if (signal_index < 0) {
  31. err_method_notfound(sender, signal_arg, "connect");
  32. err_info_about_objects("connect", sender, receiver);
  33. return false;
  34. }
  35. }
  36. QByteArray tmp_method_name;
  37. int membcode = extract_code(method);
  38. if (!check_method_code(membcode, receiver, method, "connect"))
  39. return false;
  40. const char *method_arg = method;
  41. ++method; // skip code
  42. const QMetaObject *rmeta = receiver->metaObject();
  43. int method_index = -1;
  44. switch (membcode) {
  45. case QSLOT_CODE:
  46. method_index = rmeta->indexOfSlot(method);
  47. break;
  48. case QSIGNAL_CODE:
  49. method_index = rmeta->indexOfSignal(method);
  50. break;
  51. }
  52. if (method_index < 0) {
  53. // check for normalized methods
  54. tmp_method_name = QMetaObject::normalizedSignature(method);
  55. method = tmp_method_name.constData();
  56. switch (membcode) {
  57. case QSLOT_CODE:
  58. method_index = rmeta->indexOfSlot(method);
  59. break;
  60. case QSIGNAL_CODE:
  61. method_index = rmeta->indexOfSignal(method);
  62. break;
  63. }
  64. }
  65. if (method_index < 0) {
  66. err_method_notfound(receiver, method_arg, "connect");
  67. err_info_about_objects("connect", sender, receiver);
  68. return false;
  69. }
  70. if (!QMetaObject::checkConnectArgs(signal, method)) {
  71. qWarning("QObject::connect: Incompatible sender/receiver arguments"
  72. "\n        %s::%s --> %s::%s",
  73. sender->metaObject()->className(), signal,
  74. receiver->metaObject()->className(), method);
  75. return false;
  76. }
  77. int *types = 0;
  78. if ((type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
  79. && !(types = queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
  80. return false;
  81. QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
  82. const_cast<QObject*>(sender)->connectNotify(signal - 1);
  83. return true;
  84. }

上面是去除了debug代码的connect实现。

  1. const void *cbdata[] = { sender, signal, receiver, method, &type };
  2. if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
  3. return true;

判断连接是否已经建立。
QInternal::ConnectCallback在qglobal.cpp中实现。

  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. QInternal_CallBackTable *cbt = global_callback_table();
  5. if (cbt && cb < cbt->callbacks.size()) {
  6. QList<qInternalCallback> callbacks = cbt->callbacks[cb];
  7. bool ret = false;
  8. for (int i=0; i<callbacks.size(); ++i)
  9. ret |= (callbacks.at(i))(parameters);
  10. return ret;
  11. }
  12. return false;
  13. }

QInternal_CallBackTable 定义为(qglobal.cpp)

  1. struct QInternal_CallBackTable {
  2. QVector<QList<qInternalCallback> > callbacks;
  3. };

qInternalCallback定义为(qnamespace.h)

  1. typedef bool (*qInternalCallback)(void **);这是一个函数指针 返回值是bool,只有一个参数为void**。这个指针在调用registerCallback加入列表。
  1. if (!check_signal_macro(sender, signal, "connect", "bind"))
  2. return false;

判断signal是否合法。

在QObject.cpp文件中可以找到check_signal_macro的实现

  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位的值。

  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文件中定义的。

  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的定义是对应的。

  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. signal_index = smeta->indexOfSignal(signal);
  10. if (signal_index < 0) {
  11. err_method_notfound(sender, signal_arg, "connect");
  12. err_info_about_objects("connect", sender, receiver);
  13. return false;
  14. }
  15. }

获取signal的索引。

metaObject()是在moc_name.cpp文件中生成的。

  1. return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;

其中staticMetaObject也是在moc文件中定义的

  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文件中定义。

  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. };

indexOfSignal(signal);的实现在qmetaobject.cpp中。其主要作用是利用qt_meta_stringdata_MainWindow 和qt_meta_data_MainWindow查找已经定义了的signal并返回索引。

  1. QByteArray tmp_method_name;
  2. int membcode = extract_code(method);
  3. if (!check_method_code(membcode, receiver, method, "connect"))
  4. return false;
  5. const char *method_arg = method;
  6. ++method; // skip code
  7. const QMetaObject *rmeta = receiver->metaObject();
  8. int method_index = -1;
  9. switch (membcode) {
  10. case QSLOT_CODE:
  11. method_index = rmeta->indexOfSlot(method);
  12. break;
  13. case QSIGNAL_CODE:
  14. method_index = rmeta->indexOfSignal(method);
  15. break;
  16. }
  17. if (method_index < 0) {
  18. // check for normalized methods
  19. tmp_method_name = QMetaObject::normalizedSignature(method);
  20. method = tmp_method_name.constData();
  21. switch (membcode) {
  22. case QSLOT_CODE:
  23. method_index = rmeta->indexOfSlot(method);
  24. break;
  25. case QSIGNAL_CODE:
  26. method_index = rmeta->indexOfSignal(method);
  27. break;
  28. }
  29. }
  30. if (method_index < 0) {
  31. err_method_notfound(receiver, method_arg, "connect");
  32. err_info_about_objects("connect", sender, receiver);
  33. return false;
  34. }

校验method并且查找它的索引。过程与signal类似。

  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。

  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中定义。

  1. QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);

调用QMetaObject的connect函数,再次不详细写出。

  1. const_cast<QObject*>(sender)->connectNotify(signal - 1);

最后调用虚函数connectNotify表示connect已经执行完成。