QT 中的元对象系统(五):QMetaObject::invokeMethod的使用和实现原理

发布于:2025-04-05 ⋅ 阅读:(13) ⋅ 点赞:(0)

目录

1.简介

2.原理概述

3.实现分析

3.1.通过方法名调用方法的实现分析

3.2.通过可调用对象调用方法的实现分析

4.使用场景

5.总结


1.简介

        QMetaObject::invokeMethod 是 Qt 框架中的一个静态方法,用于在运行时调用对象的成员函数。这个方法提供了一种动态调用方法的方式,不需要在编译时知道具体的方法名或参数。QMetaObject::invokeMethod 可以用于调用任何对象的任何可调用方法,包括信号、槽和普通成员函数,只要它们符合一定的条件。

        当使用 invokeMethod 时,还需要注意以下几点:

  • 确保对象 object 是有效的,并且其类使用了 Q_OBJECT 宏。
  • 方法 method 必须是可调用的,这通常意味着它是一个槽或使用了 Q_INVOKABLE 宏。
  • 被 Q_INVOKABLE 标记的函数必须是公开的(public),因为元对象系统无法访问私有或受保护的成员函数
  • 如果方法需要参数,确保提供的参数与方法的期望类型匹配。
  • 如果方法返回值,确保正确处理这个返回值。

函数原型为:

QMetaObject::invokeMethod 有几种重载形式,但最常用的一种是:

bool QMetaObject::invokeMethod(QObject *object, const char *method,  
                               QGenericArgument val0 = QGenericArgument(nullptr),  
                               QGenericArgument val1 = QGenericArgument(nullptr),  
                               QGenericArgument val2 = QGenericArgument(nullptr),  
                               QGenericArgument val3 = QGenericArgument(nullptr),  
                               QGenericArgument val4 = QGenericArgument(nullptr),  
                               QGenericArgument val5 = QGenericArgument(nullptr),  
                               QGenericArgument val6 = QGenericArgument(nullptr),  
                               QGenericArgument val7 = QGenericArgument(nullptr),  
                               QGenericArgument val8 = QGenericArgument(nullptr),  
                               QGenericArgument val9 = QGenericArgument(nullptr))
  • object:要调用方法的对象。
  • method:要调用的方法的名称。
  • val0 - val9:方法的参数,最多支持10个。使用 QGenericArgument 类型封装参数。

   invokeMethod 返回一个布尔值,表示方法是否成功调用。如果方法成功被调用,返回 true;如果方法不存在、对象无法找到、参数类型不匹配或方法不是可调用的,返回 false

        假设有一个类 MyClass,它有一个槽 mySlot,可以接受两个整数作为参数:

#include <QCoreApplication>
#include <QObject>
#include <QDebug>

class MyObject : public QObject
{
    Q_OBJECT
public:
    explicit MyObject(QObject *parent = nullptr) : QObject(parent) {}

public slots:
    void mySlot(int value)
    {
        qDebug() << "Received value:" << value;
    }
};

#include "main.moc"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyObject obj;
    int param = 42;

    // 同步调用
    bool result = QMetaObject::invokeMethod(&obj, "mySlot", Qt::DirectConnection, Q_ARG(int, param));
    if (result) {
        qDebug() << "Method called successfully.";
    } else {
        qDebug() << "Method call failed.";
    }

    return a.exec();
}

        在这个示例中,我们使用 QMetaObject::invokeMethod 动态调用 MyObject 的 mySlot 方法。Qt::DirectConnection 表示直接调用该方法,Q_ARG(int, param) 用于传递参数。

        QMetaObject::invokeMethod的几种重载 

   QMetaObject::invokeMethod 在 Qt 框架中是一个强大的静态方法,它提供了几种重载形式来适应不同的调用需求。以下是 QMetaObject::invokeMethod 的几种常见重载形式:

        1)基础重载

