深入探究connect函式
connect,是QT中的連線函式,將訊號傳送者sender物件中的訊號signal與接受者receiver中的member槽函式聯絡起來。
QObject::connect的定義是這樣的:
static bool connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *member, Qt::ConnectionType =
#ifdef qdoc
Qt::AutoConnection
#else
#ifdef QT3_SUPPORT
Qt::AutoCompatConnection
#else
Qt::AutoConnection
#endif
#endif
);
inline bool connect(const QObject *sender, const char *signal,
const char *member, Qt::ConnectionType type =
#ifdef qdoc
Qt::AutoConnection
#else
#ifdef QT3_SUPPORT
Qt::AutoCompatConnection
#else
Qt::AutoConnection
#endif
#endif
) const;
其中第二個connect的實現其實只有一句話:
{ return connect(asender, asignal, this, amember, atype); }
所以對於connect函式的學習其實就是研究第一個connect函式。
在使用connect函式的時候一般是這樣呼叫的:
connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));兩個巨集:SIGNAL() 和SLOT();通過connect宣告可以知道這兩個巨集最後倒是得到一個const char*型別。
在qobjectdefs.h中可以看到SIGNAL() 和SLOT()的巨集定義:
- #ifndef QT_NO_DEBUG
- # define QLOCATION "\0"__FILE__":"QTOSTRING(__LINE__)
- # define METHOD(a) qFlagLocation("0"#a QLOCATION)
- # define SLOT(a) qFlagLocation("1"#a QLOCATION)
- # define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
- #else
- # define METHOD(a) "0"#a
- # define SLOT(a) "1"#a
- # define SIGNAL(a) "2"#a
- #endif
這兩個巨集的作用就是把函式名轉換為字串並且在前面加上識別符號。
比如:SIGNAL(read())展開後就是"2read()";同理SLOT(read())展開後就是"1read()"。
connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));
實際上就是connect(sender,“2signal()”,receiver,“1slot())”;
在QObject.cpp檔案中可以找到connect的實現程式碼,下面是去除了debug程式碼的connect實現。
- bool QObject::connect(const QObject *sender, constchar *signal,
- const QObject *receiver, constchar *method,
- Qt::ConnectionType type)
- {
- {
- constvoid *cbdata[] = { sender, signal, receiver, method, &type };
- if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
- returntrue;
- }
- 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)");
- returnfalse;
- }
- QByteArray tmp_signal_name;
- if (!check_signal_macro(sender, signal, "connect", "bind"))
- returnfalse;
- const QMetaObject *smeta = sender->metaObject();
- constchar *signal_arg = signal;
- ++signal; //skip code
- int signal_index = smeta->indexOfSignal(signal);
- if (signal_index < 0) {
- // check for normalized signatures
- tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
- signal = tmp_signal_name.constData() + 1;
- signal_index = smeta->indexOfSignal(signal);
- if (signal_index < 0) {
- err_method_notfound(sender, signal_arg, "connect");
- err_info_about_objects("connect", sender, receiver);
- returnfalse;
- }
- }
- QByteArray tmp_method_name;
- int membcode = extract_code(method);
- if (!check_method_code(membcode, receiver, method, "connect"))
- returnfalse;
- constchar *method_arg = method;
- ++method; // skip code
- const QMetaObject *rmeta = receiver->metaObject();
- int method_index = -1;
- switch (membcode) {
- case QSLOT_CODE:
- method_index = rmeta->indexOfSlot(method);
- break;
- case QSIGNAL_CODE:
- method_index = rmeta->indexOfSignal(method);
- break;
- }
- if (method_index < 0) {
- // check for normalized methods
- tmp_method_name = QMetaObject::normalizedSignature(method);
- method = tmp_method_name.constData();
- switch (membcode) {
- case QSLOT_CODE:
- method_index = rmeta->indexOfSlot(method);
- break;
- case QSIGNAL_CODE:
- method_index = rmeta->indexOfSignal(method);
- break;
- }
- }
- if (method_index < 0) {
- err_method_notfound(receiver, method_arg, "connect");
- err_info_about_objects("connect", sender, receiver);
- returnfalse;
- }
- if (!QMetaObject::checkConnectArgs(signal, method)) {
- qWarning("QObject::connect: Incompatible sender/receiver arguments"
- "\n %s::%s --> %s::%s",
- sender->metaObject()->className(), signal,
- receiver->metaObject()->className(), method);
- returnfalse;
- }
- int *types = 0;
- if ((type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
- && !(types = queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
- returnfalse;
- QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
- const_cast<QObject*>(sender)->connectNotify(signal - 1);
- returntrue;
- }
判斷連線是否已經建立:
- constvoid *cbdata[] = { sender, signal, receiver, method, &type };
- if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
- returntrue;
QInternal::ConnectCallback在qglobal.cpp中實現:
- bool QInternal::activateCallbacks(Callback cb, void **parameters)
- {
- Q_ASSERT_X(cb >= 0, "QInternal::activateCallback()", "Callback id must be a valid id");
- QInternal_CallBackTable *cbt = global_callback_table();
- if (cbt && cb < cbt->callbacks.size()) {
- QList<qInternalCallback> callbacks = cbt->callbacks[cb];
- bool ret = false;
- for (int i=0; i<callbacks.size(); ++i)
- ret |= (callbacks.at(i))(parameters);
- return ret;
- }
- returnfalse;
- }
QInternal_CallBackTable 定義為(qglobal.cpp):
- struct QInternal_CallBackTable {
- QVector<QList<qInternalCallback> > callbacks;
- };
qInternalCallback定義為(qnamespace.h):
- typedefbool (*qInternalCallback)(void **);
//這是一個函式指標 返回值是bool,只有一個引數為void**。這個指標在呼叫registerCallback加入列表。
typedef bool (*qInternalCallback)(void **);這是一個函式指標 返回值是bool,只有一個引數為void**。這個指標在呼叫registerCallback加入列表。
判斷signal是否合法
- if (!check_signal_macro(sender, signal, "connect", "bind"))
- returnfalse;
在QObject.cpp檔案中可以找到check_signal_macro的實現
- staticbool check_signal_macro(const QObject *sender, constchar *signal,
- constchar *func, constchar *op)
- {
- int sigcode = extract_code(signal);
- if (sigcode != QSIGNAL_CODE) {
- if (sigcode == QSLOT_CODE)
- qWarning("Object::%s: Attempt to %s non-signal %s::%s",
- func, op, sender->metaObject()->className(), signal+1);
- else
- qWarning("Object::%s: Use the SIGNAL macro to %s %s::%s",
- func, op, sender->metaObject()->className(), signal);
- returnfalse;
- }
- returntrue;
- }
extract的實現也在QObject中,它就是去字串第一個字元,並且只取低2位的值。
- staticint extract_code(constchar *member)
- {
- // extract code, ensure QMETHOD_CODE <= code <= QSIGNAL_CODE
- return (((int)(*member) - '0') & 0x3);
- }
這裡有兩個巨集:QSIGNAL_CODE 和QSLOT_CODE。它們也是在qobjectdefs.h檔案中定義的。這個定義與之前的SIGNAL和SLOT的定義是對應的。
- #ifdef QT3_SUPPORT
- #define METHOD_CODE 0 // member type codes
- #define SLOT_CODE 1
- #define SIGNAL_CODE 2
- #endif
獲取signal的索引,其中metaObject()是在moc_name.cpp檔案中生成的。
- const QMetaObject *smeta = sender->metaObject();
- constchar *signal_arg = signal;
- ++signal; //skip code
- int signal_index = smeta->indexOfSignal(signal);
- if (signal_index < 0) {
- // check for normalized signatures
- tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
- signal = tmp_signal_name.constData() + 1;
- signal_index = smeta->indexOfSignal(signal);
- if (signal_index < 0) {
- err_method_notfound(sender, signal_arg, "connect");
- err_info_about_objects("connect", sender, receiver);
- returnfalse;
- }
- }
return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
其中staticMetaObject也是在moc檔案中定義的
- const QMetaObject MainWindow::staticMetaObject = {
- { &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow,
- qt_meta_data_MainWindow, 0 }
- };
const QMetaObject MainWindow::staticMetaObject = { { &QMainWindow::staticMetaObject, qt_meta_stringdata_MainWindow, qt_meta_data_MainWindow, 0 } };
qt_meta_stringdata_MainWindow(具體名字和類名有關)就是staticconstchar[]型別。它記錄了全部的signals和slots等的函式名、返回值和引數表的資訊。
qt_meta_data_MainWindow(具體名字和類名有關)是staticconstuint[]型別。它記錄了每一個函式的函式名、返回值和引數表在qt_meta_stringdata_MainWindow中的索引。同時它還記錄了每一個函式的型別具體在qmetaobject.cpp檔案中定義。
- enum MethodFlags {
- AccessPrivate = 0x00,
- AccessProtected = 0x01,
- AccessPublic = 0x02,
- AccessMask = 0x03, //mask
- MethodMethod = 0x00,
- MethodSignal = 0x04,
- MethodSlot = 0x08,
- MethodConstructor = 0x0c,
- MethodTypeMask = 0x0c,
- MethodCompatibility = 0x10,
- MethodCloned = 0x20,
- MethodScriptable = 0x40
- };
enum MethodFlags {
AccessPrivate = 0x00,
AccessProtected = 0x01,
AccessPublic = 0x02,
AccessMask = 0x03, //mask
MethodMethod = 0x00,
MethodSignal = 0x04,
MethodSlot = 0x08,
MethodConstructor = 0x0c,
MethodTypeMask = 0x0c,
MethodCompatibility = 0x10,
MethodCloned = 0x20,
MethodScriptable = 0x40
};
indexOfSignal(signal);的實現在qmetaobject.cpp中。其主要作用是利用qt_meta_stringdata_MainWindow 和qt_meta_data_MainWindow查詢已經定義了的signal並返回索引。
- QByteArray tmp_method_name;
- int membcode = extract_code(method);
- if (!check_method_code(membcode, receiver, method, "connect"))
- returnfalse;
- constchar *method_arg = method;
- ++method; // skip code
- const QMetaObject *rmeta = receiver->metaObject();
- int method_index = -1;
- switch (membcode) {
- case QSLOT_CODE:
- method_index = rmeta->indexOfSlot(method);
- break;
- case QSIGNAL_CODE:
- method_index = rmeta->indexOfSignal(method);
- break;
- }
- if (method_index < 0) {
- // check for normalized methods
- tmp_method_name = QMetaObject::normalizedSignature(method);
- method = tmp_method_name.constData();
- switch (membcode) {
- case QSLOT_CODE:
- method_index = rmeta->indexOfSlot(method);
- break;
- case QSIGNAL_CODE:
- method_index = rmeta->indexOfSignal(method);
- break;
- }
- }
- if (method_index < 0) {
- err_method_notfound(receiver, method_arg, "connect");
- err_info_about_objects("connect", sender, receiver);
- returnfalse;
- }
QByteArray tmp_method_name;
int membcode = extract_code(method);
if (!check_method_code(membcode, receiver, method, "connect"))
return false;
const char *method_arg = method;
++method; // skip code
const QMetaObject *rmeta = receiver->metaObject();
int method_index = -1;
switch (membcode) {
case QSLOT_CODE:
method_index = rmeta->indexOfSlot(method);
break;
case QSIGNAL_CODE:
method_index = rmeta->indexOfSignal(method);
break;
}
if (method_index < 0) {
// check for normalized methods
tmp_method_name = QMetaObject::normalizedSignature(method);
method = tmp_method_name.constData();
switch (membcode) {
case QSLOT_CODE:
method_index = rmeta->indexOfSlot(method);
break;
case QSIGNAL_CODE:
method_index = rmeta->indexOfSignal(method);
break;
}
}
if (method_index < 0) {
err_method_notfound(receiver, method_arg, "connect");
err_info_about_objects("connect", sender, receiver);
return false;
}
校驗method並且查詢它的索引。過程與signal類似。
- if (!QMetaObject::checkConnectArgs(signal, method)) {
- qWarning("QObject::connect: Incompatible sender/receiver arguments"
- "\n %s::%s --> %s::%s",
- sender->metaObject()->className(), signal,
- receiver->metaObject()->className(), method);
- returnfalse;
- }
if (!QMetaObject::checkConnectArgs(signal, method)) {
qWarning("QObject::connect: Incompatible sender/receiver arguments"
"\n %s::%s --> %s::%s",
sender->metaObject()->className(), signal,
receiver->metaObject()->className(), method);
return false;
}
判斷signal和method是否相容,checkConnectArgs函式的在qmetaObject.cpp檔案中實現。這個函式校驗了signal和method的引數。當兩者的引數一致或method引數比signal引數少(method與signal前幾個引數一致)的時候返回true,其它返回false。
- int *types = 0;
- if ((type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
- && !(types = queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
- returnfalse;
int *types = 0;
if ((type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
&& !(types = queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
return false;
如果是以發訊息的方式執行method就需要對引數型別進行判斷。queuedConnectionTypes在QObject.cpp實現。實際上是在QMetatype.cpp中定義了一個
staticconststruct{constchar*typeName;inttype;}types[];在這裡記錄了全部型別和名稱如({"void",QMetaType::Void});Void在Qmetatype.h中定義。
- QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
呼叫QMetaObject的connect函式,不詳細寫出。
- const_cast<QObject*>(sender)->connectNotify(signal - 1);
const_cast<QObject*>(sender)->connectNotify(signal - 1);
最後呼叫虛擬函式connectNotify表示connect已經執行完成。