精华内容
下载资源
问答
  • 基于Qt5.14.2和mingw的Qt源码学习(四) — 元对象系统之invoke原理及反射实践


    上次我们看了Qt是如何把类信息生成到moc文件中。那么这次我们来看看Qt是如何根据对象和函数名称动态调用函数的。

    一、invoke函数

    QMetaMethod 中重载了几种 invoke 函数,我们挑其中一个作为例子分析其实现原理。

    // qmetaobject.cpp
    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
    {
        ...
    }
    

    1、步骤一 — 对象关系检查

    	Q_ASSERT(mobj->cast(object));
    
    QObject *QMetaObject::cast(QObject *obj) const
    {
        // ### Qt 6: inline
        return const_cast<QObject*>(cast(const_cast<const QObject*>(obj)));
    }
    
    const QObject *QMetaObject::cast(const QObject *obj) const
    {
        return (obj && obj->metaObject()->inherits(this)) ? obj : nullptr;
    }
    
    bool QMetaObject::inherits(const QMetaObject *metaObject) const noexcept
    {
        const QMetaObject *m = this;
        do {
            if (metaObject == m)
                return true;
        } while ((m = m->d.superdata));
        return false;
    }
    

    inherits 方法也是我们常用的方法,是确定传入的元对象是否为this的超类。那么这里从下往上看可以看出,第一步的作用是判断 object 是否继承于 mobj(拥有此 QMetaMethod 的元对象)。这是由于当我们通过元对象调用其超类方法时,所返回的 QMetaMethod 中存的元对象实际是超类对象。

    2、步骤二 — 返回值类型匹配

    // check return type
    if (returnValue.data()) {
        const char *retType = typeName();
        if (qstrcmp(returnValue.name(), retType) != 0) {
            // normalize the return value as well
            QByteArray 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;
            }
        }
    }
    

    如果有返回值类型这里需要对返回值类型进行匹配。

    (1)QByteArray QMetaObject::normalizedType(const char *type)

    这个函数的作用是把类型标准化。参考Qt手册上的解释,我们知道它是把类型上的const和空格去掉。同时文档上也说了,在检查信号和槽是否匹配时也是采取相同的方式对类型进行了标准化处理。
    看到这,我想这个部分还挺神奇的,函数类型名称不匹配还要继续比较。关键在于下面这个函数。

    (2)int QMetaType::type(const char *typeName)

    // qmetatype.cpp
    int QMetaType::type(const char *typeName)
    {
        return qMetaTypeTypeImpl</*tryNormalizedType=*/true>(typeName, qstrlen(typeName));
    }
    
    // qmetatype.cpp
    template <bool tryNormalizedType>
    static inline int qMetaTypeTypeImpl(const char *typeName, int length)
    {
        if (!length)
            return QMetaType::UnknownType;
        int type = qMetaTypeStaticType(typeName, length);
        if (type == QMetaType::UnknownType) {
            QReadLocker locker(customTypesLock());
            type = qMetaTypeCustomType_unlocked(typeName, length);
    #ifndef QT_NO_QOBJECT
            if ((type == QMetaType::UnknownType) && tryNormalizedType) {
                const NS(QByteArray) normalizedTypeName = QMetaObject::normalizedType(typeName);
                type = qMetaTypeStaticType(normalizedTypeName.constData(),
                                           normalizedTypeName.size());
                if (type == QMetaType::UnknownType) {
                    type = qMetaTypeCustomType_unlocked(normalizedTypeName.constData(),
                                                        normalizedTypeName.size());
                }
            }
    #endif
        }
        return type;
    }
    
    static inline int qMetaTypeStaticType(const char *typeName, int length)
    {
        int i = 0;
        while (types[i].typeName && ((length != types[i].typeNameLength)
                                     || memcmp(typeName, types[i].typeName, length))) {
            ++i;
        }
        return types[i].type;
    }
    

    这里的 types 是一个 static 变量,储存了Qt支持的原生类型名、类型名长度、类型ID。查看其初始化可以看到qt支持的原生类型包括哪些(太多了这里就不粘贴了)。那么这个函数最后返回的就是类型的唯一标识了。看完这个函数我们就能理解前面为什么判断完类型名是否匹配还要判断ID了。参考该数组的一部分

    // qmetatype.h
    #define QT_FOR_EACH_STATIC_ALIAS_TYPE(F)\
        F(ULong, -1, ulong, "unsigned long")
    

    所以我们可以知道不同的类型名也可以对应相同的类型。但是类型ID是唯一的。
    types 数组最后初始化了 QMetaTypeId2::MetaTypeqreal,这两个是单独声明的,我也不知道干什么的,有可能是用在 qtdbus 中的。
    这里的 qMetaTypeCustomType_unlocked 方法实际就是在搜索用户自定义类型。下面 ifndef 部分实际就是把类型名标准化之后重复了上述的搜索步骤。
    那我们现在单独讨论如何设置自定义类型及变量。

    3、补充 — 自定义类型

    customTypes 储存了所有的用户自定义类型。

    (1)(QVector, customTypes)

    这个宏的作用可以直接上Qt的手册上查询,下面来看看实现。

    // qglobalstatic.h
    #define Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ARGS)                         \
        namespace { namespace Q_QGS_ ## NAME {                                  \
            typedef TYPE Type;                                                  \
            QBasicAtomicInt guard = Q_BASIC_ATOMIC_INITIALIZER(QtGlobalStatic::Uninitialized); \
            Q_GLOBAL_STATIC_INTERNAL(ARGS)                                      \
        } }                                                                     \
        static QGlobalStatic<TYPE,                                              \
                             Q_QGS_ ## NAME::innerFunction,                     \
                             Q_QGS_ ## NAME::guard> NAME;
    
    #define Q_GLOBAL_STATIC(TYPE, NAME)                                         \
        Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ())
    

    说实话,这个匿名空间嵌套命名空间,我看了很久没看出来作用。

    a. QtGlobalStatic::Uninitialized

    namespace QtGlobalStatic {
    enum GuardValues {
        Destroyed = -2,
        Initialized = -1,
        Uninitialized = 0,
        Initializing = 1
    };
    }
    

    这里的 QtGlobalStatic::Uninitialized 是Qt定义的全局变量值的枚举。看着名称有点神奇,莫不是靠状态控制Qt初始化进程?

    b. QBasicAtomicInt

    这个是Qt的一个原子操作类。以前我一直以为直接对 intchar 等基本类型的操作都是原子类型。这次上网看了看原子操作浅谈。基本类型是否为原子类型操作取决于编译系统生成的汇编指令个数。
    那么什么是原子操作呢?原子操作是指不会被线程调度机制打断的操作。所以这种声明为原子类型的数据都是线程安全的。那么这里就不过多展开了,因为原子类型的实现多跟处理器有关。
    那么这里把 guard 声明为原子类型变量就是保证该变量只会被初始化一次,即使这个宏在多处被引用。

    c. innerFunction

    这个方法的提供是为了保证手册里说的宏 Q_GLOBAL_STATIC 可以被当做指针使用。查看结构体 QGlobalStatic* 、_->__ 就会发现他们实际上都是通过这个函数进行调用。这个方法实际就是类似于单例模式。保证只有一个静态变量被创建。

    // qglobalstatic.h
    #define Q_GLOBAL_STATIC_INTERNAL(ARGS)                                  \
        Q_DECL_HIDDEN inline Type *innerFunction()                          \
        {                                                                   \
            static Type *d;                                                 \
            static QBasicMutex mutex;                                       \
            int x = guard.loadAcquire();                                    \
            if (Q_UNLIKELY(x >= QtGlobalStatic::Uninitialized)) {           \
                const std::lock_guard<QBasicMutex> locker(mutex);           \
                if (guard.loadRelaxed() == QtGlobalStatic::Uninitialized) {        \
                    d = new Type ARGS;                                      \
                    static struct Cleanup {                                 \
                        ~Cleanup() {                                        \
                            delete d;                                       \
                            guard.storeRelaxed(QtGlobalStatic::Destroyed);         \
                        }                                                   \
                    } cleanup;                                              \
                    guard.storeRelease(QtGlobalStatic::Initialized);        \
                }                                                           \
            }                                                               \
            return d;                                                       \
        }
    

    那么上述代码就是创建了 QGlobalStatic 的变量 NAME。在调用此变量时实际是调用了更内部的静态变量 d。注意,这里的 _guard 变量正如我们上面推测的,是用于控制变量状态的。由于其被声明为原子类型,因此我们可以使用它来控制全局变量只被初始化一次。

    现在我们知道用户自定义类型是个vector,接下来我们就要寻找怎样能向这个vector插值和获取。

    (2)qMetaTypeCustomType_unlocked

    // qmetatype.cpp
    static int qMetaTypeCustomType_unlocked(const char *typeName, int length, int *firstInvalidIndex = nullptr)
    {
        const QVector<QCustomTypeInfo> * const ct = customTypes();
        if (!ct)
            return QMetaType::UnknownType;
    
        if (firstInvalidIndex)
            *firstInvalidIndex = -1;
        for (int v = 0; v < ct->count(); ++v) {
            const QCustomTypeInfo &customInfo = ct->at(v);
            if ((length == customInfo.typeName.size())
                && !memcmp(typeName, customInfo.typeName.constData(), length)) {
                if (customInfo.alias >= 0)
                    return customInfo.alias;
                return v + QMetaType::User;
            }
            if (firstInvalidIndex && (*firstInvalidIndex < 0) && customInfo.typeName.isEmpty())
                *firstInvalidIndex = v;
        }
        return QMetaType::UnknownType;
    }
    

    unlocked — 表示这个查找用户自定义类型的方法是没有在内部加锁的。
    firstInvalidIndex — 作为输出参数,表示的作用就是第一个无效的也就是类型名为空的用户自定义类型。
    返回值 — 这里的返回值区分了 customInfo.alias。那么结合我们前面提到的类型id,我们可以猜测这个值多半是用户指定的类型ID。在用户不指定用户ID的时候,Qt所分配的ID是根据下标来的。因此如果我们不指定ID,使用时应该要注意在声明类型的时候获取其编译器分配的类型ID并保存起来。

    (3)qRegisterMetaType(const char *typeName)

    这个方法是我们用来注册自定义类和结构体的。其实代码很简单,就是把传进去的名字标准化再调用 qRegisterNormalizedMetaType

    // qmetatype.h
    template <typename T>
    int qRegisterNormalizedMetaType(const QT_PREPEND_NAMESPACE(QByteArray) &normalizedTypeName
    #ifndef Q_CLANG_QDOC
        , T * dummy = 0
        , typename QtPrivate::MetaTypeDefinedHelper<T, QMetaTypeId2<T>::Defined && !QMetaTypeId2<T>::IsBuiltIn>::DefinedType defined = QtPrivate::MetaTypeDefinedHelper<T, QMetaTypeId2<T>::Defined && !QMetaTypeId2<T>::IsBuiltIn>::Defined
    #endif
    )
    {
    #ifndef QT_NO_QOBJECT
        Q_ASSERT_X(normalizedTypeName == QMetaObject::normalizedType(normalizedTypeName.constData()), "qRegisterNormalizedMetaType", "qRegisterNormalizedMetaType was called with a not normalized type name, please call qRegisterMetaType instead.");
    #endif
        const int typedefOf = dummy ? -1 : QtPrivate::QMetaTypeIdHelper<T>::qt_metatype_id();
        if (typedefOf != -1)
            return QMetaType::registerNormalizedTypedef(normalizedTypeName, typedefOf);
    
        QMetaType::TypeFlags flags(QtPrivate::QMetaTypeTypeFlags<T>::Flags);
    
        if (defined)
            flags |= QMetaType::WasDeclaredAsMetaType;
    
        const int id = QMetaType::registerNormalizedType(normalizedTypeName,
                                       QtMetaTypePrivate::QMetaTypeFunctionHelper<T>::Destruct,
                                       QtMetaTypePrivate::QMetaTypeFunctionHelper<T>::Construct,
                                       int(sizeof(T)),
                                       flags,
                                       QtPrivate::MetaObjectForType<T>::value());
    
        if (id > 0) {
            QtPrivate::SequentialContainerConverterHelper<T>::registerConverter(id);
            QtPrivate::AssociativeContainerConverterHelper<T>::registerConverter(id);
            QtPrivate::MetaTypePairHelper<T>::registerConverter(id);
            QtPrivate::MetaTypeSmartPointerHelper<T>::registerConverter(id);
        }
    
        return id;
    }
    

    a. 参数

    这个函数支持三个参数:
    normalizedTypeName 类型名
    dummy 是否在已注册类型中寻找要注册的类型
    defined 该类型是否被定义过。那么什么是被定义过的类型呢?我们跟着这个泛函数最后会跳转到如下泛函数结构体:

    template <typename T, int =
        QtPrivate::IsPointerToTypeDerivedFromQObject<T>::Value ? QMetaType::PointerToQObject :
        QtPrivate::IsGadgetHelper<T>::IsRealGadget             ? QMetaType::IsGadget :
        QtPrivate::IsPointerToGadgetHelper<T>::IsRealGadget    ? QMetaType::PointerToGadget :
        QtPrivate::IsQEnumHelper<T>::Value                     ? QMetaType::IsEnumeration : 0>
    struct QMetaTypeIdQObject
    {
        enum {
            Defined = 0
        };
    };
    

    当然,这个模板函数还有一些专门化处理,咱们这里就不列出来了。从这我们可以看出来定义过的类型包括:
    指向QObject类及其派生类的指针
    Gadget类 Gadget指的是由宏 __Q_GADGET__修饰的类。根据手册上的意义这个宏的作用是为那些不想声明为QObject类的提供反射的机制。
    指向Gadget类的指针
    指向QObject类及其派生类的指针 被Q_ENUM修饰的类
    其实仔细看代码的话,可以发现后面两个参数最后都是走到同一个专门化的模板分支中,那么我们看下查找id是否被声明的方法 — qt_metatype_id 的实现。

    b. qt_metatype_id

    static int qt_metatype_id()
        {
            static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0);
            if (const int id = metatype_id.loadAcquire())
                return id;
            const char * const cName = T::staticMetaObject.className();
            const int newId = qRegisterNormalizedMetaType<T>(
                cName,
                reinterpret_cast<T*>(quintptr(-1)));
            metatype_id.storeRelease(newId);
            return newId;
        }
    

    我们发现,只要调用的是这几个专门化的模板的id函数一定会返回正数。也就是说如果我们想新增类型并且让它走到传递的define参数为true的分支,我们传的第二个参数不能为空指针。我们试下:

    // main.cpp
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        CLS_ReflectionTest *clsRefTest = new CLS_ReflectionTest(nullptr);
        qRegisterMetaType<CLS_ReflectionTest*>("CLS_ReflectionTest", &clsRefTest);
        qRegisterMetaType<CLS_ReflectionTest*>("CLS_ReflectionTest");
    
        return a.exec();
    }
    

    跟断点到源码发现我们分析的没问题。那么这个 WasDeclaredAsMetaType 的作用是什么呢?全局搜了下就是在注销类型的时候,如果类型有这个标志,则不注销。。。看似分析了很多,其实作用很简单。

    c. QMetaType::registerNormalizedTypedef

    那么如果我们发现类型已定义过的话,我们就会走到这个函数这里。这个函数的实际作用就是注册相同id的不同名称。当然从外部调用我们知道,这里并不能对所有typedef声明的相同类型都识别,而是只能对定义好的那几种类型进行识别。但是这个函数我们是可以从外部调用的,一会可以测试下自己能否注册typedef的同id类型。

    d. QMetaType::registerNormalizedType

    这个函数是初始化类QCustomTypeInfo的所有变量。如果是已经声明过的用户自定义类型会更新它的一些属性。如果是Qt内置的类型,会检测二进制兼容性。

    e. constructor

    这里初始化的时候有一个变量值得注意,就是构造函数指针。

    static void *Construct(void *where, const void *t)
        {
            if (t)
                return new (where) T(*static_cast<const T*>(t));
            return new (where) T;
        }
    

    注意这里对于给定的类使用new操作符进行构造。那么也就是对于我们想要声明的类型,需要提供两个构造函数:拷贝构造函数和无参构造函数。否则编译都过不去。我一开始写的一个继承于QObject的函数直接调用这个函数是无法编译的,因为QObject已经被拷贝构造函数干掉了。

    (4)qRegisterMetaType() Q_DECLARE_METATYPE

    那么我们注意到 qRegisterMetaType 还有一个无参版本。

    template <typename T>
    inline Q_DECL_CONSTEXPR int qMetaTypeId()
    {
        Q_STATIC_ASSERT_X(QMetaTypeId2<T>::Defined, "Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system");
        return QMetaTypeId2<T>::qt_metatype_id();
    }
    
    template <typename T>
    inline Q_DECL_CONSTEXPR int qRegisterMetaType()
    {
        return qMetaTypeId<T>();
    }
    

    a. static_assert

    以前用过断言,用于在开发过程中更好的发现运行时错误。那么编译过程中也有断言机制,用于编译出错的时候对错误的内容进行提示。

    b. Q_DECLARE_METATYPE

    上面我们看过 qMetaTypeId 的声明,知道这是用于获取已定义类型的id。上面我们列举了Qt自己支持的几种类型。我们也可以通过 Q_DECLARE_METATYPE 去增加已定义类型。

    #define Q_DECLARE_METATYPE_IMPL(TYPE)                                   \
        QT_BEGIN_NAMESPACE                                                  \
        template <>                                                         \
        struct QMetaTypeId< TYPE >                                          \
        {                                                                   \
            enum { Defined = 1 };                                           \
            static int qt_metatype_id()                                     \
                {                                                           \
                    static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0); \
                    if (const int id = metatype_id.loadAcquire())           \
                        return id;                                          \
                    const int newId = qRegisterMetaType< TYPE >(#TYPE,      \
                                  reinterpret_cast< TYPE *>(quintptr(-1))); \
                    metatype_id.storeRelease(newId);                        \
                    return newId;                                           \
                }                                                           \
        };                                                                  \
        QT_END_NAMESPACE
    

    我们可以看到这个函数的内部还是调用我们刚才说过的有参 qRegisterMetaType 函数。同时它的第二个参数传的是一个非空指针,因为这里实在没必要去已注册类型中寻找。如果该类型已注册,这个专门化的模板根本声明不了也编译不过。

    c. 有参无参的区别

    两个区别:
    能否获得声明类型对应的类型ID
    能否对同一个类起别名

    4、步骤三 — 参数个数匹配

    这里检查的是name不为空的参数个数。

    // 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;
    

    5、步骤四 — 确定连接类型

    Qt::AutoConnection — 自动型,根据调用方法所对应对象所属线程和当前运行线程是否为同一线程选择。如果是,则使用 Qt::DirectConnection, 否则使用 Qt::QueuedConnection
    Qt::DirectConnection — 直接连接,相当于直接在当前位置运行该函数。
    Qt::QueuedConnection — 排队连接,发出一个event到接受者的线程,不等返回继续执行。
    Qt::BlockingQueuedConnection — 同样是排队连接,等待接受者运行完返回再继续执行,如果调用者和被调用者在同一线程中会导致死锁。

    // check connection type
        if (connectionType == Qt::AutoConnection) {
            QThread *currentThread = QThread::currentThread();
            QThread *objectThread = object->thread();
            connectionType = currentThread == objectThread
                             ? Qt::DirectConnection
                             : Qt::QueuedConnection;
        }
    

    那么这里我们先不讨论事件循环,只看直接调用的部分。

    6、qt_static_metacall

    // 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;
            }
        }
    

    那么我们看到这里构造了函数参数,函数在当前类 index ,类函数在该元对象树中的起始 index,以及Moc文件中生成的函数调用方法。那么这个方法并不是一定存在的,首先我们分析下 moc 工具生成该函数的条件。

    (1)生成条件

    // generator.cpp
    const bool hasStaticMetaCall = !isQt &&
                (cdef->hasQObject || !cdef->methodList.isEmpty()
                 || !cdef->propertyList.isEmpty() || !cdef->constructorList.isEmpty());
    if (hasStaticMetaCall)
        generateStaticMetacall();
    
    // generator.cpp
    bool isQt = (cdef->classname == "Qt");
    
    // moc.cpp
    case Q_OBJECT_TOKEN:
        def.hasQObject = true;
    

    把这两部分判断贴出来,我们就可以比较简单的理解其条件了 — 就是被 Q_OBJECT 修饰或者有咱们前面讨论过的那些个被特殊宏修饰的属性或方法。其实尝试一下我们就会发现,这个函数指针没有被初始化的条件就是使用 Q_GADGET 修饰并且没有使用Qt自定义的反射宏的类。也就是说包含这种宏声明的类会走到下面这个函数分支中。

    (2)QMetaObject::metacall

    // qmetaobject.cpp
    int QMetaObject::metacall(QObject *object, Call cl, int idx, void **argv)
    {
        if (object->d_ptr->metaObject)
            return object->d_ptr->metaObject->metaCall(object, cl, idx, argv);
        else
            return object->qt_metacall(cl, idx, argv);
    }
    

    这里的 metaobject 是我们前面讨论过的 QObjectData 的一个变量。查看它的声明我们可以看出来,这是一个用抽象类声明的变量,如果想要使用它我们必须继承QObjectData并初始化此变量。如果我们没有初始化此变量,那么这个函数的调用会被转发到QObject元对象的 qt_metacall 方法。这个方法也是由moc文件生成的。
    那么再回头看前面调用处。在调用此方法的时候传进来的方法是该函数在元对象树中的总位置,就是为了让其可以调用QObject的方法。

    (3)QMetaObject::InvokeMetaMethod 分支的实现

    那么我们这里调用时的参数为 QMetaObject::InvokeMetaMethod,我们就看看这个分支的实现。

    if (_c == QMetaObject::InvokeMetaMethod) {
            auto *_t = static_cast<CLS_ReflectionTest *>(_o);
            Q_UNUSED(_t)
            switch (_id) {
            case 0: _t->sigTest((*reinterpret_cast< int(*)>(_a[1]))); break;
            case 1: _t->slotTest((*reinterpret_cast< int(*)>(_a[1]))); break;
            case 2: _t->Test(); break;
            default: ;
            }
    

    其实看到这,结合我们前面的分析,就很好理解了。通过 id/index 相当于在前面的类属性的int数组中的methoddata对应的信息,然后直接把函数名字、参数等fprintf在后面就可以了。当然,这里调用的时候传的参数都是void*,因此参数类型和解引用时的正确与否就需要由我们保证了。
    其余的分支其实都类似,我们这里就不展开说了。

    7、QMetaObject::invokeMethod

    除了Invoke,我们还可以通过上面这个函数直接调用。这个函数比刚才我们讨论的函数多了一个member的标识,就是用来判断我们具体该调用哪个方法的。那么问题又来了,重载函数的调用是怎么实现的?默认参数又是怎么传递的?
    源码比较多,我这里就不贴了。用的是函数签名的方式。和编译器对重载函数的实现类似。调用时把返回值类型和参数类型和方法名接续起来构成函数签名。不过看源码就会发现这部分的函数签名就是为了去掉空格等标准化行为用的,因为后续去寻找对应的index时又把函数签名给拆开了。
    这个方法的好处是我们可以调用到这个元对象的所有超类的方法。

    二、反射及调用实践

    1、什么是反射

    百度百科
    在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为反射机制。

    下面咱们把QT文档里提到的一些函数说一下。

    2、Qt反射中常用的函数

    (1)QObject::metaObject()

    const QMetaObject *CLS_ReflectionTest::metaObject() const
    {
        return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
    }
    

    这部分的变量我们很熟悉了。只要我们声明Q_OBJECT宏走的返回的就是 staticMetaObject

    (2)const char * QMetaObject::className()

    返回元对象类名。

    (3)QMetaClassInfo QMetaObject::classInfo()

    返回类信息结构体 QMetaClassInfo

    (4)QMetaMethod QMetaObject::method(int index) const

    QMetaMethod QMetaObject::method(int index) const
    {
        int i = index;
        i -= methodOffset();
        if (i < 0 && d.superdata)
            return d.superdata->method(index);
    
        QMetaMethod result;
        if (i >= 0 && i < priv(d.data)->methodCount) {
            result.mobj = this;
            result.handle = priv(d.data)->methodData + 5*i;
        }
        return result;
    }
    

    看实现我们就知道这部分的index指的不是在当前类中的index,而是在整个元对象树中的index。

    (5)QObject::setProperty() and QObject::property()

    bool QObject::setProperty(const char *name, const QVariant &value)
    {
        Q_D(QObject);
        const QMetaObject* meta = metaObject();
        if (!name || !meta)
            return false;
    
        int id = meta->indexOfProperty(name);
        if (id < 0) {
            if (!d->extraData)
                d->extraData = new QObjectPrivate::ExtraData;
    
            const int idx = d->extraData->propertyNames.indexOf(name);
    
            if (!value.isValid()) {
                if (idx == -1)
                    return false;
                d->extraData->propertyNames.removeAt(idx);
                d->extraData->propertyValues.removeAt(idx);
            } else {
                if (idx == -1) {
                    d->extraData->propertyNames.append(name);
                    d->extraData->propertyValues.append(value);
                } else {
                    if (value.userType() == d->extraData->propertyValues.at(idx).userType()
                            && value == d->extraData->propertyValues.at(idx))
                        return false;
                    d->extraData->propertyValues[idx] = value;
                }
            }
    
            QDynamicPropertyChangeEvent ev(name);
            QCoreApplication::sendEvent(this, &ev);
    
            return false;
        }
        QMetaProperty p = meta->property(id);
    #ifndef QT_NO_DEBUG
        if (!p.isWritable())
            qWarning("%s::setProperty: Property \"%s\" invalid,"
                     " read-only or does not exist", metaObject()->className(), name);
    #endif
        return p.write(this, value);
    }
    

    看源码可以知道除了用宏定义 Q_PROPERTY 可以声明属性(静态属性,编译过程中已经存到院元对象中),还可用 setProperty 设置新的属性。这部分的属性会存在d指针的extradata变量中,是在运行时动态分配的。也因此,我们对比 QMetaMethodQMetaProperty 可以发现,在调用对应的方法时,后者要求传对象指针。这是因为这里的属性已经不单单属于元对象了;而方法是只属于元对象的。

    (6)QMetaObject::newInstance()

    这个方法和 InvokeMethod 类似,都是通过index调用对应的构造函数。

    (7)qobject_cast

    先看看Qt手册里对这个函数的解释:类似dynamic_cast,但是不需要RTTI而且可以穿过动态库工作。但是用于继承于QObject的类并且声明了Q_OBJCT宏。

    a. RTTI

    参考C++对象模型之RTTI的实现原理。那么一方面我们知道dynamic_cast效率不算高;另一方面,由于RTTI依赖于静态编译,也就是无法在可执行文件和库文件之间实现良好的类型检查。

    b. qobject_cast 的实现

    // qobject.h
    template <class T>
    inline T qobject_cast(QObject *object)
    {
        typedef typename std::remove_cv<typename std::remove_pointer<T>::type>::type ObjType; // remove const and volatile
        Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro<ObjType>::Value,
                        "qobject_cast requires the type to have a Q_OBJECT macro"); // whether use macro Q_OBJECT
        return static_cast<T>(ObjType::staticMetaObject.cast(object));
    }
    

    这里的cast函数我们在步骤一讨论过。那么从这部分实现我们就可以看出来实际上是借助元对象系统实现的转换。this指针指向的对象所属的类是转换的目标类。无论传进来的是什么对象,其所对应的元对象的超对象的数据是不会被强制转换走的。那么只要二者之间存在继承关系或者是同一个元对象就说明可以进行转换。那么这样的转换确实是可以跨过动态库边界的,前提是库的二进制兼容性有保障。

    3、代码实践

    // CLS_ReflectionTest.h
    #ifndef CLS_REFLECTIONTEST_H
    #define CLS_REFLECTIONTEST_H
    
    #include <QObject>
    
    class CLS_ReflectionTest : public QObject
    {
        Q_OBJECT
    
        Q_CLASSINFO("Author", "coding-zwh")
        Q_CLASSINFO("use", "test")
    
        Q_PROPERTY(int num READ GetNum WRITE SetNum STORED true)
        Q_PROPERTY(QString name MEMBER m_qstrName CONSTANT)
    
    public:
        enum EnumTest
        {
            e_Test_1st,
            e_Test_2nd
        };
    
        Q_ENUM(EnumTest);
    
        Q_INVOKABLE explicit CLS_ReflectionTest(QObject *p = nullptr);
    
        Q_INVOKABLE CLS_ReflectionTest(const CLS_ReflectionTest&);
    
        Q_INVOKABLE Q_REVISION(4) void Test();
    
        Q_INVOKABLE void TestParam(const int _iPara);
    
        Q_INVOKABLE void TestDefaultParam(const QString _qstrPara = "testzwh");
    
        int GetNum();
    
        void SetNum(int _iNum);
    
    private:
        int m_iNum;
        QString m_qstrName;
    };
    
    #endif // CLS_REFLECTIONTEST_H
    
    // CLS_ReflectionTest.cpp
    #include "CLS_ReflectionTest.h"
    
    #include <QDebug>
    
    CLS_ReflectionTest::CLS_ReflectionTest(QObject *p):
        QObject(p)
    {
        qDebug() << "CLS_ReflectionTest(QObject *p)";
        qDebug() << "";
    }
    
    CLS_ReflectionTest::CLS_ReflectionTest(const CLS_ReflectionTest &p):
        QObject(p.parent())
    {
        qDebug() << "CLS_ReflectionTest(const CLS_ReflectionTest &p)";
        qDebug() << "";
    }
    
    void CLS_ReflectionTest::Test()
    {
        qDebug() << "CLS_ReflectionTest::Test()";
        qDebug() << "";
    }
    
    void CLS_ReflectionTest::TestParam(const int _iPara)
    {
        qDebug() << "CLS_ReflectionTest::TestParam(const int _iPara): " << _iPara;
    }
    
    void CLS_ReflectionTest::TestDefaultParam(const QString _qstrPara)
    {
        qDebug() << "CLS_ReflectionTest::TestDefaultParam(const QString _qstrPara): " << _qstrPara;
    }
    
    int CLS_ReflectionTest::GetNum()
    {
        return m_iNum;
    }
    
    void CLS_ReflectionTest::SetNum(int _iNum)
    {
        m_iNum = _iNum;
    }
    
    // main.cpp
    #include <QCoreApplication>
    #include <QMetaClassInfo>
    #include <QMetaProperty>
    #include <QMetaEnum>
    #include <QMetaMethod>
    #include <QVariant>
    #include <QDebug>
    
    #include "CLS_ReflectionTest.h"
    
    Q_DECLARE_METATYPE(CLS_ReflectionTest)
    typedef CLS_ReflectionTest myType;
    
    #define LITERAL(name) (#name)
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        // register type;
        int typeId = qRegisterMetaType<CLS_ReflectionTest>();
    
        // register the type with same ID to make sure compatibility
        QMetaType::registerTypedef(LITERAL(myType), typeId);
    
        // construct a new object
        CLS_ReflectionTest clsRefTest;
    
        // get metaobject
        const QMetaObject *metaObject = clsRefTest.metaObject();
        if (metaObject)
        {
            // get classname
            qDebug() << "classname: " << metaObject->className();
            qDebug() << "superclass: " << metaObject->superClass()->className();
            qDebug() << "";
    
            // get classInfo
            for (int iClassInfoIndex = metaObject->classInfoOffset() \
                 /* the smallest index of classinfo in derived class ;*/
                 /* choose 0 if we want to print all classInfo including that of super class*/
                 ; iClassInfoIndex < metaObject->classInfoCount(); \
                 iClassInfoIndex++)
            {
                QMetaClassInfo classInfo = metaObject->classInfo(iClassInfoIndex);
                qDebug() << "the classinfo with index " << iClassInfoIndex << ":";
                qDebug() << "name: " << classInfo.name();
                qDebug() << "value: " << classInfo.value();
                qDebug() << "";
            }
    
            // read and write property
            for (int iPropertyIndex = metaObject->propertyOffset(); iPropertyIndex < metaObject->propertyCount(); iPropertyIndex++)
            {
                QMetaProperty property = metaObject->property(iPropertyIndex);
                qDebug() << "the property with index " << iPropertyIndex << ":";
                qDebug() << "name: " << property.name();
    
                bool blIsWritable = property.isWritable();
                qDebug() << "writeable: " << blIsWritable;
                if (blIsWritable)
                {
                    qDebug() << "writeResult: " << property.write(&clsRefTest, QVariant::fromValue(12));
                }
    
                bool blIsReadable = property.isReadable();
                qDebug() << "readable: " << blIsReadable;
                if (blIsReadable)
                {
                    qDebug() << "readValue: " << property.read(&clsRefTest).toString();
                }
                qDebug() << "";
            }
    
            // get enum
            for (int iEnumIndex = metaObject->enumeratorOffset(); iEnumIndex < metaObject->enumeratorCount(); iEnumIndex++)
            {
                QMetaEnum enumerator = metaObject->enumerator(iEnumIndex);
                qDebug() << "the enumerator with index " << iEnumIndex << ":";
                qDebug() << "name: " << enumerator.name();
    
                const char *pcKey = nullptr;
                int iValue = 0;
                int iIndexInEnum = 0;
                while(1)
                {
                    pcKey = enumerator.key(iIndexInEnum);
                    if (pcKey == nullptr)
                    {
                        break;
                    }
                    iIndexInEnum++;
    
                    iValue = enumerator.value(iIndexInEnum);
                    qDebug() << "key: " << pcKey;
                    qDebug() << "value: " << iValue;
                    qDebug() << "";
                }
            }
    
            // invoke function by object
            QGenericArgument argument("myType", &clsRefTest);
            QObject *pObject = metaObject->newInstance(argument);
            qDebug() << "newInstance: " << qobject_cast<CLS_ReflectionTest*>(pObject);
            qDebug() << "";
    
            QObject *pErrorObject = metaObject->newInstance(QGenericArgument("int", &clsRefTest));
            qDebug() << "newInstance: " << qobject_cast<CLS_ReflectionTest*>(pErrorObject);
            qDebug() << "";
    
            QMetaObject::invokeMethod(pObject, "Test");
            QMetaObject::invokeMethod(&clsRefTest, "Test");
            qDebug() << "";
    
            int iParam = 0;
            QMetaObject::invokeMethod(&clsRefTest, "TestParam");
            QMetaObject::invokeMethod(&clsRefTest, "TestParam", QGenericArgument("int", &iParam));
            qDebug() << "";
    
            QString qstrParam = "inputParam";
            QMetaObject::invokeMethod(&clsRefTest, "TestDefaultParam");
            QMetaObject::invokeMethod(&clsRefTest, "TestDefaultParam", QGenericArgument("int", &iParam));
            QMetaObject::invokeMethod(&clsRefTest, "TestDefaultParam", QGenericArgument("QString", &qstrParam));
            qDebug() << "";
    
            // invoke function by QMetaMethod
            for (int iMethodIndex = metaObject->methodOffset(); iMethodIndex < metaObject->methodCount(); iMethodIndex++)
            {
                QMetaMethod method = metaObject->method(iMethodIndex);
                qDebug() << "name: " << method.name();
                qDebug() << "access: " << method.access();
                qDebug() << "revision: " << method.revision();
                qDebug() << "methodSignature: " << method.methodSignature();
                qDebug() << "";
    
                method.invoke(&clsRefTest);
                qDebug() << "";
            }
        }
    
        return a.exec();
    }
    

    三、TODO

    RTTI的原理
    QT_CONFIG(thread)
    事件循环的使用

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 726
精华内容 290
关键字:

invoke原理