您的位置:首页 > 移动开发 > Objective-C

Qt MetaObject System详解之四:meta call

2018-03-30 14:58 615 查看
所谓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 plaincopy to clipboardprint?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);  
}  
先依据传递的方法名称和参数,构造完整的函数签名(存储在局部变量sig)。参数的类型名就是调用时传递时的参数静态类型,这里可不会有什么类型转换,这是运行时的行为,参数类型转换是编译时的行为。然后通过这个sig签名在obj中去查找该方法,查询的结果就是一个QMetaMethod值,再将调用委托给QMetaMethod::invoke方法。view plaincopy to clipboardprint?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.
150b1
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;  
}  
代码首先检查返回值的类型是否正确;再检查参数的个数是否匹配,看懂这段代码需要参考该系列之二对moc文件的解析;再依据当前线程和被调对象所属 线程来调整connnection type;如果是directconnection,直接调用 QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param),param是将所有参数值指针排列组成的指针数组。如果不是directconnection,也即异步调用,就通过一个post一个 QMetaCallEvent到obj,此时须将所有的参数复制一份存入event对象。QMetaObject::metacall的实现如下:view plaincopy to clipboardprint?/*! 
    \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);  
}   
如果object->d_ptr->metaObject(QMetaObjectPrivate)存在,通过该metaobject 来调用,这里要参考该系列之三对QMetaObjectPrivate的介绍,这个条件实际上就是object就是QObject类型,而不是派生类型。 否则调用object::qt_metacall。对于异步调用,QObject的event函数里有如下代码:view plaincopy to clipboardprint?    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;  
        }  
QMetaCallEvent的代码很简单:int QMetaCallEvent::placeMetaCall(QObject *object)
{    return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, id_, args_);}殊途同归。最后来看一下object->qt_metacall是如何实现的,这又回到了该系统之二所提供的示例moc文件中去了。该文件提供了该方法的实现:view plaincopy to clipboardprint?# 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;    
# }    
这段代码将调用最终转到我们自己的实现的函数中来。这个函数不经提供了metamethod的动态调用,而且也提供了property的动态操作方法。可想而知,property的动态调用的实现方式一定和invocalbe method是一致的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: