Предисловие
Надеюсь, всем, кто использовал в разработке 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 такой структуры не описано, что весьма странно, поэтому заведем свою —
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.
Осталось только написать код, который будет генерировать все это с каких-нибудь наших данных. Если будет интерес, в следующем выпуске покажу, как написать вполне рабочий динамический метаобъект.