📜 ⬆️ ⬇️

Dynamic Meta Objects (part 1, study)

Foreword


I hope everyone who used to develop Qt was interested in finding out how the meta-information is arranged and what happens inside this beautiful framework? This record will be about this - we will look inside the source code and try to write the implementation of a dynamic metaobject (but not in this record). Meta-object in which you can create signals and slots in realtime.

Many will say that everything is already implemented (if not available: you can find it in Google's cache ). But with this implementation, we will not be able to make QObject::connect . The value of such an implementation will tend to zero.

A bit of study


So, first we will look at the contents of the QObject class. What for? All classes with meta-information must be QObject descendants and have the Q_OBJECT macro in order for moc to generate meta-information.

I will copy the code from Qt from the official site . I will use Qt 5.4.
')
So the class declaration itself looks like this:

QObject class code
 class Q_CORE_EXPORT QObjectData { public: virtual ~QObjectData() = 0; QObject *q_ptr; QObject *parent; QObjectList children; uint isWidget : 1; uint blockSig : 1; uint wasDeleted : 1; uint isDeletingChildren : 1; uint sendChildEvents : 1; uint receiveChildEvents : 1; uint isWindow : 1; //for QWindow uint unused : 25; int postedEvents; QDynamicMetaObjectData *metaObject; QMetaObject *dynamicMetaObject() const; }; class Q_CORE_EXPORT QObject { Q_OBJECT Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged) Q_DECLARE_PRIVATE(QObject) ///  protected: QScopedPointer<QObjectData> d_ptr; static const QMetaObject staticQtMetaObject; ///   } 


At the same time, you can create a project with a simple class A

 #include <QObject> class A : public QObject { Q_OBJECT public: explicit A(QObject *parent = 0); ~A(); signals: void signal(); public slots: void slot(){} }; 

But in the midst of all this, one must pay attention to the meta-object itself, and what it consists of.
MOC text
 ///  QT_BEGIN_MOC_NAMESPACE struct qt_meta_stringdata_A_t { QByteArrayData data[4]; char stringdata[15]; }; #define QT_MOC_LITERAL(idx, ofs, len) \ Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ qptrdiff(offsetof(qt_meta_stringdata_A_t, stringdata) + ofs \ - idx * sizeof(QByteArrayData)) \ ) static const qt_meta_stringdata_A_t qt_meta_stringdata_A = { { QT_MOC_LITERAL(0, 0, 1), // "A" QT_MOC_LITERAL(1, 2, 6), // "signal" QT_MOC_LITERAL(2, 9, 0), // "" QT_MOC_LITERAL(3, 10, 4) // "slot" }, "A\0signal\0\0slot" }; #undef QT_MOC_LITERAL static const uint qt_meta_data_A[] = { // content: 7, // revision 0, // classname 0, 0, // classinfo 2, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 1, // signalCount // signals: name, argc, parameters, tag, flags 1, 0, 24, 2, 0x06 /* Public */, // slots: name, argc, parameters, tag, flags 3, 0, 25, 2, 0x0a /* Public */, // signals: parameters QMetaType::Void, // slots: parameters QMetaType::Void, 0 // eod }; void A::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { A *_t = static_cast<A *>(_o); switch (_id) { case 0: _t->signal(); break; case 1: _t->slot(); break; default: ; } } else if (_c == QMetaObject::IndexOfMethod) { ///        } Q_UNUSED(_a); } ///   !    ! const QMetaObject A::staticMetaObject = { { &QObject::staticMetaObject, qt_meta_stringdata_A.data, qt_meta_data_A, qt_static_metacall, Q_NULLPTR, Q_NULLPTR} }; const QMetaObject *A::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject; } int A::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) { if (_id < 2) qt_static_metacall(this, _c, _id, _a); _id -= 2; } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) { if (_id < 2) *reinterpret_cast<int*>(_a[0]) = -1; _id -= 2; } return _id; } // SIGNAL 0 void A::signal() { QMetaObject::activate(this, &staticMetaObject, 0, Q_NULLPTR); } QT_END_MOC_NAMESPACE 


So, from what you see you can draw several conclusions: the developments for dynamic metaobjects are the variable QDynamicMetaObjectData * QObjectData::metaObject and the function QMetaObject * QObjectData::dynamicMetaObject() const . Therefore, it remains to learn how to work with them and how Qt works with them.

Skipping the boring reading of source codes, I’ll say right away: we even left classes for creating dynamic metaobjects.

text q_object_p.h
 ///   struct QAbstractDynamicMetaObject; struct Q_CORE_EXPORT QDynamicMetaObjectData { virtual ~QDynamicMetaObjectData() {} virtual void objectDestroyed(QObject *) { delete this; } virtual QAbstractDynamicMetaObject *toDynamicMetaObject(QObject *) = 0; ///    metaObject virtual int metaCall(QObject *, QMetaObject::Call, int _id, void **) = 0;///    //. }; ///     ,   . struct Q_CORE_EXPORT QAbstractDynamicMetaObject : public QDynamicMetaObjectData, public QMetaObject { virtual QAbstractDynamicMetaObject *toDynamicMetaObject(QObject *) { return this; } virtual int createProperty(const char *, const char *) { return -1; }///    .    virtual int metaCall(QObject *, QMetaObject::Call c, int _id, void **a) { return metaCall(c, _id, a); } virtual int metaCall(QMetaObject::Call, int _id, void **) { return _id; } // Compat overload }; ///     


So, what we have out. If we create a new metaobject and save it in QObject::d_ptr->metaObject in any QObject heir, then no calls to signals and slots will pass by us (by the way, an excellent tool for debugging signals and slots can be done), and you can take a place for your signals and slots. In general, to do everything that will support our sick imagination, but I was more inspired by the creation of a meta-object, which could add signals and slots, so I will highlight here exactly the preparation for the creation of such a meta-object.

We struggle with laziness and collect information about the task


So, to make your metaobject, you need to look at how the metaobject is generally arranged. To do this, go back to the sources and find it:

Metaobject structure
 struct Q_CORE_EXPORT QMetaObject { ///  struct { // private data const QMetaObject *superdata;///     const QByteArrayData *stringdata;///   (  ,  ,   ) const uint *data;///    (  ,  ,   ) typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **); StaticMetacallFunction static_metacall;///  ,     . const QMetaObject * const *relatedMetaObjects;///,    void *extradata; //reserved for future use } d; } 


From here, and from the listing with the MOC generator, it can be seen that for a valid metaobject you need to fill only 2 variables: stringdata and data , or completely rewrite all the functions of the QMetaObject class. Out of 2 evils, I chose the least - I decided to fill in this data, because the search for this data will be done using Qt tools and it will be no slower to search for ordinary metaobjects (yes, this is premature optimization).

First, let's consider the easiest - string information. MOC gives us this code for our test class A:

String array
 struct qt_meta_stringdata_A_t { QByteArrayData data[4]; char stringdata[15]; }; #define QT_MOC_LITERAL(idx, ofs, len) \ Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \ qptrdiff(offsetof(qt_meta_stringdata_A_t, stringdata) + ofs \ - idx * sizeof(QByteArrayData)) \ ) static const qt_meta_stringdata_A_t qt_meta_stringdata_A = { { QT_MOC_LITERAL(0, 0, 1), // "A" QT_MOC_LITERAL(1, 2, 6), // "signal" QT_MOC_LITERAL(2, 9, 0), // "" QT_MOC_LITERAL(3, 10, 4) // "slot" }, "A\0signal\0\0slot" }; #undef QT_MOC_LITERAL 


Those. there is just an array of QByteArrayData , which contains relative references to strings (relative to QByteArrayData itself). Thus, we can safely place each line in memory separately, and not together, as the MOC did.

Now let's turn to the main meta-information, where MOC prepared a large uint array for us.

Large uint array
 static const uint qt_meta_data_A[] = { ///1  // content: 7, // revision 0, // classname 0, 0, // classinfo 2, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 1, // signalCount ///2  // signals: name, argc, parameters, tag, flags 1, 0, 24, 2, 0x06 /* Public */, // slots: name, argc, parameters, tag, flags 3, 0, 25, 2, 0x0a /* Public */, ///3  // signals: parameters QMetaType::Void, // slots: parameters QMetaType::Void, 0 // eod }; 


Divide it into 3 blocks. The 1st block we have represents the usual QMetaObjectPrivate class:

QMetaObjectPrivate basic
 struct QMetaObjectPrivate { enum { OutputRevision = 7 }; // Used by moc, qmetaobjectbuilder and qdbus int revision; int className; int classInfoCount, classInfoData; int methodCount, methodData; int propertyCount, propertyData; int enumeratorCount, enumeratorData; int constructorCount, constructorData; //since revision 2 int flags; //since revision 3 int signalCount; //since revision 4 // revision 5 introduces changes in normalized signatures, no new members // revision 6 added qt_static_metacall as a member of each Q_OBJECT and inside QMetaObject itself // revision 7 is Qt 5 ///    ,      } 


Compliance with what is equal from the first block is not difficult to hold. The 2nd unit is a bit more complicated. There it turns out an array of structures (in Qt such a structure is not described, which is very strange, so we will get our own - DataMethodInfo ):

DataMethodInfo
 struct DataMethodInfo{ uint name;///    (    ) uint argsCount; ///   uint argOffset; /// offset    uint tag;/// ,   uint flags;///   private/protected/public    - ,     }; 


This is all clear. But the description of the arguments is much more fun. First comes the type that the method must return, and most often it is QMetaType::Void . Next comes the listing of all types of arguments. Namely, if we have the QString testString (QString src, QString dst) method QString testString (QString src, QString dst) , then there will be 2 QMetaType :: QString. If the method has no arguments, then we do not fill in anything. And after listing the types of arguments, there is a list of the names of these arguments. Thus, for our QString testString( QString src, QString dst ) method QString testString( QString src, QString dst ) the metadata code would be:

 static const qt_meta_stringdata_A_t qt_meta_stringdata_A = { { QT_MOC_LITERAL(0, 0, 1), // "A" QT_MOC_LITERAL(1, 2, 6), // "signal" QT_MOC_LITERAL(2, 9, 0), // "" QT_MOC_LITERAL(3, 10, 4) // "slot" QT_MOC_LITERAL(4, 15, 10) // "testString" QT_MOC_LITERAL(5, 26, 3) // "src" QT_MOC_LITERAL(6, 30, 3) // "dst" }, "A\0signal\0\0slot\0testString\0src\dst" }; static const uint qt_meta_data_A[] = { ///1  // content: 7, // revision 0, // classname 0, 0, // classinfo 3, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 1, // signalCount ///2  // signals: name, argc, parameters, tag, flags 1, 0, 29, 2, 0x06 /* Public */, // slots: name, argc, parameters, tag, flags 3, 0, 30, 2, 0x0a /* Public */, 4, 2, 31, 2, 0x0a /* Public */, ///3  // signals: parameters QMetaType::Void, // slots: parameters QMetaType::Void, ////----------------------------------------------------------------- ///| return | Arguments Type | names | QMetaType::QString , QMetaType::QString, QMetaType::QString, 5 , 6 0 // eod }; 

I could be mistaken in calculating the offset for the arguments, but the meaning, I think, is clear? By inserting this code instead of what MOC did, you can add the testString method to our class A. Metaobject, however, it will not work, but it will appear in the list. And will have your unique id.

It remains only to write the code that will generate all this from some of our data. If there is interest, in the next issue I will show you how to write a fully working dynamic metaobject.

Source: https://habr.com/ru/post/258441/


All Articles