Qt Meta Type System

Posted unclerunning

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt Meta Type System相关的知识,希望对你有一定的参考价值。

Qt Meta Type System

本文是对 Qt元系统之类型注册 的补充

Meta Type System支持下的异步的信号和槽连接

同步的信号和槽连接用不到类型信息,因为参数可以使用void指针来传递。但是,异步的信号和槽连接由于需要存储参数,所以需要类型信息:

static int *queuedConnectionTypes(const QArgumentType *argumentTypes, int argc)

    QScopedArrayPointer<int> types(new int [argc + 1]);
    for (int i = 0; i < argc; ++i) 
        const QArgumentType &type = argumentTypes[i];
        if (type.type())
            types[i] = type.type();
        else if (type.name().endsWith('*'))
            types[i] = QMetaType::VoidStar;
        else
            // QMetaType::type返回类名对应的type id    
            /*
            如果该类型没有注册,返回值为QMetaType::UnknownType
            */
            types[i] = QMetaType::type(type.name());

        if (!types[i]) 
            qWarning("QObject::connect: Cannot queue arguments of type '%s'\\n"
                     "(Make sure '%s' is registered using qRegisterMetaType().)",
                     type.name().constData(), type.name().constData());
            return 0;
        
    
    types[argc] = 0;

    return types.take();

QMetaType::type静态函数返回类型名称对应的type id,所以:

知道了类型名称字符串就可以得到类型的type id,就能得到类的类型信息。

class Q_CORE_EXPORT QMetaType 
...
//从tape name获取type id
static int type(const char *typeName);
static int type(const QByteArray &typeName);
...
//从tape id获取type name  
static const char *typeName(int type);  
...  
//从type id获取QMetaType
static QMetaType typeInfo(const int type);
explicit QMetaType(const int type);
...  
;  

如果信号和槽使用的参数中存在未注册的类型,我们是无法建立起异步连接的:

QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,
                                     const QObject *receiver, const char *method,
                                     Qt::ConnectionType type)
 
   ...
    int *types = 0;
    /*
    如果连接类型为Qt::QueuedConnection,并且参数中存在未注册的类型
    【type id为QMetaType::UnknownType】,则不会建立连接。
    */
    if ((type == Qt::QueuedConnection)&&
        !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) 
    
        return QMetaObject::Connection(0);
    
   ...    
 

Meta Type System支持下的Property System

bool QObject::setProperty(const char *name, const QVariant &value)

    Q_D(QObject);
    const QMetaObject* meta = metaObject();
    if (!name || !meta)
        return false;

    //根据属性名获取在整个继承链的属性信息<name, type, flags>中的位置:
    //即确定name是类的整个继承链的第一个属性呢还是第二个属性呢...
    int id = meta->indexOfProperty(name);
  ...

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

setProperty的第二个参数是一个QVariant型变量,除了QVariant静态支持的类型外,所有自定义类型都需要使用Q_DECLARE_METATYPE(TYPE)生成一个QMetaTypeId2<MyClass>特化类。

Meta Type System支持下的异步的InvokeMethod

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

//...
    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()
    ;
//... 
    // 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;
        
     

    // ### 异步调用 ### 
    else if (connectionType == Qt::QueuedConnection) 
        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 **) malloc(paramCount * sizeof(void *));
        Q_CHECK_PTR(args);
        //存放参数的type id
        int *types = (int *) malloc(paramCount * sizeof(int));
        Q_CHECK_PTR(types);

        types[0] = 0; // return type
        args[0] = 0;

        for (int i = 1; i < paramCount; ++i) 
            //QMetaType::type 返回type id 
            types[i] = QMetaType::type(typeNames[i]);
            if (types[i] != QMetaType::UnknownType) 
                //拷贝参数,args[i]指向拷贝的参数
                args[i] = QMetaType::create(types[i], param[i]);
                ++nargs;
             else if (param[i]) 
                // Try to register the type and try again before reporting an error.
                void *argv[] =  &types[i], &i ;
                QMetaObject::metacall(object, 
                                      QMetaObject::RegisterMethodArgumentMetaType,
                                      idx_relative + idx_offset, argv);
                if (types[i] == -1) 
                    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]);
                    

                    free(types);
                    free(args);
                    return false;
                
            
        

        // Post a QMetaCallEvent
        QCoreApplication::postEvent(object, 
                                    new QMetaCallEvent(idx_offset, idx_relative, 
                                                       callFunction,
                                                        0, -1, nargs, types, args));
     

  // ### 阻塞式同步调用 ### 
  else  // blocking queued connection
#ifndef QT_NO_THREAD
        if (currentThread == objectThread) 
            qWarning("QMetaMethod::invoke: Dead lock detected in "
                        "BlockingQueuedConnection: Receiver is %s(%p)",
                        mobj->className(), object);
        

        QSemaphore semaphore;
        QCoreApplication::postEvent(object, 
                                    new QMetaCallEvent(idx_offset, idx_relative, 
                                                       callFunction,
                                                        0, -1, 0, 0, param, 
                                                       &semaphore));
        semaphore.acquire();
#endif // QT_NO_THREAD
    
    return true;

这样看来,其实和异步的信号槽一样,异步的InvokeMethod也需要元类型系统的支持。

注册类型的方式

//方式一:
template <typename T>
int qRegisterMetaType(const char *typeName)
// qRegisterMetaType("MyClass");

//方式二:
Q_DECLARE_METATYPE(MyClass)// 延迟注册
QMetaTypeId2<MyClass>::qt_metatype_id();  
// Q_DECLARE_METATYPE(MyClass);
// QMetaTypeId2<MyClass>::qt_metatype_id(); 

其实,第二种向系统注册类型的方式归根到低还是使用了方式一,那他有何存在的必要呢?

想想像QVariant这样的类,如果qt只提供方式一的qRegisterMetaType模板接口,那么QVariant势必需要知道他将要存储的类的类名字符串。那么,它的QVariant::fromValue接口就会是这样子:

MyClass mc;
qRegisterMetaType<MyClass>("MyClass"); //注册
QVariant v = QVariant::fromValue("MyClass",mc); //知道了类名才能获取它的注册信息

或者,将qRegisterMetaType<MyClass>("MyClass"); //注册延迟到QVariant::fromValue中进行:

MyClass mc;
 //知道了类名就能在找不到它的注册信息的情况下对其进行注册,并获取它的注册信息。
QVariant v = QVariant::fromValue("MyClass",mc);

qt确实可以这样做,只提供方式一,不提供也不用实现方式二。

只提供方式一的问题是:对于每一个类型,每一次使用都得让用户传入相应的类名字符串,不仅麻烦,而且容易出错,不好处理。

与其要求用户指定类名字符串,不如把指定类名字符串的任务交给编译器,这应该就是方式二的价值。只需使用Q_DECLARE_METATYPE(MyClass)宏定义一个QMetaTypeId2<MyClass>特化类,QVariant结合模板推导机制就能获取MyClass的类型信息:

调用Q_DECLARE_METATYPE(MyClass)定义的QMetaTypeId<MyClass>::qt_metatype_id()保证能够返回MyClass注册到系统中的type id ,因为如果不存在,它会注册类型信息到系统中,然后返回注册的type id,这种情况在它第一次被调用时发生。

于是乎,世界就是现在这样子:

myclass.h

class MyClass
 
   ...
  
Q_DECLARE_METATYPE(MyClass)     // 延迟注册
MyClass mc;
QVariant v = QVariant::fromValue(mc); 

确实省了用户不少事。

以上是关于Qt Meta Type System的主要内容,如果未能解决你的问题,请参考以下文章

Qt Meta Object System-元对象系统

Qt Meta Object system 学习

Qt MetaObject System详解

Qt属性系统(Qt Property System)

Qt属性系统(Qt Property System)

如何使用 meta-toolchain-qt5 构建 Qt(支持 QtWebEngine)?