bool QMetaObject::invokeMethod(QObject *object, const char *method,  
                               Qt::ConnectionType type = Qt::DirectConnection,  
                               QGenericReturnArgument ret = QGenericReturnArgument(nullptr),  
                               QGenericArgument val0 = QGenericArgument(nullptr),  
                               QGenericArgument val1 = QGenericArgument(nullptr),  
                               ... // 最多到 val9  
                               )

        这是最常用的重载形式。它允许你指定要调用的对象、方法名、连接类型(同步或异步)、返回值以及最多10个参数。

        2)无返回值重载

bool QMetaObject::invokeMethod(QObject *object, const char *method,  
                               Qt::ConnectionType type = Qt::DirectConnection,  
                               QGenericArgument val0 = QGenericArgument(nullptr),  
                               ... // 最多到 val9  
                               )

        这个重载与上一个类似,但它不期望方法返回任何值。这在你只关心方法是否被成功调用,而不关心其返回值时很有用。   

       3)带返回值的重载(简化版)

bool QMetaObject::invokeMethod(QObject *object, const char *method,  
                               Qt::ConnectionType type,  
                               QGenericReturnArgument ret)

        这个重载允许你指定一个返回值,但不支持传递参数给方法。它适用于那些不需要参数但期望返回值的场景。

        4)异步调用重载

        虽然这不是一个完全独立的重载,但值得注意的是,invokeMethod 支持异步调用。你可以通过指定 Qt::QueuedConnection 作为连接类型来实现这一点。当使用异步调用时,方法将在事件循环的下一个迭代中被调用,这允许你在不阻塞当前线程的情况下调用方法。

        5)模板重载(C++11及更高版本)

        在 C++11 及更高版本中,Qt 提供了模板化的 invokeMethod,它允许你更直接地传递参数而不需要使用 QGenericArgument。这个重载在编译时根据提供的参数类型自动推断,并调用相应的方法。然而,这个模板重载在 Qt 的某些版本中可能不是直接作为 QMetaObject 的静态成员函数提供的,而是作为 QMetaObject::invokeMethod 的一个帮助器函数或模板特化存在的。

2.原理概述

   QMetaObject::invokeMethod 的核心原理基于 Qt 的元对象系统。元对象系统是 Qt 实现信号槽机制、属性系统和动态调用的基础。每个继承自 QObject 且包含 Q_OBJECT 宏的类都有一个与之关联的 QMetaObject 对象,该对象存储了类的元数据,如类名、信号、槽、属性等信息。

   QMetaObject::invokeMethod 函数利用这些元数据,通过方法的名称或索引来查找并调用对象的方法。它可以在不同线程之间安全地调用方法,并且支持同步和异步调用。

        调用流程

        当调用 QMetaObject::invokeMethod 时,大致会经历以下步骤:

1)查找元对象信息

  • 首先,函数会获取调用对象的 QMetaObject 对象。通过 QObject 的 metaObject() 方法可以获取该对象的元数据。
  • 然后,根据传入的方法名称或索引,在 QMetaObject 中查找对应的方法信息。

2)检查方法是否存在

  • 检查查找结果,如果方法不存在,函数会根据 Qt::ConnectionType 参数的设置返回相应的结果。通常,如果方法不存在,同步调用会返回 false,异步调用会忽略该调用。

3)处理连接类型

QMetaObject::invokeMethod 支持多种连接类型,不同的连接类型会影响方法的调用方式:

  • Qt::DirectConnection:直接调用目标方法,就像直接调用普通函数一样。这种方式适用于调用对象和被调用对象在同一线程的情况。
  • Qt::QueuedConnection:将方法调用封装成一个 QMetaCallEvent 对象,并将其放入目标对象所在线程的事件队列中。当目标线程的事件循环处理到该事件时,会调用目标方法。这种方式适用于跨线程调用。
  • Qt::BlockingQueuedConnection:与 Qt::QueuedConnection 类似,但会阻塞当前线程,直到目标方法调用完成并返回结果。使用时要注意避免在同一线程中使用,否则会导致死锁。
  • Qt::AutoConnection:根据调用对象和被调用对象所在的线程自动选择合适的连接方式。如果在同一线程,使用 Qt::DirectConnection;否则使用 Qt::QueuedConnection

