Динамические метаобъекты (part 1, изучение)

от автора


Предисловие

Надеюсь, всем, кто использовал в разработке Qt, было интересно узнать, как же устроена метаинформация и что же происходит внутри этого прекрасного фреймворка? Об этом и будет эта запись — мы заглянем внутрь исходников и попробуем написать реализацию динамического метаобъекта (но не в этой записи). Метаобъекта, в котором можно создавать сигнали и слоты в realtime.

Многие скажут, что все уже реализовано (если недоступно: можно найти в кеше гугла). Но с такой реализацией мы не сможем сделать QObject::connect . Ценность такой реализации будет стремиться к нулю.

Немного изучения

Итак, для начала мы рассмотрим содержимое класса QObject. Зачем? Все классы с метаинформацией должны быть наследниками QObject и ещё иметь макрос Q_OBJECT, чтобы moc сгенерировал метаинформацию.

Код из Qt буду копировать с официального сайта. Использовать буду Qt 5.4.

Итак, само объявление класса выглядит так:

Код класса QObject

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; ///пропускаем все остальное } 

В тоже самое время можно создать проект с простым классом A

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

То среди всего этого надо обратить внимание на сам метаобъект, и чего он состоит.

текст MOC

///Пропускаем лишнее  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  

Итак, из увиденного можно сделать несколько выводов: наработки для динамических метаобъектов есть переменная QDynamicMetaObjectData * QObjectData::metaObject и функция QMetaObject * QObjectData::dynamicMetaObject() const. Следовательно, осталось узнать, как с ними работать и как с ними работают Qt.

Пропуская занудное чтение исходников скажу сразу: нам даже оставили классы для создания динамических метаобъектов.

текст 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 }; ///остальное нам тоже не интересно 

Итак, что у нас выходит. Если мы создадим новый метаобъект и сохраним его в QObject::d_ptr->metaObject в каком либо наследнике QObject, то мимо нас не пройдет ни одно обращение к сигналам и слотам (кстати, отличный инструмент для отлаживания сигналов и слотов можно сделать), а так же можно занять место под свои сигналы и слоты. В общем, сделать все, что поддержит наше больное воображение, но меня больше вдохновляло создание метаобъекта, которому можно было бы добавлять и сигналы, и слоты, поэтому я освещу здесь именно подготовку к созданию такого метаобъекта.

Боремся с ленью и собираем информацию о задаче

Итак, чтобы сделать свой метаобъект, надо посмотреть, как метаобъект вообще устроен. Для этого снова лезем в исходники и находим это:

Структура метаобъекта

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

Отсюда, и из листинга с MOC генератора, видно, что для валидного метаобъекта требуется заполнить только 2 переменные: stringdata и data, либо полностью переписывать все функции класса QMetaObject . Из 2-х зол я выбрал меньшее — решил заполнить эти данные, потому что поиск по этим данным будет проводиться средствами Qt и искать он будет никак не медленнее обычных метаобъектов (да, это преждевременная оптимизация).

Для начала давайте рассмотрим самое легкое — строковую информацию. MOC нам дает вот такой код для нашего тестового класса A:

Строковый массив

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 

Т.е. там всего-то навсего массив QByteArrayData, который содержит относительные ссылки на строки (относительно самого QByteArrayData). Таким образом, мы спокойно можем разместить каждую строку в памяти отдельно, а не вместе, как сделал это MOC.

Теперь давайте обратимся к основной метаинформации, где MOC нам приготовил большой uint массив.

Большой uint массив

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

Разделим его на 3 блока. 1-й блок у нас представляет обычный класс QMetaObjectPrivate:

QMetaObjectPrivate основное

 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 ///класс там продолжается и дальше, но дальше нам не интересно } 

Соответствия, что чему равно из первого блока провести не составляет труда. 2-й блок чуточку посложнее. Там получается массив из структур (в Qt такой структуры не описано, что весьма странно, поэтому заведем свою — <code lang=«cpp:>DataMethodInfo):

DataMethodInfo

struct DataMethodInfo{     uint name;/// номер имени метода ( в строковом массиве )     uint argsCount; /// количество аргументов     uint argOffset; /// offset информации о методах     uint tag;/// увы, не понял     uint flags;/// влияет на private/protected/public доступ и на что-то ещё, до конца не разобрался }; 

С этим все понятно. А вот описание аргументов намного веселее. Вначале идет тип, который метод должен вернуть, и чаще всего это бывает QMetaType::Void. Далее идет перечисление всех типов аргументов. А именно, если у нас метод QString testString (QString src, QString dst), то будет лежать 2 QMetaType::QString. Если же метод не имеет аргументов, то ничего и не заполняем. А после перечисления типов аргументов идет список имен этих аргументов. Таким образов для нашего метода QString testString( QString src, QString dst ) код метадаты будет такой:

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

Я мог ошибиться в подсчете offset для аргументов, но смысл, думаю, понятен? Вставив этот код вместо того, что сделал MOC, можно добавить метод testString в наш метаобъект класса A. Вызвать его, правда, не получиться, но в списке значиться он будет. И будет иметь свой уникальный id.

Осталось только написать код, который будет генерировать все это с каких-нибудь наших данных. Если будет интерес, в следующем выпуске покажу, как написать вполне рабочий динамический метаобъект.

ссылка на оригинал статьи http://habrahabr.ru/post/258441/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *