The so-called meta call is to dynamically invoke the method of object through the support of meta system of object. metacall is also the cornerstone of signal & slot mechanism. This article explores the implementation of meta call by referring to the source code.
QMetaObject::invokeMethod():
bool invokeMethod ( QObject * obj , const char * member , Qt::ConnectionType type , QGenericReturnArgument ret , QGenericArgument val0 = QGenericArgument( 0 ), 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() )
QMetaObject is a static method that can dynamically call a method whose obj object name is member. The type parameter indicates whether the call is synchronous or asynchronous. ret is a generic type for storing returned values. The following nine parameters are used to pass call parameters. QGenericArgument() is a generic type for storing parameter values. (It's strange here that Qt doesn't make this parameter list a dynamic one, but a maximum of nine.)
The method invoked must be invocable, that is, signal, slot, or any other method declared Q_INVOCABLE.
The implementation of this method is as follows:
- if (!obj)
- return false;
- 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');
- int idx = obj->metaObject()->indexOfMethod(sig.constData());
- if (idx < 0) {
- QByteArray norm = QMetaObject::normalizedSignature(sig.constData());
- idx = obj->metaObject()->indexOfMethod(norm.constData());
- }
- if (idx < 0 || idx >= obj->metaObject()->methodCount())
- return false;
- QMetaMethod method = obj->metaObject()->method(idx);
- return method.invoke(obj, type, ret,
- val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
- }
Firstly, a complete function signature (stored in local variable sig) is constructed according to the method name and parameters passed. The type name of the parameter is the static type of the parameter passed during invocation. There is no type conversion here. This is the behavior of the runtime, and the type conversion of the parameter is the behavior of the compiler.
The sig signature is then used to find the method in obj. The result of the query is a QMetaMethod value, and then the call is delegated to the QMetaMethod:: invoke method.
- 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;
- // check return type
- if (returnValue.data()) {
- const char *retType = typeName();
- if (qstrcmp(returnValue.name(), retType) != 0) {
- // normalize the return value as well
- // the trick here is to make a function signature out of the return type
- // so that we can call normalizedSignature() and avoid duplicating code
- QByteArray unnormalized;
- int len = qstrlen(returnValue.name());
- unnormalized.reserve(len + 3);
- unnormalized = "_("; // the function is called "_"
- unnormalized.append(returnValue.name());
- unnormalized.append(')');
- QByteArray normalized = QMetaObject::normalizedSignature(unnormalized.constData());
- normalized.truncate(normalized.length() - 1); // drop the ending ')'
- if (qstrcmp(normalized.constData() + 2, retType) != 0)
- 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;
- }
- int metaMethodArgumentCount = 0;
- {
- // based on QMetaObject::parameterNames()
- const char *names = mobj->d.stringdata + mobj->d.data[handle + 1];
- if (*names == 0) {
- // do we have one or zero arguments?
- const char *signature = mobj->d.stringdata + mobj->d.data[handle];
- while (*signature && *signature != '(')
- ++signature;
- if (*++signature != ')')
- ++metaMethodArgumentCount;
- } else {
- --names;
- do {
- ++names;
- while (*names && *names != ',')
- ++names;
- ++metaMethodArgumentCount;
- } while (*names);
- }
- }
- if (paramCount <= metaMethodArgumentCount)
- return false;
- // check connection type
- QThread *currentThread = QThread::currentThread();
- QThread *objectThread = object->thread();
- if (connectionType == Qt::AutoConnection) {
- connectionType = currentThread == objectThread
- ? Qt::DirectConnection
- : Qt::QueuedConnection;
- }
- // 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()
- };
- // recompute the methodIndex by reversing the arithmetic in QMetaObject::property()
- int methodIndex = ((handle - priv(mobj->d.data)->methodData) / 5) + mobj->methodOffset();
- if (connectionType == Qt::DirectConnection) {
- return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param) < 0;
- } else {
- 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 **) qMalloc(paramCount * sizeof(void *));
- Q_CHECK_PTR(args);
- int *types = (int *) qMalloc(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]) {
- args[i] = QMetaType::construct(types[i], param[i]);
- ++nargs;
- } else if (param[i]) {
- 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]);
- }
- qFree(types);
- qFree(args);
- return false;
- }
- }
- if (connectionType == Qt::QueuedConnection) {
- QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,
- 0,
- -1,
- nargs,
- types,
- args));
- } else {
- if (currentThread == objectThread) {
- qWarning("QMetaMethod::invoke: Dead lock detected in "
- "BlockingQueuedConnection: Receiver is %s(%p)",
- mobj->className(), object);
- }
- // blocking queued connection
- #ifdef QT_NO_THREAD
- QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,
- 0,
- -1,
- nargs,
- types,
- args));
- #else
- QSemaphore semaphore;
- QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,
- 0,
- -1,
- nargs,
- types,
- args,
- &semaphore));
- semaphore.acquire();
- #endif // QT_NO_THREAD
- }
- }
- return true;
- }
The code first checks whether the type of return value is correct; then checks whether the number of parameters matches, to understand the code needs to refer to the second of the series of moc file parsing; then adjusts the connnection type according to the current thread and the thread to which the object is called; if it is directconnection, directly calls QMetaObject:: metacall (object, QMetaObject:: InvokeMetaMethod, methodIndex). Param, param, is an array of pointers arranged by pointers of all parameter values. If it is not a direct connection, that is, an asynchronous call, a QMetaCallEvent is post ed to obj, at which point all the parameters must be copied into an event object.
QMetaObject::metacall is implemented as follows:
- /*!
- \internal
- */
- int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)
- {
- if (QMetaObject *mo = object->d_ptr->metaObject)
- return static_cast(mo)->metaCall(cl, idx, argv);
- else
- return object->qt_metacall(cl, idx, argv);
- }
If the object - > d_ptr - > metaObject (QMetaObject Private) exists and is invoked through the metaobject, refer to the introduction of QMetaObject Private in the third part of this series. This condition is that the object is actually a QObject type, not a derived type. Otherwise, object:: qt_metacall is called.
For asynchronous calls, QObject's event function has the following code:
- case QEvent::MetaCall:
- {
- d_func()->inEventHandler = false;
- QMetaCallEvent *mce = static_cast(e);
- QObjectPrivate::Sender currentSender;
- currentSender.sender = const_cast(mce->sender());
- currentSender.signal = mce->signalId();
- currentSender.ref = 1;
- QObjectPrivate::Sender * const previousSender =
- QObjectPrivate::setCurrentSender(this, ¤tSender);
- #if defined(QT_NO_EXCEPTIONS)
- mce->placeMetaCall(this);
- #else
- QT_TRY {
- mce->placeMetaCall(this);
- } QT_CATCH(...) {
- QObjectPrivate::resetCurrentSender(this, ¤tSender, previousSender);
- QT_RETHROW;
- }
- #endif
- QObjectPrivate::resetCurrentSender(this, ¤tSender, previousSender);
- break;
- }
The code for QMetaCallEvent is simple:
int QMetaCallEvent::placeMetaCall(QObject *object)
{ return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, id_, args_);}
All roads lead to Rome.
Finally, let's look at how object - > qt_metacall is implemented, which goes back to the example moc file provided by System II. This document provides an implementation of this method:
- # int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
- # {
- # _id = QObject::qt_metacall(_c, _id, _a);
- # if (_id < 0)
- # return _id;
- # if (_c == QMetaObject::InvokeMetaMethod) {
- # switch (_id) {
- # case 0: clicked(); break;
- # case 1: pressed(); break;
- # case 2: onEventA((*reinterpret_cast< const QString(*)>(_a[1]))); break;
- # case 3: onEventB((*reinterpret_cast< int(*)>(_a[1]))); break;
- # default: ;
- # }
- # _id -= 4;
- # }
- # #ifndef QT_NO_PROPERTIES
- # else if (_c == QMetaObject::ReadProperty) {
- # void *_v = _a[0];
- # switch (_id) {
- # case 0: *reinterpret_cast< QString*>(_v) = getPropertyA(); break;
- # case 1: *reinterpret_cast< QString*>(_v) = getPropertyB(); break;
- # }
- # _id -= 2;
- # } else if (_c == QMetaObject::WriteProperty) {
- # void *_v = _a[0];
- # switch (_id) {
- # case 0: getPropertyA(*reinterpret_cast< QString*>(_v)); break;
- # case 1: getPropertyB(*reinterpret_cast< QString*>(_v)); break;
- # }
- # _id -= 2;
- # } else if (_c == QMetaObject::ResetProperty) {
- # switch (_id) {
- # case 0: resetPropertyA(); break;
- # case 1: resetPropertyB(); break;
- # }
- # _id -= 2;
- # } else if (_c == QMetaObject::QueryPropertyDesignable) {
- # _id -= 2;
- # } else if (_c == QMetaObject::QueryPropertyScriptable) {
- # _id -= 2;
- # } else if (_c == QMetaObject::QueryPropertyStored) {
- # _id -= 2;
- # } else if (_c == QMetaObject::QueryPropertyEditable) {
- # _id -= 2;
- # } else if (_c == QMetaObject::QueryPropertyUser) {
- # _id -= 2;
- # }
- # #endif // QT_NO_PROPERTIES
- # return _id;
- # }
This code will eventually transfer the call to our own implemented function. This function does not provide a dynamic call to metamethod, but also provides a dynamic operation method of property. It is conceivable that the dynamic invocation of property must be implemented in the same way as invocalbe method.