4)调用目标方法

  • 如果是直接调用(Qt::DirectConnection),函数会直接调用目标方法,并将传入的参数传递给该方法。
  • 如果是队列调用(Qt::QueuedConnection 或 Qt::BlockingQueuedConnection),函数会创建一个 QMetaCallEvent 对象,将方法调用的信息(如方法索引、参数等)封装在该事件中,然后将事件发送到目标对象所在线程的事件队列中。

3.实现分析

3.1.通过方法名调用方法的实现分析

   //.\Qt\Qt5.12.12\5.12.12\Src\qtbase\src\corelib\kernel\qobjectdefs.h
   static bool invokeMethod(QObject *obj, const char *member,
                             Qt::ConnectionType,
                             QGenericReturnArgument ret,
                             QGenericArgument val0 = QGenericArgument(nullptr),
                             QGenericArgument val1 = QGenericArgument(),
                             QGenericArgument val2 = QGenericArgument(),
                             QGenericArgument val3 = QGenericArgument(),
                             QGenericArgument val4 = QGenericArgument(),
                             QGenericArgument val5 = QGenericArgument(),
                             QGenericArgument val6 = QGenericArgument(),
                             QGenericArgument val7 = QGenericArgument(),
                             QGenericArgument val8 = QGenericArgument(),
                             QGenericArgument val9 = QGenericArgument());

    static inline bool invokeMethod(QObject *obj, const char *member,
                             QGenericReturnArgument ret,
                             QGenericArgument val0 = QGenericArgument(nullptr),
                             QGenericArgument val1 = QGenericArgument(),
                             QGenericArgument val2 = QGenericArgument(),
                             QGenericArgument val3 = QGenericArgument(),
                             QGenericArgument val4 = QGenericArgument(),
                             QGenericArgument val5 = QGenericArgument(),
                             QGenericArgument val6 = QGenericArgument(),
                             QGenericArgument val7 = QGenericArgument(),
                             QGenericArgument val8 = QGenericArgument(),
                             QGenericArgument val9 = QGenericArgument())
    {
        return invokeMethod(obj, member, Qt::AutoConnection, ret, val0, val1, val2, val3,
                val4, val5, val6, val7, val8, val9);
    }

    static inline bool invokeMethod(QObject *obj, const char *member,
                             Qt::ConnectionType type,
                             QGenericArgument val0 = QGenericArgument(nullptr),
                             QGenericArgument val1 = QGenericArgument(),
                             QGenericArgument val2 = QGenericArgument(),
                             QGenericArgument val3 = QGenericArgument(),
                             QGenericArgument val4 = QGenericArgument(),
                             QGenericArgument val5 = QGenericArgument(),
                             QGenericArgument val6 = QGenericArgument(),
                             QGenericArgument val7 = QGenericArgument(),
                             QGenericArgument val8 = QGenericArgument(),
                             QGenericArgument val9 = QGenericArgument())
    {
        return invokeMethod(obj, member, type, QGenericReturnArgument(), val0, val1, val2,
                                 val3, val4, val5, val6, val7, val8, val9);
    }

    static inline bool invokeMethod(QObject *obj, const char *member,
                             QGenericArgument val0 = QGenericArgument(nullptr),
                             QGenericArgument val1 = QGenericArgument(),
                             QGenericArgument val2 = QGenericArgument(),
                             QGenericArgument val3 = QGenericArgument(),
                             QGenericArgument val4 = QGenericArgument(),
                             QGenericArgument val5 = QGenericArgument(),
                             QGenericArgument val6 = QGenericArgument(),
                             QGenericArgument val7 = QGenericArgument(),
                             QGenericArgument val8 = QGenericArgument(),
                             QGenericArgument val9 = QGenericArgument())
    {
        return invokeMethod(obj, member, Qt::AutoConnection, QGenericReturnArgument(), val0,
                val1, val2, val3, val4, val5, val6, val7, val8, val9);
    }

几个不同参数的invokeMethod最终调用了下面的参数接口:

