bool QMetaObject::invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericA...)

DesertCactus / 2025-01-20 / 原文



class QMetaMethodPrivate : public QMetaMethodInvoker
{
public:
    static const QMetaMethodPrivate *get(const QMetaMethod *q)
    { return static_cast<const QMetaMethodPrivate *>(q); }

    inline QByteArray signature() const;
    inline QByteArray name() const;
    inline int typesDataIndex() const;
    inline const char *rawReturnTypeName() const;
    inline int returnType() const;
    inline int parameterCount() const;
    inline int parametersDataIndex() const;
    inline uint parameterTypeInfo(int index) const;
    inline int parameterType(int index) const;
    inline void getParameterTypes(int *types) const;
    inline const QtPrivate::QMetaTypeInterface *returnMetaTypeInterface() const;
    inline const QtPrivate::QMetaTypeInterface *const *parameterMetaTypeInterfaces() const;
    inline QByteArray parameterTypeName(int index) const;
    inline QList<QByteArray> parameterTypes() const;
    inline QList<QByteArray> parameterNames() const;
    inline QByteArray tag() const;
    inline int ownMethodIndex() const;
    inline int ownConstructorMethodIndex() const;

private:
    void checkMethodMetaTypeConsistency(const QtPrivate::QMetaTypeInterface *iface, int index) const;
    QMetaMethodPrivate();
};
} // unnamed namespace

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