[Из песочницы] Динамические метаобъекты (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 d_ptr;

static const QMetaObject staticQtMetaObject; ///пропускаем все остальное } В тоже самое время можно создать проект с простым классом A #include

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, 0×06 /* Public */,

// slots: name, argc, parameters, tag, flags 3, 0, 25, 2, 0×0a /* 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(_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(_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, 0×06 /* Public */,

// slots: name, argc, parameters, tag, flags 3, 0, 25, 2, 0×0a /* 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 такой структуры не описано, что весьма странно, поэтому заведем свою — 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, 0×06 /* Public */,

// slots: name, argc, parameters, tag, flags 3, 0, 30, 2, 0×0a /* Public */, 4, 2, 31, 2, 0×0a /* 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.Осталось только написать код, который будет генерировать все это с каких-нибудь наших данных. Если будет интерес, в следующем выпуске покажу, как написать вполне рабочий динамический метаобъект.

© Habrahabr.ru