天天看點

Qt connect的實作原理概述正文

概述

connect實質上是将對象A的信号和對象B的槽函數進行連接配接,然後傳回一個句柄Connection。

正文

下面通過源碼來解析一下:(注意看中文注釋)

// connection表示信号槽連接配接句柄
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,
                                     const QObject *receiver, const char *method,
                                     Qt::ConnectionType type)
{
    // 校驗對象的信号和槽
    if (sender == nullptr || receiver == nullptr || signal == nullptr || method == nullptr) {
        qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
                 sender ? sender->metaObject()->className() : "(nullptr)",
                 (signal && *signal) ? signal+1 : "(nullptr)",
                 receiver ? receiver->metaObject()->className() : "(nullptr)",
                 (method && *method) ? method+1 : "(nullptr)");
        return QMetaObject::Connection(nullptr);
    }
    QByteArray tmp_signal_name;

    if (!check_signal_macro(sender, signal, "connect", "bind"))
        return QMetaObject::Connection(nullptr);
    const QMetaObject *smeta = sender->metaObject();// 發送者元對象
    const char *signal_arg = signal;
    ++signal; //skip code
    QArgumentTypeArray signalTypes;
    Q_ASSERT(QMetaObjectPrivate::get(smeta)->revision >= 7);
    QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);// 擷取信号簽名(就是轉成字元串)
    // 通過發送者元對象擷取信号的索引
    int signal_index = QMetaObjectPrivate::indexOfSignalRelative(
            &smeta, signalName, signalTypes.size(), signalTypes.constData());
    if (signal_index < 0) {
        // 重新校驗前面然後擷取信号的索引
        // check for normalized signatures
        tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
        signal = tmp_signal_name.constData() + 1;

        signalTypes.clear();
        signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
        smeta = sender->metaObject();
        signal_index = QMetaObjectPrivate::indexOfSignalRelative(
                &smeta, signalName, signalTypes.size(), signalTypes.constData());
    }
    if (signal_index < 0) {
        // 沒有索引,傳回空句柄
        err_method_notfound(sender, signal_arg, "connect");
        err_info_about_objects("connect", sender, receiver);
        return QMetaObject::Connection(nullptr);
    }
    signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);// 擷取原生方法的索引(例如傳遞destroyed()的索引,則傳回destroyed(QObject*)的索引
    signal_index += QMetaObjectPrivate::signalOffset(smeta);

    QByteArray tmp_method_name;
    int membcode = extract_code(method); // 擷取方法code
    // 校驗方法code,如果該方法code不屬于信号和槽函數,則傳回空句柄
    if (!check_method_code(membcode, receiver, method, "connect"))
        return QMetaObject::Connection(nullptr);
    const char *method_arg = method;
    ++method; // skip code

    QArgumentTypeArray methodTypes;
    QByteArray methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);// 擷取方法簽名(格式和信号前面一緻)
    const QMetaObject *rmeta = receiver->metaObject();// 接收者元對象
    int method_index_relative = -1;
    Q_ASSERT(QMetaObjectPrivate::get(rmeta)->revision >= 7);
    // 下面是擷取信号或槽函數的相對索引
    switch (membcode) {
    case QSLOT_CODE:
        method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
                &rmeta, methodName, methodTypes.size(), methodTypes.constData());
        break;
    case QSIGNAL_CODE:
        method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
                &rmeta, methodName, methodTypes.size(), methodTypes.constData());
        break;
    }
    if (method_index_relative < 0) {
        // check for normalized methods
        tmp_method_name = QMetaObject::normalizedSignature(method);
        method = tmp_method_name.constData();

        methodTypes.clear();
        methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
        // rmeta may have been modified above
        rmeta = receiver->metaObject();
        switch (membcode) {
        case QSLOT_CODE:
            method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
                    &rmeta, methodName, methodTypes.size(), methodTypes.constData());
            break;
        case QSIGNAL_CODE:
            method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
                    &rmeta, methodName, methodTypes.size(), methodTypes.constData());
            break;
        }
    }

    if (method_index_relative < 0) {
        err_method_notfound(receiver, method_arg, "connect");
        err_info_about_objects("connect", sender, receiver);
        return QMetaObject::Connection(nullptr);
    }
    // 校驗信号和槽的參數
    if (!QMetaObjectPrivate::checkConnectArgs(signalTypes.size(), signalTypes.constData(),
                                              methodTypes.size(), methodTypes.constData())) {
        qWarning("QObject::connect: Incompatible sender/receiver arguments"
                 "\n        %s::%s --> %s::%s",
                 sender->metaObject()->className(), signal,
                 receiver->metaObject()->className(), method);
        return QMetaObject::Connection(nullptr);
    }

    // 當連結類型為隊列時需要檢測信号參數的類型是否為元類型,不是的話需要注冊,沒有注冊就無法連結
    int *types = nullptr;
    if ((type == Qt::QueuedConnection)
            && !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) {
        return QMetaObject::Connection(nullptr);
    }

#ifndef QT_NO_DEBUG
    QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
    QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset());
    check_and_warn_compat(smeta, smethod, rmeta, rmethod);
#endif
    // 句柄 包含了發送者、信号索引、發送者元對象、接收者、槽函數索引、接收者元對象、連結類型、參數類型
    QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
        sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));
    return handle;
}

           

總結來說,connect前面主要是進行校驗發送者、信号索引、接收者、槽函數索引、連結類型以及參數類型。都校驗成功時,傳回句柄。

connection句柄是非常有用的,裡面包含了發送者、信号索引、發送者元對象、接收者、槽函數索引、接收者元對象、連結類型、參數類型;後續在執行信号函數的時候會檢索對應信号的所有資訊。

繼續閱讀