Qt Property System

Posted unclerunning

tags:

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

Qt Property System

如同我在Qt 信号和槽所介绍的,在MOC code generator的帮助下,qt会产生精心组织的代码将名称和位置联系在一起,知道了对象、名称,就能找出相应的位置,进而调用相应的函数。

test_1:

#include "propertysystem.h"
#include <QVariant>

void test_1()

    Property pt;  
    MyClass mc;
    mc.m_str = "mc property";
    pt.setProperty("property_mc", QVariant::fromValue(mc));
    MyClass mc1 = pt.property("property_mc").value<MyClass>();

代码中使用的propertysystem.h的代码在后面的示例代码 小节。

当我写下:

pt.setProperty("property_mc", QVariant::fromValue(mc));
MyClass mc1 = pt.property("property_mc").value<MyClass>();

调用时,就开始好奇,他们做了什么呢,他们是怎么关联到相关属性的呢?

我们可以如上面那样操作,是因为我们知道,pt这个对象的类中用Q_PROPERTY关联了属性名”property_mc“与某个类型为MyClass的成员变量(或以MyClass类型变量为参数的成员函数),MOC在幕后已经将名称和位置的关系组织好了。

moc_propertysystem.cpp摘要:

static const qt_meta_stringdata_Property_t qt_meta_stringdata_Property = 
    
        QT_MOC_LITERAL(0, 0, 8),  // "Property"
        QT_MOC_LITERAL(1, 9, 11), // "property_mc"
        QT_MOC_LITERAL(2, 21, 7), // "MyClass"
        QT_MOC_LITERAL(3, 29, 12) // "property_int"
    ,
      "Property\\0property_mc\\0MyClass\\0property_int"
;
#undef QT_MOC_LITERAL


static const uint qt_meta_data_Property[] = 
...
       2,   14, // properties
...

 //14: 
 // 从这里可以看到:
 // 名为"property_mc" 的属性的相对位置为0,因为它在最前面。 
 // 名为"property_int"的属性的相对位置为1,在属性"property_mc"的后面。
 // properties: name,             type,                   flags
                1,             0x80000000 | 2,          0x0009500b,
 //         "property_mc"       "MyClass"
                3,             QMetaType::Int,          0x00095003,
 //         "property_int"  


       0        // eod
;



void Property::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)


#ifndef QT_NO_PROPERTIES
    if (_c == QMetaObject::ReadProperty) 
        Property *_t = static_cast<Property *>(_o);
        Q_UNUSED(_t)
        void *_v = _a[0];
        switch (_id) 
        //对应相对位置0    
        case 0: *reinterpret_cast< MyClass*>(_v) = _t->m_mc; break;
        //对应相对位置1    
        case 1: *reinterpret_cast< int*>(_v) = _t->getInt(); break;
        default: break;
        
     else if (_c == QMetaObject::WriteProperty) 
        Property *_t = static_cast<Property *>(_o);
        Q_UNUSED(_t)
        void *_v = _a[0];
        switch (_id) 
        //对应相对位置0       
        case 0:
            if (_t->m_mc != *reinterpret_cast< MyClass*>(_v)) 
                _t->m_mc = *reinterpret_cast< MyClass*>(_v);
            
            break;
        //对应相对位置1      
        case 1: _t->setInt(*reinterpret_cast< int*>(_v)); break;
        default: break;
        
     else if (_c == QMetaObject::ResetProperty) 
    
#endif // QT_NO_PROPERTIES
    Q_UNUSED(_o);
    Q_UNUSED(_id);
    Q_UNUSED(_c);
    Q_UNUSED(_a);



//第二个参数_id是某个属性在继承树中的绝对位置[相对位置+属性偏移]
int Property::qt_metacall(QMetaObject::Call _c, int _id, void **_a)

  //先在父类中查找匹配
    _id = PropertyBase::qt_metacall(_c, _id, _a);

    //如果返回的_id小于0,表示属性在继承树的某个上层类中定义,或者不存在。
    if (_id < 0)
        return _id;

#ifndef QT_NO_PROPERTIES
   if (_c == QMetaObject::ReadProperty  ||
       _c == QMetaObject::WriteProperty ||
       _c == QMetaObject::ResetProperty ||
       _c == QMetaObject::RegisterPropertyMetaType) 
   
        //返回值_id为属性相对该类的相对位置。     
        qt_static_metacall(this, _c, _id, _a);
        //对_id减2很重要,2是本类定义的属性数量。
        _id -= 2;
    
  ...
#endif // QT_NO_PROPERTIES
    return _id;

其实明白了信号和槽的工作机制后,理解属性系统的工作机制就容易多了,这里以上面的test_1为例,简单地说说setProperty和property的工作机制:

函数根据输入的属性名参数 “property_mc”,从pt开始,在QMetaObject组成的继承树中,自下向上地查找定义了 “property_mc”属性的类的QMetaObject,因为Property类定义了 “property_mc”属性,所以就找到了Property类的QMetaObject:Property::staticMetaObject。然后得到 “property_mc”属性相对于Property::staticMetaObject的相对位置,在qt_meta_data_Property[]中我们看到, “property_mc”相对于Property::staticMetaObject的相对位置为0。有了这两个信息:

  • 继承树中第一个定义了 “property_mc”属性的类的staticMetaObject:Property::staticMetaObject;
  • “property_mc”属性相对于找到的staticMetaObject的相对位置:0。

