Qt MetaObject System Explanation 4: Metacall

Keywords: Qt

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:

  1. if (!obj)  
  2.         return false;  
  3.     QVarLengthArray<char, 512> sig;  
  4.     int len = qstrlen(member);  
  5.     if (len <= 0)  
  6.         return false;  
  7.     sig.append(member, len);  
  8.     sig.append('(');  
  9.     const char *typeNames[] = {ret.name(), val0.name(), val1.name(), val2.name(), val3.name(),  
  10.                                val4.name(), val5.name(), val6.name(), val7.name(), val8.name(),  
  11.                                val9.name()};  
  12.     int paramCount;  
  13.     for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {  
  14.         len = qstrlen(typeNames[paramCount]);  
  15.         if (len <= 0)  
  16.             break;  
  17.         sig.append(typeNames[paramCount], len);  
  18.         sig.append(',');  
  19.     }  
  20.     if (paramCount == 1)  
  21.         sig.append(')'); // no parameters  
  22.     else  
  23.         sig[sig.size() - 1] = ')';  
  24.     sig.append('\0');  
  25.     int idx = obj->metaObject()->indexOfMethod(sig.constData());  
  26.     if (idx < 0) {  
  27.         QByteArray norm = QMetaObject::normalizedSignature(sig.constData());  
  28.         idx = obj->metaObject()->indexOfMethod(norm.constData());  
  29.     }  
  30.     if (idx < 0 || idx >= obj->metaObject()->methodCount())  
  31.         return false;  
  32.     QMetaMethod method = obj->metaObject()->method(idx);  
  33.     return method.invoke(obj, type, ret,  
  34.                          val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);  
  35. }  

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.

  1. bool QMetaMethod::invoke(QObject *object,  
  2.                          Qt::ConnectionType connectionType,  
  3.                          QGenericReturnArgument returnValue,  
  4.                          QGenericArgument val0,  
  5.                          QGenericArgument val1,  
  6.                          QGenericArgument val2,  
  7.                          QGenericArgument val3,  
  8.                          QGenericArgument val4,  
  9.                          QGenericArgument val5,  
  10.                          QGenericArgument val6,  
  11.                          QGenericArgument val7,  
  12.                          QGenericArgument val8,  
  13.                          QGenericArgument val9) const  
  14. {  
  15.     if (!object || !mobj)  
  16.         return false;  
  17.     // check return type  
  18.     if (returnValue.data()) {  
  19.         const char *retType = typeName();  
  20.         if (qstrcmp(returnValue.name(), retType) != 0) {  
  21.             // normalize the return value as well  
  22.             // the trick here is to make a function signature out of the return type  
  23.             // so that we can call normalizedSignature() and avoid duplicating code  
  24.             QByteArray unnormalized;  
  25.             int len = qstrlen(returnValue.name());  
  26.             unnormalized.reserve(len + 3);  
  27.             unnormalized = "_(";        // the function is called "_"  
  28.             unnormalized.append(returnValue.name());  
  29.             unnormalized.append(')');  
  30.             QByteArray normalized = QMetaObject::normalizedSignature(unnormalized.constData());  
  31.             normalized.truncate(normalized.length() - 1); // drop the ending ')'  
  32.             if (qstrcmp(normalized.constData() + 2, retType) != 0)  
  33.                 return false;  
  34.         }  
  35.     }  
  36.     // check argument count (we don't allow invoking a method if given too few arguments)  
  37.     const char *typeNames[] = {  
  38.         returnValue.name(),  
  39.         val0.name(),  
  40.         val1.name(),  
  41.         val2.name(),  
  42.         val3.name(),  
  43.         val4.name(),  
  44.         val5.name(),  
  45.         val6.name(),  
  46.         val7.name(),  
  47.         val8.name(),  
  48.         val9.name()  
  49.     };  
  50.     int paramCount;  
  51.     for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {  
  52.         if (qstrlen(typeNames[paramCount]) <= 0)  
  53.             break;  
  54.     }  
  55.     int metaMethodArgumentCount = 0;  
  56.     {  
  57.         // based on QMetaObject::parameterNames()  
  58.         const char *names = mobj->d.stringdata + mobj->d.data[handle + 1];  
  59.         if (*names == 0) {  
  60.             // do we have one or zero arguments?  
  61.             const char *signature = mobj->d.stringdata + mobj->d.data[handle];  
  62.             while (*signature && *signature != '(')  
  63.                 ++signature;  
  64.             if (*++signature != ')')  
  65.                 ++metaMethodArgumentCount;  
  66.         } else {  
  67.             --names;  
  68.             do {  
  69.                 ++names;  
  70.                 while (*names && *names != ',')  
  71.                     ++names;  
  72.                 ++metaMethodArgumentCount;  
  73.             } while (*names);  
  74.         }  
  75.     }  
  76.     if (paramCount <= metaMethodArgumentCount)  
  77.         return false;  
  78.     // check connection type  
  79.     QThread *currentThread = QThread::currentThread();  
  80.     QThread *objectThread = object->thread();  
  81.     if (connectionType == Qt::AutoConnection) {  
  82.         connectionType = currentThread == objectThread  
  83.                          ? Qt::DirectConnection  
  84.                          : Qt::QueuedConnection;  
  85.     }  
  86.     // invoke!  
  87.     void *param[] = {  
  88.         returnValue.data(),  
  89.         val0.data(),  
  90.         val1.data(),  
  91.         val2.data(),  
  92.         val3.data(),  
  93.         val4.data(),  
  94.         val5.data(),  
  95.         val6.data(),  
  96.         val7.data(),  
  97.         val8.data(),  
  98.         val9.data()  
  99.     };  
  100.     // recompute the methodIndex by reversing the arithmetic in QMetaObject::property()  
  101.     int methodIndex = ((handle - priv(mobj->d.data)->methodData) / 5) + mobj->methodOffset();  
  102.     if (connectionType == Qt::DirectConnection) {  
  103.         return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param) < 0;  
  104.     } else {  
  105.         if (returnValue.data()) {  
  106.             qWarning("QMetaMethod::invoke: Unable to invoke methods with return values in "  
  107.                      "queued connections");  
  108.             return false;  
  109.         }  
  110.         int nargs = 1; // include return type  
  111.         void **args = (void **) qMalloc(paramCount * sizeof(void *));  
  112.         Q_CHECK_PTR(args);  
  113.         int *types = (int *) qMalloc(paramCount * sizeof(int));  
  114.         Q_CHECK_PTR(types);  
  115.         types[0] = 0; // return type  
  116.         args[0] = 0;  
  117.         for (int i = 1; i < paramCount; ++i) {  
  118.             types[i] = QMetaType::type(typeNames[i]);  
  119.             if (types[i]) {  
  120.                 args[i] = QMetaType::construct(types[i], param[i]);  
  121.                 ++nargs;  
  122.             } else if (param[i]) {  
  123.                 qWarning("QMetaMethod::invoke: Unable to handle unregistered datatype '%s'",  
  124.                          typeNames[i]);  
  125.                 for (int x = 1; x < i; ++x) {  
  126.                     if (types[x] && args[x])  
  127.                         QMetaType::destroy(types[x], args[x]);  
  128.                 }  
  129.                 qFree(types);  
  130.                 qFree(args);  
  131.                 return false;  
  132.             }  
  133.         }  
  134.         if (connectionType == Qt::QueuedConnection) {  
  135.             QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,  
  136.                                                                    0,  
  137.                                                                    -1,  
  138.                                                                    nargs,  
  139.                                                                    types,  
  140.                                                                    args));  
  141.         } else {  
  142.             if (currentThread == objectThread) {  
  143.                 qWarning("QMetaMethod::invoke: Dead lock detected in "  
  144.                          "BlockingQueuedConnection: Receiver is %s(%p)",  
  145.                          mobj->className(), object);  
  146.             }  
  147.             // blocking queued connection  
  148. #ifdef QT_NO_THREAD  
  149.             QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,  
  150.                                                                    0,  
  151.                                                                    -1,  
  152.                                                                    nargs,  
  153.                                                                    types,  
  154.                                                                    args));  
  155. #else  
  156.             QSemaphore semaphore;  
  157.             QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex,  
  158.                                                                    0,  
  159.                                                                    -1,  
  160.                                                                    nargs,  
  161.                                                                    types,  
  162.                                                                    args,  
  163.                                                                    &semaphore));  
  164.             semaphore.acquire();  
  165. #endif // QT_NO_THREAD  
  166.         }  
  167.     }  
  168.     return true;  
  169. }  

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:

  1. /*! 
  2.     \internal 
  3. */  
  4. int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)  
  5. {  
  6.     if (QMetaObject *mo = object->d_ptr->metaObject)  
  7.         return static_cast(mo)->metaCall(cl, idx, argv);  
  8.     else  
  9.         return object->qt_metacall(cl, idx, argv);  
  10. }   

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:

  1.     case QEvent::MetaCall:  
  2.         {  
  3.             d_func()->inEventHandler = false;  
  4.             QMetaCallEvent *mce = static_cast(e);  
  5.             QObjectPrivate::Sender currentSender;  
  6.             currentSender.sender = const_cast(mce->sender());  
  7.             currentSender.signal = mce->signalId();  
  8.             currentSender.ref = 1;  
  9.             QObjectPrivate::Sender * const previousSender =  
  10.                 QObjectPrivate::setCurrentSender(this, ¤tSender);  
  11. #if defined(QT_NO_EXCEPTIONS)  
  12.             mce->placeMetaCall(this);  
  13. #else  
  14.             QT_TRY {  
  15.                 mce->placeMetaCall(this);  
  16.             } QT_CATCH(...) {  
  17.                 QObjectPrivate::resetCurrentSender(this, ¤tSender, previousSender);  
  18.                 QT_RETHROW;  
  19.             }  
  20. #endif  
  21.             QObjectPrivate::resetCurrentSender(this, ¤tSender, previousSender);  
  22.             break;  
  23.         }  

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:

  1. # int TestObject::qt_metacall(QMetaObject::Call _c, int _id, void **_a)    
  2. # {    
  3. #     _id = QObject::qt_metacall(_c, _id, _a);    
  4. #     if (_id < 0)    
  5. #         return _id;    
  6. #     if (_c == QMetaObject::InvokeMetaMethod) {    
  7. #         switch (_id) {    
  8. #         case 0: clicked(); break;    
  9. #         case 1: pressed(); break;    
  10. #         case 2: onEventA((*reinterpret_cast< const QString(*)>(_a[1]))); break;    
  11. #         case 3: onEventB((*reinterpret_cast< int(*)>(_a[1]))); break;    
  12. #         default: ;    
  13. #         }    
  14. #         _id -= 4;    
  15. #     }    
  16. # #ifndef QT_NO_PROPERTIES    
  17. #       else if (_c == QMetaObject::ReadProperty) {    
  18. #         void *_v = _a[0];    
  19. #         switch (_id) {    
  20. #         case 0: *reinterpret_cast< QString*>(_v) = getPropertyA(); break;    
  21. #         case 1: *reinterpret_cast< QString*>(_v) = getPropertyB(); break;    
  22. #         }    
  23. #         _id -= 2;    
  24. #     } else if (_c == QMetaObject::WriteProperty) {    
  25. #         void *_v = _a[0];    
  26. #         switch (_id) {    
  27. #         case 0: getPropertyA(*reinterpret_cast< QString*>(_v)); break;    
  28. #         case 1: getPropertyB(*reinterpret_cast< QString*>(_v)); break;    
  29. #         }    
  30. #         _id -= 2;    
  31. #     } else if (_c == QMetaObject::ResetProperty) {    
  32. #         switch (_id) {    
  33. #         case 0: resetPropertyA(); break;    
  34. #         case 1: resetPropertyB(); break;    
  35. #         }    
  36. #         _id -= 2;    
  37. #     } else if (_c == QMetaObject::QueryPropertyDesignable) {    
  38. #         _id -= 2;    
  39. #     } else if (_c == QMetaObject::QueryPropertyScriptable) {    
  40. #         _id -= 2;    
  41. #     } else if (_c == QMetaObject::QueryPropertyStored) {    
  42. #         _id -= 2;    
  43. #     } else if (_c == QMetaObject::QueryPropertyEditable) {    
  44. #         _id -= 2;    
  45. #     } else if (_c == QMetaObject::QueryPropertyUser) {    
  46. #         _id -= 2;    
  47. #     }    
  48. # #endif // QT_NO_PROPERTIES    
  49. #     return _id;    
  50. # }    

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.


Posted by Ringo on Sat, 15 Dec 2018 04:21:04 -0800