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 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?是一個非常有用的函數,但在使用時需要根據具體情況權衡其優缺點,并注意相關的注意事項,以確保代碼的正確性和性能。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/76618.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/76618.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/76618.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Unity3D開發AI桌面精靈/寵物系列 【三】 語音識別 ASR 技術、語音轉文本多平臺 - 支持科大訊飛、百度等 C# 開發

Unity3D 交互式AI桌面寵物開發系列【三】ASR 語音識別 該系列主要介紹怎么制作AI桌面寵物的流程&#xff0c;我會從項目開始創建初期到最終可以和AI寵物進行交互為止&#xff0c;項目已經開發完成&#xff0c;我會仔細梳理一下流程&#xff0c;分步講解。 這篇文章主要講有關于…

Java 狀態模式 詳解

狀態模式詳解 一、狀態模式概述 狀態模式(State Pattern)是一種行為型設計模式&#xff0c;它允許一個對象在其內部狀態改變時改變它的行為&#xff0c;使對象看起來似乎修改了它的類。 核心特點 狀態封裝&#xff1a;將每個狀態的行為封裝到獨立的類中狀態轉換&#xff1a…

Nginx 配置 HTTPS 與 WSS 完整指南

Nginx 配置 HTTPS 與 WSS 完整指南 本教程將手把手教你如何為網站配置 HTTPS 加密訪問&#xff0c;并通過反向代理實現安全的 WebSocket&#xff08;WSS&#xff09;通信。以 https://www.zhegepai.cn 域名為例&#xff0c;完整流程約需 30 分鐘完成。 一、前置準備 1.1 域名…

雙向鏈表的理解

背景 代碼中經常會出現雙向鏈表&#xff0c;對于雙向鏈表的插入和刪除有對應的API函數接口&#xff0c;但直觀的圖表更容易理解&#xff0c;所以本文會對rt-thread內核代碼中提供的雙向鏈表的一些API函數操作進行繪圖&#xff0c;方便后續隨時查看。 代碼塊 rt-thread中提供…

大文件上傳源碼,支持單個大文件與多個大文件

大文件上傳源碼&#xff0c;支持單個大文件與多個大文件 Ⅰ 思路Ⅱ 具體代碼前端--單個大文件前端--多個大文件前端接口后端 Ⅰ 思路 具體思路請參考我之前的文章&#xff0c;這里分享的是上傳流程與源碼 https://blog.csdn.net/sugerfle/article/details/130829022 Ⅱ 具體代碼…

Unity中的靜態合批使用整理

靜態批處理是一種繪制調用批處理方法&#xff0c;它組合不移動的網格以減少繪制調用。它將組合的網格轉換為世界空間&#xff0c;并為它們構建一個共享頂點和索引緩沖區。然后&#xff0c;對于可見網格&#xff0c;Unity 會執行一系列簡單的繪制調用&#xff0c;每個調用之間幾…

【機器學習中的基本術語:特征、樣本、訓練集、測試集、監督/無監督學習】

機器學習基本術語詳解 1. 特征&#xff08;Feature&#xff09; 定義&#xff1a;數據的屬性或變量&#xff0c;用于描述樣本的某個方面。作用&#xff1a;模型通過學習特征與目標之間的關系進行預測。示例&#xff1a; 預測房價時&#xff0c;特征可以是 面積、地段、房齡。…

C++學習之路:指針基礎

目錄 指針介紹與基本用法雙重指針函數指針空指針與野指針函數參數的指針傳遞最后 指針一般在C/C語言學習的后期接觸&#xff0c;這樣就導致指針給新手一種高深莫測、難以掌握的刻板印象。但實際上指針的使用很簡單&#xff0c;并且還能夠極大的提高程序的靈活性&#xff0c;幫助…

【服務日志鏈路追蹤】

MDCInheritableThreadLocal和spring cloud sleuth 在微服務架構中&#xff0c;日志鏈路追蹤&#xff08;Logback Distributed Tracing&#xff09; 是一個關鍵需求&#xff0c;主要用于跟蹤請求在不同服務間的調用鏈路&#xff0c;便于排查問題。常見的實現方案有兩種&#x…

Kafka+Zookeeper從docker部署到spring boot使用完整教程