就可以调用Property::qt_static_metacall ,像这样:

Property::qt_static_metacall(&pt,QMetaObject::WriteProperty,0,value);
Property::qt_static_metacall(&pt,QMetaObject::ReadProperty,0,value);

qt_static_metacall函数再进一步根据传递进来的相对位置0,执行case 0。

得到两个信息之后,还有一种调用路径也能达到同样的目的,这次不调用Property::qt_static_metacall,而是调用Property::qt_metacall:

bool QMetaProperty::write(QObject *object, const QVariant &value) const

    if (!object || !isWritable())
        return false;

    QVariant v = value;
    uint t = QVariant::Invalid;
...
    int status = -1;
    // the flags variable is used by the declarative module to implement
    // interception of property writes.
    int flags = 0;
    void *argv[] =  0, &v, &status, &flags ;
    if (t == QMetaType::QVariant)
        argv[0] = &v;
    else
        argv[0] = v.data();
    if (priv(mobj->d.data)->flags & PropertyAccessInStaticMetaCall && 
        mobj->d.static_metacall)
      //调用qt_static_metacall
        mobj->d.static_metacall(object, QMetaObject::WriteProperty, 
                                idx, argv);
    else
      //转而调用object->qt_metacall 
        QMetaObject::metacall(object, QMetaObject::WriteProperty, 
                              idx + mobj->propertyOffset(), argv);

    return status;

可以看到,传给qt_metacall的位置是加上了偏移的绝对位置。QObject有一个objectName : QString属性,PropertyBase有一个property_name : QString属性,对于本例,代码中object就是pt,mobj就是Property::staticMetaObject,所以mobj->propertyOffset()返回的偏移值就是1(QObject)+1(PropertyBase) = 2,idx就是 “property_mc”属性的相对位置,idx + mobj->propertyOffset() = 2就是 “property_mc”属性在继承树中的绝对位置。

显然,直接通过绝对位置作为参数调用Property::qt_static_metacall处理是不行的,qt_metacall做了一个很用心的处理,以绝对位置作为参数,先调用直接基类的qt_metacall,直接基类的qt_metacall继续这个传递过程,直到传递到了QObject::qt_metacall。对于这个例子,以2为参数传递到QObject::qt_metacall之后,QObject::qt_metacall以2为参数调用QObject::qt_static_metacall,2匹配到case default,什么不做,返回到QObject::qt_metacall,QObject::qt_metacall返回2-1(QObject的属性个数)=1,回到PropertyBase::qt_metacall,PropertyBase::qt_metacall以返回值1为参数调用PropertyBase::qt_static_metacall,1匹配到case default,什么不做,返回到PropertyBase::qt_metacall,PropertyBase::qt_metacall返回1-1(PropertyBase的属性个数)=0,回到Property::qt_metacall,Property::qt_metacall以返回值0为参数调用Property::qt_static_metacall,配置到case 0,并执行case 0。

殊途通过!

仔细分析第二种情况下的执行过程,发现QObject::qt_metacall和PropertyBase::qt_metacall就像打了个酱油一样,除了从绝对偏移中减去自己的属性个数之外,他们没做什么。从QObject::qt_metacall一步步返回到Property::qt_metacall的过程中,会不断的将继承树上层的属性数从绝对位置id中减掉,到最后,我们得到的就是一个相对于Property::staticMetaObject的相对位置:0。

示例代码

propertybase.h

#ifndef PROPERTYBASE_H
#define PROPERTYBASE_H

#include <QObject>

class PropertyBase : public QObject

    // 先启用Meta object system
    Q_OBJECT
    // 启用Property system
    Q_PROPERTY(QString property_name READ getName WRITE setName)

public:
    PropertyBase(QObject *parent = 0) :QObject(parent)
private:
    QString getName() const  return m_name; 
    void setName(QString const & _name) m_name = _name; 
private:
    QString m_name;
;

#endif

myclass.h

#ifndef MYCLASS_H
#define MYCLASS_H

#include <QString>
#include <QObject>

class MyClass

public:
    MyClass() = default;
    MyClass(const MyClass &_t)
        m_str = _t.m_str;
    

    bool operator !=(MyClass const & _mc )
        return m_str != _mc.m_str;
    

    QString m_str;
;
//延迟注册
Q_DECLARE_METATYPE(MyClass)

propertysystem.h

#ifndef PROPERTY_H
#define PROPERTY_H

#include <QObject>
#include <propertybase.h>
#include "myclass.h"

class Property : public PropertyBase

    // 先启用Meta object system
    Q_OBJECT
    // 启用Property system
    //Q_PROPERTY(MyClass property_mc READ getMc WRITE setMc)
    Q_PROPERTY(MyClass property_mc MEMBER m_mc)
    Q_PROPERTY(int property_int READ getInt WRITE setInt)

public:
    Property(QObject *parent = 0) :PropertyBase(parent)

private:
    MyClass getMc() const  return m_mc; 
    void setMc(MyClass &_ob) m_mc = _ob;

    int getInt() const  return m_i; 
    void setInt(int _i) m_i = _i; 
private:
    MyClass m_mc;
    int m_i;
;
#endif

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

Qt Property System

CCF|打酱油|Java

configSource 在 system.serviceModel *或*它的小节中不起作用

Qt之Q_PROPERTY宏理解

Qt 中的 Q_PROPERTY?

Qt:如何在 C++ 端而不是 QML 上监视 Q_PROPERTY 更改