bool QMetaObject::invokeMethod(QObject *obj,
                               const char *member,
                               Qt::ConnectionType type,
                               QGenericReturnArgument ret,
                               QGenericArgument val0,
                               QGenericArgument val1,
                               QGenericArgument val2,
                               QGenericArgument val3,
                               QGenericArgument val4,
                               QGenericArgument val5,
                               QGenericArgument val6,
                               QGenericArgument val7,
                               QGenericArgument val8,
                               QGenericArgument val9)
{
    if (!obj)
        return false;

    //1.把函数和函数的参数组合成这种形式:mySlot(int)
    QVarLengthArray<char, 512> sig;
    int len = qstrlen(member);
    if (len <= 0)
        return false;
    sig.append(member, len);
    sig.append('(');

    const char *typeNames[] = {ret.name(), val0.name(), val1.name(), val2.name(), val3.name(),
                               val4.name(), val5.name(), val6.name(), val7.name(), val8.name(),
                               val9.name()};

    int paramCount;
    for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {
        len = qstrlen(typeNames[paramCount]);
        if (len <= 0)
            break;
        sig.append(typeNames[paramCount], len);
        sig.append(',');
    }
    if (paramCount == 1)
        sig.append(')'); // no parameters
    else
        sig[sig.size() - 1] = ')';
    sig.append('\0');

    //2.从元数据中获取函数mySlot(int)的idx
    const QMetaObject *meta = obj->metaObject();
    int idx = meta->indexOfMethod(sig.constData());
    if (idx < 0) {
        QByteArray norm = QMetaObject::normalizedSignature(sig.constData());
        idx = meta->indexOfMethod(norm.constData());
    }

    if (idx < 0 || idx >= meta->methodCount()) {
        // This method doesn't belong to us; print out a nice warning with candidates.
        qWarning("QMetaObject::invokeMethod: No such method %s::%s%s",
                 meta->className(), sig.constData(), findMethodCandidates(meta, member).constData());
        return false;
    }
    //3.调用QMetaMethod的invoke方法
    QMetaMethod method = meta->method(idx);
    return method.invoke(obj, type, ret,
                         val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
}

这个函数关键实现过程主要有3步:

1)取出方法名和方法参数名,把它们组合成" 函数名(参数1类型,参数2类型,...)",形如:

" mySlot(int) " 的字符串。

2)从元数据中获取此方法在QMetaObject的相对位置信息。

int QMetaObject::indexOfMethod(const char *method) const
{
    const QMetaObject *m = this;
    int i;
    Q_ASSERT(priv(m->d.data)->revision >= 7);
    QArgumentTypeArray types;
    QByteArray name = QMetaObjectPrivate::decodeMethodSignature(method, types);
    i = indexOfMethodRelative<0>(&m, name, types.size(), types.constData());
    if (i >= 0)
        i += m->methodOffset();
    return i;
}