文章目錄 一、Kafka1.Kafka核心介紹&#xff1a;?核心架構?核心特性?典型應用 2.Kafka對 ZooKeeper 的依賴&#xff1a;3.去 ZooKeeper 的演進之路&#xff1a;注&#xff1a;&#xff08;本文采用ZooKeeper3.8 Kafka2.8.1&#xff09; 二、Zookeeper1.核心架構與特性2.典型…

JUC系列JMM學習之隨筆

JUC: JUC 是 Java 并發編程的核心工具包,全稱為 Java Util Concurrent,是 java.util.concurrent 包及其子包的簡稱。它提供了一套強大且高效的并發編程工具,用于簡化多線程開發并提高性能。 CPU核心數和線程數的關系:1核處理1線程(同一時間單次) CPU內核結構: 工作內…

The Rust Programming Language 學習 (九)

泛型 每一個編程語言都有高效處理重復概念的工具。在 Rust 中其工具之一就是 泛型&#xff08;generics&#xff09;。泛型是具體類型或其他屬性的抽象替代。我們可以表達泛型的屬性&#xff0c;比如他們的行為或如何與其他泛型相關聯&#xff0c;而不需要在編寫和編譯代碼時知…

藍橋杯 混乘數字

問題描述 混乘數字的定義如下&#xff1a; 對于一個正整數 n&#xff0c;如果存在正整數 a 和 b&#xff0c;使得&#xff1a; n a b且 a 與 b 的十進制數位中每個數字出現的次數之和&#xff0c;與 n 中對應數字出現的次數相同&#xff0c;則稱 n 為混乘數字。 示例 對于…

CExercise04_1位運算符_2 定義一個函數判斷給定的正整數是否為2的冪

題目&#xff1a; 給定一個正整數&#xff0c;請定義一個函數判斷它是否為2的冪(1, 2, 4, 8, 16, …) 分析&#xff1a; &#xff1a; 代碼 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdbool.h>/* 給定一個正整數&#xff0c;請定義一個函數…

SSL證書不可信的原因有哪些?(國科云)

SSL證書用于在客戶端和服務器端之間建立一條加密通道&#xff0c;確保數據在傳輸過程中的安全性和完整性。然而&#xff0c;在實際應用中&#xff0c;我們有時會遇到SSL證書不可信的情況&#xff0c;嚴重影響了用戶對網站的信任度。那么&#xff0c;SSL證書不可信的原因究竟有哪…

[王陽明代數講義]琴語言類型系統工程特性

琴語言類型系統工程特性 層展物理學組織實務與藝術與琴生生.物機.械科.技工.業研究.所軟凝聚態物理開發工具包社會科學氣質砥礪學人生意氣場社群成員魅力場與心氣微積分社會關系力學 意氣實體過程圖論信息編碼&#xff0c;如來碼導引 注意力機制道裝Transformer架構的發展標度律…

自抗擾ADRC之二階線性擴展狀態觀測器(LESO)推導

1.龍伯格觀測器 實際工程應用中&#xff0c;狀態變量有時難以使用傳感器直接測量&#xff0c;在這種情況下&#xff0c;使用狀態觀測器估計系統實際狀態是非常常見的做法。最出名的狀態觀測器當屬龍伯格博士在1971年發表于TAC的An Introduction to Observer[1]一文中提出的基于…

從頭開發一個Flutter插件(二)高德地圖定位插件

開發基于高德定位SDK的Flutter插件 在上一篇文章里具體介紹了Flutter插件的具體開發流程&#xff0c;從創建項目到發布。接下來將為Flutter天氣項目開發一個基于高德定位SDK的Flutter定位插件。 申請key 首先進入高德地圖定位SDK文檔內下載定位SDK&#xff0c;并按要求申請A…

分布式鎖之redis6

一、分布式鎖介紹 之前我們都是使用本地鎖&#xff08;synchronize、lock等&#xff09;來避免共享資源并發操作導致數據問題&#xff0c;這種是鎖在當前進程內。 那么在集群部署下&#xff0c;對于多個節點&#xff0c;我們要使用分布式鎖來避免共享資源并發操作導致數據問題…

ubuntu中使用安卓模擬器

本文這里介紹 使用 android studio Emulator &#xff0c; 當然也有 Anbox (Lightweight)&#xff0c; Waydroid (Best for Full Android Experience), 首先確保自己安裝了 android studio &#xff1b; sudo apt update sudo apt install openjdk-11-jdk sudo snap install…