分享

Qt MetaObject System详解之四:meta call

 懒人海马 2023-02-27 发布于山东

所谓meta call就是通过object的meta system的支持来动态调用object的方法,metacall也是signal&slot的机制的基石。本篇通过参考源代码来探究meta call的实现方法。

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这个静态方法可以动态地调用obj对象名字为member的方法,type参数表明该调用时同步的还是异步的。ret是一个 通用的用来存储返回值的类型,后面的9个参数是用来传递调用参数的,QGenericArgument()是一种通用的存储参数值的类型。(这里让人感觉 比较奇怪的是Qt为什么不将这个参数列表弄成某种动态的形式,而是最多九个)

所调用的方法必须是invocable的,也就是signal,slot或者是加了声明为Q_INVOCABLE的其他方法。

这个方法的实现如下:

view plain copy to clipboard print ?

  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. }  

先依据传递的方法名称和参数,构造完整的函数签名(存储在局部变量sig)。参数的类型名就是调用时传递时的参数静态类型,这里可不会有什么类型转换,这是运行时的行为,参数类型转换是编译时的行为。

然后通过这个sig签名在obj中去查找该方法,查询的结果就是一个QMetaMethod值,再将调用委托给QMetaMethod::invoke方法。

view plain copy to clipboard print ?

  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. }  

代码首先检查返回值的类型是否正确;再检查参数的个数是否匹配,看懂这段代码需要参考该系列之二对moc文件的解析;再依据当前线程和被调对象所属 线程来调整connnection type;如果是directconnection,直接调用 QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param),param是将所有参数值指针排列组成的指针数组。如果不是directconnection,也即异步调用,就通过一个post一个 QMetaCallEvent到obj,此时须将所有的参数复制一份存入event对象。

QMetaObject::metacall的实现如下:

view plain copy to clipboard print ?

  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. }   

如果object->d_ptr->metaObject(QMetaObjectPrivate)存在,通过该metaobject 来调用,这里要参考该系列之三对QMetaObjectPrivate的介绍,这个条件实际上就是object就是QObject类型,而不是派生类型。 否则调用object::qt_metacall。

对于异步调用,QObject的event函数里有如下代码:

view plain copy to clipboard print ?

  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.         }  

QMetaCallEvent的代码很简单:

int QMetaCallEvent::placeMetaCall(QObject *object)
{    return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, id_, args_);}

殊途同归。

最后来看一下object->qt_metacall是如何实现的,这又回到了该系统之二所提供的示例moc文件中去了。该文件提供了该方法的实现:

view plain copy to clipboard print ?

  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. # }    

这段代码将调用最终转到我们自己的实现的函数中来。这个函数不经提供了metamethod的动态调用,而且也提供了property的动态操作方法。可想而知,property的动态调用的实现方式一定和invocalbe method是一致的。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多