3)最后调用QMetaMethod的invoke方法,此步骤最为关键

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;

    Q_ASSERT(mobj->cast(object));

    // check return type,检测返回值
    if (returnValue.data()) {
        const char *retType = typeName();
        if (qstrcmp(returnValue.name(), retType) != 0) {
            // normalize the return value as well
            QByteArray normalized = QMetaObject::normalizedType(returnValue.name());
            if (qstrcmp(normalized.constData(), retType) != 0) {
                // String comparison failed, try compare the metatype.
                int t = returnType();
                if (t == QMetaType::UnknownType || t != QMetaType::type(normalized))
                    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()
    };
    int paramCount;
    for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {
        if (qstrlen(typeNames[paramCount]) <= 0)
            break;
    }
    if (paramCount <= QMetaMethodPrivate::get(this)->parameterCount())
        return false;

    // check connection type
    // 检查连接类型
    QThread *currentThread = QThread::currentThread();
    QThread *objectThread = object->thread();
    if (connectionType == Qt::AutoConnection) {
        connectionType = currentThread == objectThread
                         ? Qt::DirectConnection
                         : Qt::QueuedConnection;
    }

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

    // invoke! //调用
    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 idx_relative = QMetaMethodPrivate::get(this)->ownMethodIndex();
    int idx_offset =  mobj->methodOffset();
    Q_ASSERT(QMetaObjectPrivate::get(mobj)->revision >= 6);
    QObjectPrivate::StaticMetaCallFunction callFunction = mobj->d.static_metacall;

    if (connectionType == Qt::DirectConnection) {
        if (callFunction) {
            callFunction(object, QMetaObject::InvokeMetaMethod, idx_relative, param);
            return true;
        } else {
            return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, idx_relative + idx_offset, param) < 0;
        }
    } else if (connectionType == Qt::QueuedConnection) {
        if (returnValue.data()) {
            qWarning("QMetaMethod::invoke: Unable to invoke methods with return values in "
                     "queued connections");
            return false;
        }

        int nargs = 1; // include return type
        void **args = (void **) malloc(paramCount * sizeof(void *));
        Q_CHECK_PTR(args);
        int *types = (int *) malloc(paramCount * sizeof(int));
        Q_CHECK_PTR(types);
        types[0] = 0; // return type
        args[0] = 0;

        for (int i = 1; i < paramCount; ++i) {
            types[i] = QMetaType::type(typeNames[i]);
            if (types[i] == QMetaType::UnknownType && param[i]) {
                // Try to register the type and try again before reporting an error.
                int index = nargs - 1;
                void *argv[] = { &types[i], &index };
                QMetaObject::metacall(object, QMetaObject::RegisterMethodArgumentMetaType,
                                      idx_relative + idx_offset, argv);
                if (types[i] == -1) {
                    qWarning("QMetaMethod::invoke: Unable to handle unregistered datatype '%s'",
                            typeNames[i]);
                    for (int x = 1; x < i; ++x) {
                        if (types[x] && args[x])
                            QMetaType::destroy(types[x], args[x]);
                    }
                    free(types);
                    free(args);
                    return false;
                }
            }
            if (types[i] != QMetaType::UnknownType) {
                args[i] = QMetaType::create(types[i], param[i]);
                ++nargs;
            }
        }

        QCoreApplication::postEvent(object, new QMetaCallEvent(idx_offset, idx_relative, callFunction,
                                                        0, -1, nargs, types, args));
    } else { // blocking queued connection
#if QT_CONFIG(thread)
        if (currentThread == objectThread) {
            qWarning("QMetaMethod::invoke: Dead lock detected in "
                        "BlockingQueuedConnection: Receiver is %s(%p)",
                        mobj->className(), object);
        }

        QSemaphore semaphore;
        QCoreApplication::postEvent(object, new QMetaCallEvent(idx_offset, idx_relative, callFunction,
                                                        0, -1, 0, 0, param, &semaphore));
        semaphore.acquire();
#endif // QT_CONFIG(thread)
    }
    return true;
}
  • 如果是直接调用(Qt::DirectConnection),函数会直接调用目标方法,并将传入的参数传递给该方法。
  • 如果是队列调用(Qt::QueuedConnection 或 Qt::BlockingQueuedConnection),函数会创建一个 QMetaCallEvent 对象,将方法调用的信息(如方法索引、参数等)封装在该事件中,然后将事件发送到目标对象所在线程的事件队列中。

3.2.通过可调用对象调用方法的实现分析

C++ 的 Tag Dispatching(标签派发) 惯用法_c++ tag dispatch-CSDN博客

C++之std::enable_if_std enable if-CSDN博客 

1)invokeMethod() 调用类成员函数指针

