Предисловие
Надеюсь, всем, кто использовал в разработке Qt, было интересно узнать, как же устроена метаинформация и что же происходит внутри этого прекрасного фреймворка? Об этом и будет эта запись — мы заглянем внутрь исходников и попробуем написать реализацию динамического метаобъекта (но не в этой записи). Метаобъекта, в котором можно создавать сигнали и слоты в realtime.
Многие скажут, что все уже реализовано (если недоступно: можно найти в кеше гугла). Но с такой реализацией мы не сможем сделать QObject::connect
. Ценность такой реализации будет стремиться к нулю.
Немного изучения
Итак, для начала мы рассмотрим содержимое класса QObject
. Зачем? Все классы с метаинформацией должны быть наследниками QObject
и ещё иметь макрос Q_OBJECT
, чтобы moc сгенерировал метаинформацию.
Код из Qt буду копировать с официального сайта. Использовать буду Qt 5.4.
Итак, само объявление класса выглядит так:
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(){} };
То среди всего этого надо обратить внимание на сам метаобъект, и чего он состоит.
///Пропускаем лишнее 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.
Пропуская занудное чтение исходников скажу сразу: нам даже оставили классы для создания динамических метаобъектов.
///пропускаем все лишнее 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 массив.
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
:
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):
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/
Добавить комментарий