QT 中的元对象系统(五):QMetaObject::invokeMethod的使用和实现原理
目录
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.hstatic 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 parameterselsesig[sig.size() - 1] = ')';sig.append('\0');//2.从元数据中获取函数mySlot(int)的idxconst 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 wellQByteArray 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 typevoid **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 typeargs[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 pointertemplate <typename Func>static typename std::enable_if<QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction&& !std::is_convertible<Func, const char*>::value&& QtPrivate::FunctionPointer<Func>::ArgumentCount == 0, bool>::typeinvokeMethod(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>::typeinvokeMethod(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>::typeinvokeMethod(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>::typeinvokeMethod(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 Functortemplate <typename Func>static typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction&& QtPrivate::FunctionPointer<Func>::ArgumentCount == -1&& !std::is_convertible<Func, const char*>::value, bool>::typeinvokeMethod(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>::typeinvokeMethod(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 method和Q_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 method和Q_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 是一个非常有用的函数,但在使用时需要根据具体情况权衡其优缺点,并注意相关的注意事项,以确保代码的正确性和性能。
相关文章:
QT 中的元对象系统(五):QMetaObject::invokeMethod的使用和实现原理
目录 1.简介 2.原理概述 3.实现分析 3.1.通过方法名调用方法的实现分析 3.2.通过可调用对象调用方法的实现分析 4.使用场景 5.总结 1.简介 QMetaObject::invokeMethod 是 Qt 框架中的一个静态方法,用于在运行时调用对象的成员函数。这个方法提供了一种动态调…...
Linux进程管理与进程间通信
一、进程基础知识 1. 进程的定义与特性 **定义**:进程是程序的一次执行过程,是系统资源分配的基本单位 **特性**: - 动态性:进程是程序的动态执行过程 - 并发性:多个进程可以并发执行 - 独立性:进…...
【无人机】无人机PX4飞控系统高级软件架构
目录 1、概述(图解) 一、数据存储层(Storage) 二、外部通信层(External Connectivity) 三、核心通信枢纽(Message Bus) 四、硬件驱动层(Drivers) 五、飞…...
启动arthas-boot.jar端口占用
问题 [rootlocalhost arthas-4.0.4]# java -jar arthas-boot.jar [ERROR] The telnet port 3658 is used by process 7066 instead of target process 6155, you will connect to an unexpected process. [ERROR] 1. Try to restart arthas-boot, select process 7066, shutdow…...
JSVMP逆向实战:原理分析与破解思路详解
引言 在当今Web安全领域,JavaScript虚拟机保护(JSVMP)技术被广泛应用于前端代码的保护和反爬机制中。作为前端逆向工程师,掌握JSVMP逆向技术已成为必备技能。本文将深入剖析JSVMP的工作原理,并分享实用的逆向破解思路…...
【SPP】蓝牙链路控制(LC)在SPP中互操作性深度解析
在蓝牙协议栈的精密分层体系中,其链路控制(Link Control, LC)层作为基带层的核心组件,承载着物理信道管理、连接建立与维护等关键任务。其互操作性要求直接决定了不同厂商设备能否实现无缝通信。本文将以蓝牙技术规范中的LC互操作…...
单片机学习之定时器
定时器是用来定时的机器,是存在于STM32单片机中的一个外设。STM32一般总共有8个定时器,分别是2个高级定时器(TIM1、TIM8),4个通用定时器(TIM2、TIM3、TIM4、TIM5)和2个基本定时器(TI…...
供应链管理:计算题 / 倒扣法
一、理解倒扣法 在供应链管理中,倒扣法是一种常用的成本计算方法,主要用于确定商品的成本和销售价格,以确保特定的毛利率。倒扣法的基本原理是在已知售价和期望毛利率的情况下,逆推计算出供货价或成本价。 二、倒扣法的计算公式…...
算法每日一练 (25)
💢欢迎来到张翊尘的技术站 💥技术如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥 文章目录 算法每日一练 (25)四数之和题目描述解题思路解题代码c…...
【大模型基础_毛玉仁】6.4 生成增强
目录 6.4 生成增强6.4.1 何时增强1)外部观测法2)内部观测法 6.4.2 何处增强6.4.3 多次增强6.4.4 降本增效1)去除冗余文本2)复用计算结果 6.4 生成增强 检索器得到相关信息后,将其传递给大语言模型以期增强模型的生成能…...
Zephyr实时操作系统初步介绍
一、概述 Zephyr是由Linux基金会托管的开源实时操作系统(RTOS),专为资源受限的物联网设备设计。其核心特性包括模块化架构、跨平台兼容性、安全性优先以及丰富的连接协议支持。基于Apache 2.0协议,Zephyr允许商业和非商业用途的自…...
【GCC警告报错4】warning: format not a string literal and no format arguments
文章主本文根据笔者个人工作/学习经验整理而成,如有错误请留言。 文章为付费内容,已加入原创保护,禁止私自转载。 文章发布于:《C语言编译报错&警告合集》 如图所示: 原因: snprintf的函数原型&#x…...
【落羽的落羽 C++】模板简介
文章目录 一、模板的引入二、函数模板1. 函数模板的使用2. 函数模板的原理3. 函数模板的实例化4. 函数模板的匹配 三、类模板 一、模板的引入 假如我们想写一个Swap函数,针对每一种类型,都要函数重载写一次,但它们的实现原理是几乎一样的。在…...
USB(通用串行总线)数据传输机制和包结构简介
目录 1. USB的物理连接电缆结构时钟恢复技术 2. USB的数据传输方式包(Packet) 3. 包的传输规则帧和微帧 4. 包的结构1. 同步字段(Sync)2. 包标识符字段(PID)3. 数据字段4. 循环冗余校验字段(CRC…...
【目标检测】【深度学习】【Pytorch版本】YOLOV3模型算法详解
【目标检测】【深度学习】【Pytorch版本】YOLOV3模型算法详解 文章目录 【目标检测】【深度学习】【Pytorch版本】YOLOV3模型算法详解前言YOLOV3的模型结构YOLOV3模型的基本执行流程YOLOV3模型的网络参数 YOLOV3的核心思想前向传播阶段反向传播阶段 总结 前言 YOLOV3是由华盛顿…...
【前端扫盲】postman介绍及使用
Postman 是一款专为 API 开发与测试设计的 全流程协作工具,程序员可通过它高效完成接口调试、自动化测试、文档管理等工作。以下是针对程序员的核心功能介绍和应用场景说明: 一、核心功能亮点 接口请求构建与调试 支持所有 HTTP 方法(GET/POS…...
每日c/c++题 备战蓝桥杯(全排列问题)
题目描述 按照字典序输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。 输入格式 一个整数 n。 输出格式 由 1∼n 组成的所有不重复的数字序列,每行一个序列。 每个数字保留 5 个场…...
IdeaVim-AceJump
AceJump 是一款专为IntelliJ IDEA平台打造的开源插件,旨在通过简单的快捷键操作帮助用户快速跳转到编辑器中的任何符号位置,如变量名、方法调用或特定的字符串。无论是大型项目还是日常编程,AceJump 都能显著提升你的代码导航速度和效率。…...
BMS电池关键参数及其含义
BMS概述 BMS的定义与功能 BMS,即电池管理系统,是电池系统的核心控制设备,充当着电池的“状态观测器”。它通过传感器采集电池的单体电压、温度、电流等关键参数,并利用电子控制单元(ECU)进行数据处理和分…...
DataFrame行索引操作以及重置索引
一.DataFrame行索引操作 1.1 获取数据 1.1.1 loc 选取数据 df.loc[ ] 只能使用标签索引,不能使用整数索引。 当通过标签索引的切片方式来筛选数据时,它的取值前闭后闭。 传参: 1.如果选择单行或单列,返回的数据类型为 Series…...
DayDreamer: World Models forPhysical Robot Learning
DayDreamer:用于物理机器人学习的世界模型 Philipp Wu* Alejandro Escontrela* Danijar Hafner* Ken Goldberg Pieter Abbeel 加州大学伯克利分校 *贡献相同 摘要:为了在复杂环境中完成任务,机器人需要从经验中学习。深度强化学习是机器人学…...
线性欧拉筛
线性筛:高效求解素数 在数论中,素数的筛选是一个经典的问题。最常见的素数筛选方法是埃拉托斯特尼筛法,其时间复杂度为 O ( n log log n ) O(n\log \log n) O(nloglogn),非常适合求解小范围内的素数。随着问题规模的增大&…...
Flutter vs React Native:跨平台移动开发框架对比
文章目录 前言1. 框架概述什么是 Flutter?什么是 React Native? 2. 性能对比Flutter 的性能表现React Native 的性能表现总结: 3. 开发体验对比3.1 开发效率3.2 UI 组件库 4. 生态系统对比5. 适用场景分析6. 结论:如何选择&#x…...
用matlab搭建一个简单的图像分类网络
文章目录 1、数据集准备2、网络搭建3、训练网络4、测试神经网络5、进行预测6、完整代码 1、数据集准备 首先准备一个包含十个数字文件夹的DigitsData,每个数字文件夹里包含1000张对应这个数字的图片,图片的尺寸都是 28281 像素的,如下图所示…...
AI辅助开发插件
适合Java程序员的AI辅助开发插件,按功能和适用场景分类: 1. 飞算JavaAI • 特点:从需求分析到代码生成的全流程智能引导,支持Maven、Gradle等主流工具,一键生成完整工程代码,包括配置文件、源代码和测试资…...
【AI4CODE】5 Trae 锤一个基于百度Amis的Crud应用
【AI4CODE】目录 【AI4CODE】1 Trae CN 锥安装配置与迁移 【AI4CODE】2 Trae 锤一个 To-Do-List 【AI4CODE】3 Trae 锤一个贪吃蛇的小游戏 【AI4CODE】4 Trae 锤一个数据搬运工的小应用 1 百度 Amis 简介 百度 Amis 是一个低代码前端框架,由百度开源。它通过 J…...
npm webpack打包缓存 导致css引用地址未更新
问题如下: 测试环境配置: publicPath: /chat/,生产环境配置: publicPath: /,css中引用背景图片 background-image: url(/assets/images/calendar/arrow-left.png);先打包测试环境,观察打包后的css文件引用的背景图片地址 可以全…...
ollama导入huggingface下载的大模型并量化
1. 导入GGUF 类型的模型 1.1 先在huggingface 下载需要ollama部署的大模型 1.2 编写modelfile 在ollama 里面输入 ollama show --modelfile <你有的模型名称> eg: ollama show --modelfile qwen2.5:latest修改其中的from 路径为自己的模型下载路径 FROM /Users/lzx/A…...
Java 集合 Map Stream流
目录 集合遍历for each map案例 编辑 这种数组的遍历是【index】编辑map排序【对象里重写compareTo编辑map排序【匿名内部类lambda编辑 stream流编辑 编辑获取: map的键是set集合,获取方法map.keySet() map的值是collection 集合&…...
记录一下零零散散的的东西-ImageNet
ImageNet 是一个非常著名的大型图像识别数据集, 数据集基本信息 内容说明📸 图像数量超过 1400万张图片(包含各类子集)🏷️ 类别数量常用的是 ImageNet-1K(1000类)🧑Ἶ…...
