bool QMetaMethod::invoke

MarsCactus發表於2024-10-27
bool QMetaMethod::invoke(QObject *object,
                         Qt::ConnectionType connectionType,
                         QGenericReturnArgument returnValue,
                         QGenericArgument val0,
                         QGenericArgument val1,
                         QGenericArgument val2,
                         QGenericArgument val3,
                         QGenericArgument val4,
                         QGenericArgument val5,
                         QGenericArgument val6,
                         QGenericArgument val7,
                         QGenericArgument val8,
                         QGenericArgument val9) const
{
    if (!object || !mobj)
        return false;

    // check argument count (we don't allow invoking a method if given too few arguments)
    const char *typeNames[] = {
        returnValue.name(),
        val0.name(),
        val1.name(),
        val2.name(),
        val3.name(),
        val4.name(),
        val5.name(),
        val6.name(),
        val7.name(),
        val8.name(),
        val9.name()
    };
    void *param[] = {
        returnValue.data(),
        val0.data(),
        val1.data(),
        val2.data(),
        val3.data(),
        val4.data(),
        val5.data(),
        val6.data(),
        val7.data(),
        val8.data(),
        val9.data()
    };

    int paramCount;
    for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {
        if (qstrlen(typeNames[paramCount]) <= 0)
            break;
    }
    return invokeImpl(*this, object, connectionType, paramCount, param, typeNames, nullptr);
}

bool QMetaMethod::invokeImpl(QMetaMethod self, void *target, Qt::ConnectionType connectionType,
                             qsizetype paramCount, const void *const *parameters,
                             const char *const *typeNames,
                             const QtPrivate::QMetaTypeInterface *const *metaTypes)
{
    if (!target || !self.mobj)
        return false;
    QMetaMethodPrivate::InvokeFailReason r =
            QMetaMethodPrivate::invokeImpl(self, target, connectionType, paramCount, parameters,
                                           typeNames, metaTypes);
    if (Q_LIKELY(r == QMetaMethodPrivate::InvokeFailReason::None))
        return true;

    if (int(r) >= int(QMetaMethodPrivate::InvokeFailReason::FormalParameterMismatch)) {
        int n = int(r) - int(QMetaMethodPrivate::InvokeFailReason::FormalParameterMismatch);
        qWarning("QMetaMethod::invoke: cannot convert formal parameter %d from %s in call to %s::%s",
                 n, typeNames[n + 1] ? typeNames[n + 1] : metaTypes[n + 1]->name,
                 self.mobj->className(), self.methodSignature().constData());
    }
    if (r == QMetaMethodPrivate::InvokeFailReason::TooFewArguments) {
        qWarning("QMetaMethod::invoke: too few arguments (%d) in call to %s::%s",
                 int(paramCount), self.mobj->className(), self.methodSignature().constData());
    }
    return false;
}

