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