// invokeMethod() for member function pointer
    template <typename Func>
    static typename std::enable_if<QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction
                                   && !std::is_convertible<Func, const char*>::value
                                   && QtPrivate::FunctionPointer<Func>::ArgumentCount == 0, bool>::type
    invokeMethod(typename QtPrivate::FunctionPointer<Func>::Object *object,
                 Func function,
                 Qt::ConnectionType type = Qt::AutoConnection,
                 typename QtPrivate::FunctionPointer<Func>::ReturnType *ret = nullptr)
    {
        return invokeMethodImpl(object, new QtPrivate::QSlotObjectWithNoArgs<Func>(function), type, ret);
    }

    template <typename Func>
    static typename std::enable_if<QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction
                                   && !std::is_convertible<Func, const char*>::value
                                   && QtPrivate::FunctionPointer<Func>::ArgumentCount == 0, bool>::type
    invokeMethod(typename QtPrivate::FunctionPointer<Func>::Object *object,
                 Func function,
                 typename QtPrivate::FunctionPointer<Func>::ReturnType *ret)
    {
        return invokeMethodImpl(object, new QtPrivate::QSlotObjectWithNoArgs<Func>(function), Qt::AutoConnection, ret);
    }

2)invokeMethod() 调用函数指针

// invokeMethod() for function pointer (not member)
    template <typename Func>
    static typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction
                                   && !std::is_convertible<Func, const char*>::value
                                   && QtPrivate::FunctionPointer<Func>::ArgumentCount == 0, bool>::type
    invokeMethod(QObject *context, Func function,
                 Qt::ConnectionType type = Qt::AutoConnection,
                 typename QtPrivate::FunctionPointer<Func>::ReturnType *ret = nullptr)
    {
        return invokeMethodImpl(context, new QtPrivate::QFunctorSlotObjectWithNoArgsImplicitReturn<Func>(function), type, ret);
    }

    template <typename Func>
    static typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction
                                   && !std::is_convertible<Func, const char*>::value
                                   && QtPrivate::FunctionPointer<Func>::ArgumentCount == 0, bool>::type
    invokeMethod(QObject *context, Func function,
                 typename QtPrivate::FunctionPointer<Func>::ReturnType *ret)
    {
        return invokeMethodImpl(context, new QtPrivate::QFunctorSlotObjectWithNoArgsImplicitReturn<Func>(function), Qt::AutoConnection, ret);
    }

3)invokeMethod() 调用仿函数或lamdba表达式等可调用对象

// invokeMethod() for Functor
    template <typename Func>
    static typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction
                                   && QtPrivate::FunctionPointer<Func>::ArgumentCount == -1
                                   && !std::is_convertible<Func, const char*>::value, bool>::type
    invokeMethod(QObject *context, Func function,
                 Qt::ConnectionType type = Qt::AutoConnection, decltype(function()) *ret = nullptr)
    {
        return invokeMethodImpl(context,
                                new QtPrivate::QFunctorSlotObjectWithNoArgs<Func, decltype(function())>(std::move(function)),
                                type,
                                ret);
    }

    template <typename Func>
    static typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction
                                   && QtPrivate::FunctionPointer<Func>::ArgumentCount == -1
                                   && !std::is_convertible<Func, const char*>::value, bool>::type
    invokeMethod(QObject *context, Func function, decltype(function()) *ret)
    {
        return invokeMethodImpl(context,
                                new QtPrivate::QFunctorSlotObjectWithNoArgs<Func, decltype(function())>(std::move(function)),
                                Qt::AutoConnection,
                                ret);
    }

这3中情况都调用了invokeMethodImpl,invokeMethodImpl的详细实现如下:

bool QMetaObject::invokeMethodImpl(QObject *object, QtPrivate::QSlotObjectBase *slot, Qt::ConnectionType type, void *ret)
{
    struct Holder {
        QtPrivate::QSlotObjectBase *obj;
        ~Holder() { obj->destroyIfLastRef(); }
    } holder = { slot };
    Q_UNUSED(holder);

    if (! object)
        return false;

    QThread *currentThread = QThread::currentThread();
    QThread *objectThread = object->thread();
    if (type == Qt::AutoConnection)
        type = (currentThread == objectThread) ? Qt::DirectConnection : Qt::QueuedConnection;

    void *argv[] = { ret };

    if (type == Qt::DirectConnection) {
        slot->call(object, argv);
    } else if (type == Qt::QueuedConnection) {
        if (argv[0]) {
            qWarning("QMetaObject::invokeMethod: Unable to invoke methods with return values in "
                     "queued connections");
            return false;
        }

        // args and typesCopy will be deallocated by ~QMetaCallEvent() using free()
        void **args = static_cast<void **>(calloc(1, sizeof(void *)));
        Q_CHECK_PTR(args);

        int *types = static_cast<int *>(calloc(1, sizeof(int)));
        Q_CHECK_PTR(types);

        QCoreApplication::postEvent(object, new QMetaCallEvent(slot, 0, -1, 1, types, args));
    } else if (type == Qt::BlockingQueuedConnection) {
#if QT_CONFIG(thread)
        if (currentThread == objectThread)
            qWarning("QMetaObject::invokeMethod: Dead lock detected");

        QSemaphore semaphore;
        QCoreApplication::postEvent(object, new QMetaCallEvent(slot, 0, -1, 0, 0, argv, &semaphore));
        semaphore.acquire();
#endif // QT_CONFIG(thread)
    } else {
        qWarning("QMetaObject::invokeMethod: Unknown connection type");
        return false;
    }
    return true;
}

此函数的实现和上面讲的QMetaMethod的invoke方法实现类似,就不在这里赘述了。

4.使用场景

1)Q_INVOKABLE与QMetaObject::invokeMethod均由元对象系统唤起。这一机制在Qt C++/QML混合编程跨线程编程Qt Service Framework 以及 Qt/ HTML5混合编程以及里广泛使用。

        Qt C++/QML混合编程

QML中调用C++方法借助了Qt元对象系统。考虑在QML中使用Qt C++定义的方法,如下代码所示:

import Qt 4.7   
import Shapes 5.0   //自定义模块  
Item {   
    width: 300; height: 200  
    Ellipse {   
         x: 50; y: 35; width: 200; height: 100   
        color: "blue"   
         MouseArea {   
            anchors.fill: parent  
            // 调用C++中定义的randomColor方法   
            onClicked: parent.color = parent.randomColor()    
        }   
    }  
}  

为了让上述QML代码成功的调用下面这段代码定义的randomColor()函数,最为关键的一点见randomColor方法用Q_INVOKABLE 修饰。

        在跨线程编程中的使用

        我们如何调用驻足在其他线程里的QObject方法呢?Qt提供了一种非常友好而且干净的解决方案:向事件队列post一个事件,事件的处理将以调用我们所感兴趣的方法为主(当然这需要线程有一个正在运行的事件循环)。而触发机制的实现是由moc提供的内省方法实现的。因此,只有信号、槽以及被标记成Q_INVOKABLE的方法才能够被其它线程所触发调用。如果你不想通过跨线程的信号、槽这一方法来实现调用驻足在其他线程里的QObject方法。另一选择就是将方法声明为Q_INVOKABLE,并且在另一线程中用invokeMethod唤起。

        Qt Service Framework

        Qt服务框架是Qt Mobility 1.0.2版本推出的,一个服务(service)是一个独立的组件提供给客户端(client)定义好的操作。客户端可以通过服务的名称,版本号和服务的对象提供的接口来查找服务。 查找到服务后,框架启动服务并返回一个指针。

        服务通过插件(plug-ins)来实现。为了避免客户端依赖某个具体的库,服务必须继承自QObject。这样QMetaObject 系统可以用来提供动态发现和唤醒服务的能力。要使QmetaObject机制充分的工作,服务必须满足,其所有的方法都是通过 signal,slot,property 或invokable methodQ_INVOKEBLE来实现

        其中,最常见的与servicer交互的方法如下:

QServiceManager manager;QObject *storage ;  
storage = manager.loadInterface("com.nokia.qt.examples.FileStorage"); if (storage)     QMetaObject::invokeMethod(storage, "deleteFile", Q_ARG(QString, "/tmp/readme.txt")); 
上面的代码通过service的元对象提供的invokeMethod方法,调用文件存储对象的deleteFile() 方法。客户端不需要知道对象的类型,因此也没有链接到具体的service库。  当然在服务端的deleteFile方法,一定要被标记为Q_INVOKEBLE,才能够被元对象系统识别。

        Qt服务框架的一个亮点是它支持跨进程通信,服务可以接受远程进程。在服务管理器上注册后 进程通过signal,slot,invokable method和property来通信,就像本地对象一样。服务可以设定为在客户端间共享,或针对一个客户端。  请注意,在Qt服务框架推出之前,信号、槽以及invokable method仅支持跨线程。 下图是跨进成的服务/客户段通信示意图(图片来自诺基亚论坛)。这里我们可以清楚的看到,invokable methodQ_INVOKEBLE 是跨进城、跨线程对象之间通信的重要利器。

2)使用 QMetaObject::invokeMethod类外调用私有槽函数

QMetaObject::invokeMethod 可以在运行时动态调用对象的方法,包括私有槽函数。

示例代码:

#include <QObject>
#include <QDebug>

class MyClass : public QObject
{
    Q_OBJECT
private slots:
    void privateSlot() {
        qDebug() << "Private slot called.";
    }
};

#include "main.moc"

int main(int argc, char *argv[])
{
    MyClass obj;
    QMetaObject::invokeMethod(&obj, "privateSlot", Qt::DirectConnection);
    return 0;
}

QMetaObject::invokeMethod 函数利用 Qt 的元对象系统,依据方法名来查找并调用对象的方法。这里使用 Qt::DirectConnection 直接调用 privateSlot 私有槽函数。不过要注意,使用这种方法时,方法名必须准确无误,而且要保证元对象系统能正确识别该方法。

使用场景总结:

  • 动态调用:在运行时根据不同的条件动态调用对象的方法,而不需要在编译时确定具体的调用方法。例如,根据用户的输入或配置文件中的信息来决定调用哪个方法。
  • 跨线程调用:在多线程应用中,安全地在不同线程之间调用对象的方法。例如,在工作线程中更新 UI 线程的对象状态。由于 Qt 的 UI 类不是线程安全的,不能直接在非 UI 线程中操作 UI 控件,使用 QMetaObject::invokeMethod 可以将 UI 操作封装成事件,放入 UI 线程的事件队列中处理。
  • 反射机制:实现类似于反射的功能,通过方法名来调用对象的方法,提高代码的灵活性和可扩展性。

5.总结

优点

  • 灵活性:可以在运行时动态调用对象的方法,无需在编译时确定具体的调用方法,增强了代码的灵活性和可扩展性。
  • 线程安全:支持跨线程调用,通过合理设置连接类型,可以确保在不同线程之间安全地调用对象的方法。
  • 通用性:不仅可以调用槽函数,还可以调用信号和普通的成员函数,具有很强的通用性。

缺点

  • 性能开销:由于 QMetaObject::invokeMethod 是通过元对象系统进行方法查找和调用的,相比直接调用普通函数会有一定的性能开销。因此,在性能敏感的场景中要谨慎使用。
  • 类型安全问题:在使用 QGenericArgument 和 QGenericReturnArgument 传递参数和接收返回值时,需要手动管理类型,容易出现类型不匹配的问题,导致运行时错误。

注意事项

  • 方法名的准确性:传递给 invokeMethod 的方法名必须准确无误,包括大小写和参数列表。如果方法名错误,调用将失败。
  • 参数类型和数量:传递的参数类型和数量必须与被调用方法的定义一致,否则可能会导致调用失败或产生未定义行为。
  • 线程安全:在使用 Qt::BlockingQueuedConnection 时,要注意避免死锁问题,确保调用对象和被调用对象不在同一线程。
  • 异常处理:由于方法调用可能是异步的(如使用 Qt::QueuedConnection),调用线程无法直接捕获被调用方法中抛出的异常。因此,在被调用方法中要做好异常处理,避免异常导致程序崩溃。

        综上所述,QMetaObject::invokeMethod 是一个非常有用的函数,但在使用时需要根据具体情况权衡其优缺点,并注意相关的注意事项,以确保代码的正确性和性能。