auto QMetaMethodInvoker::invokeImpl(QMetaMethod self, void *target,
                                    Qt::ConnectionType connectionType,
                                    qsizetype paramCount, const void *const *parameters,
                                    const char *const *typeNames,
                                    const QtPrivate::QMetaTypeInterface *const *metaTypes) -> InvokeFailReason
{
    auto object = static_cast<QObject *>(target);
    auto priv = QMetaMethodPrivate::get(&self);
    constexpr bool MetaTypesAreOptional = QT_VERSION < QT_VERSION_CHECK(7, 0, 0);
    auto methodMetaTypes = priv->parameterMetaTypeInterfaces();
    auto param = const_cast<void **>(parameters);

    Q_ASSERT(priv->mobj);
    Q_ASSERT(self.methodType() == Constructor || object);
    Q_ASSERT(self.methodType() == Constructor || connectionType == Qt::ConnectionType(-1) ||
             priv->mobj->cast(object));
    Q_ASSERT(paramCount >= 1);  // includes the return type
    Q_ASSERT(parameters);
    Q_ASSERT(typeNames);
    Q_ASSERT(MetaTypesAreOptional || metaTypes);

    if ((paramCount - 1) < qsizetype(priv->data.argc()))
        return InvokeFailReason::TooFewArguments;

    // 0 is the return type, 1 is the first formal parameter
    auto checkTypesAreCompatible = [=](int idx) {
        uint typeInfo = priv->parameterTypeInfo(idx - 1);
        QLatin1StringView userTypeName(typeNames[idx] ? typeNames[idx] : metaTypes[idx]->name);

        if ((typeInfo & IsUnresolvedType) == 0) {
            // this is a built-in type
            if (MetaTypesAreOptional && !metaTypes)
                return int(typeInfo) == QMetaType::fromName(userTypeName).id();
            return int(typeInfo) == metaTypes[idx]->typeId;
        }

        QLatin1StringView methodTypeName = stringDataView(priv->mobj, typeInfo & TypeNameIndexMask);
        if ((MetaTypesAreOptional && !metaTypes) || !metaTypes[idx]) {
            // compatibility call, compare strings
            if (methodTypeName == userTypeName)
                return true;

            // maybe the user type needs normalization
            QByteArray normalized = normalizeTypeInternal(userTypeName.begin(), userTypeName.end());
            return methodTypeName == QLatin1StringView(normalized);
        }

        QMetaType userType(metaTypes[idx]);
        Q_ASSERT(userType.isValid());
        if (QMetaType(methodMetaTypes[idx - 1]) == userType)
            return true;

        // if the parameter type was NOT only forward-declared, it MUST have
        // matched
        if (methodMetaTypes[idx - 1])
            return false;

        // resolve from the name moc stored for us
        QMetaType resolved = QMetaType::fromName(methodTypeName);
        return resolved == userType;
    };

    // force all types to be registered, just in case
    for (qsizetype i = 0; metaTypes && i < paramCount; ++i)
        QMetaType(metaTypes[i]).registerType();

    // check formal parameters first (overload set)
    for (qsizetype i = 1; i < paramCount; ++i) {
        if (!checkTypesAreCompatible(i))
            return InvokeFailReason(int(InvokeFailReason::FormalParameterMismatch) + i - 1);
    }

    // handle constructors first
    if (self.methodType() == Constructor) {
        if (object) {
            qWarning("QMetaMethod::invokeMethod: cannot call constructor %s on object %p",
                     self.methodSignature().constData(), object);
            return InvokeFailReason::ConstructorCallOnObject;
        }

        if (!parameters[0]) {
            qWarning("QMetaMethod::invokeMethod: constructor call to %s must assign a return type",
                     self.methodSignature().constData());
            return InvokeFailReason::ConstructorCallWithoutResult;
        }

        if (!MetaTypesAreOptional || metaTypes) {
            if (metaTypes[0]->typeId != QMetaType::QObjectStar) {
                qWarning("QMetaMethod::invokeMethod: cannot convert QObject* to %s on constructor call %s",
                         metaTypes[0]->name, self.methodSignature().constData());
                return InvokeFailReason::ReturnTypeMismatch;
            }
        }

        int idx = priv->ownConstructorMethodIndex();
        if (priv->mobj->static_metacall(QMetaObject::CreateInstance, idx, param) >= 0)
            return InvokeFailReason::ConstructorCallFailed;
        return {};
    }

    // regular type - check return type
    if (parameters[0]) {
        if (!checkTypesAreCompatible(0)) {
            const char *retType = typeNames[0] ? typeNames[0] : metaTypes[0]->name;
            qWarning("QMetaMethod::invokeMethod: return type mismatch for method %s::%s:"
                     " cannot convert from %s to %s during invocation",
                     priv->mobj->className(), priv->methodSignature().constData(),
                     priv->rawReturnTypeName(), retType);
            return InvokeFailReason::ReturnTypeMismatch;
        }
    }

    Qt::HANDLE currentThreadId = nullptr;
    QThread *objectThread = nullptr;
    auto receiverInSameThread = [&]() {
        if (!currentThreadId) {
            currentThreadId = QThread::currentThreadId();
            objectThread = object->thread();
        }
        if (objectThread)
            return currentThreadId == QThreadData::get2(objectThread)->threadId.loadRelaxed();
        return false;
    };

    // check connection type
    if (connectionType == Qt::AutoConnection)
        connectionType = receiverInSameThread() ? Qt::DirectConnection : Qt::QueuedConnection;
    else if (connectionType == Qt::ConnectionType(-1))
        connectionType = Qt::DirectConnection;

#if !QT_CONFIG(thread)
    if (connectionType == Qt::BlockingQueuedConnection) {
        connectionType = Qt::DirectConnection;
    }
#endif

    // invoke!
    int idx_relative = priv->ownMethodIndex();
    int idx_offset = priv->mobj->methodOffset();
    QObjectPrivate::StaticMetaCallFunction callFunction = priv->mobj->d.static_metacall;

    if (connectionType == Qt::DirectConnection) {
        if (callFunction)
            callFunction(object, QMetaObject::InvokeMetaMethod, idx_relative, param);
        else if (QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, idx_relative + idx_offset, param) >= 0)
            return InvokeFailReason::CallViaVirtualFailed;
    } else if (connectionType == Qt::QueuedConnection) {
        if (parameters[0]) {
            qWarning("QMetaMethod::invoke: Unable to invoke methods with return values in "
                     "queued connections");
            return InvokeFailReason::CouldNotQueueParameter;
        }

        auto event = std::make_unique<QMetaCallEvent>(idx_offset, idx_relative, callFunction, nullptr, -1, paramCount);
        QMetaType *types = event->types();
        void **args = event->args();

        // fill in the meta types first
        for (int i = 1; i < paramCount; ++i) {
            types[i] = QMetaType(methodMetaTypes[i - 1]);
            if (!types[i].iface() && (!MetaTypesAreOptional || metaTypes))
                types[i] = QMetaType(metaTypes[i]);
            if (!types[i].iface())
                types[i] = priv->parameterMetaType(i - 1);
            if (!types[i].iface() && typeNames[i])
                types[i] = QMetaType::fromName(typeNames[i]);
            if (!types[i].iface()) {
                qWarning("QMetaMethod::invoke: Unable to handle unregistered datatype '%s'",
                         typeNames[i]);
                return InvokeFailReason(int(InvokeFailReason::CouldNotQueueParameter) - i);
            }
        }

        // now create copies of our parameters using those meta types
        for (int i = 1; i < paramCount; ++i)
            args[i] = types[i].create(parameters[i]);

        QCoreApplication::postEvent(object, event.release());
    } else { // blocking queued connection
#if QT_CONFIG(thread)
        if (receiverInSameThread()) {
            qWarning("QMetaMethod::invoke: Dead lock detected in BlockingQueuedConnection: "
                     "Receiver is %s(%p)", priv->mobj->className(), object);
            return InvokeFailReason::DeadLockDetected;
        }

        QSemaphore semaphore;
        QCoreApplication::postEvent(object, new QMetaCallEvent(idx_offset, idx_relative, callFunction,
                                                        nullptr, -1, param, &semaphore));
        semaphore.acquire();
#endif // QT_CONFIG(thread)
    }
    return {};
}

相關文章