ИССЛЕДОВАНИЕ МЕТАОБЪЕКТНОГО КОМПИЛЯТОРА QT - Студенческий научный форум

XIII Международная студенческая научная конференция Студенческий научный форум - 2021

ИССЛЕДОВАНИЕ МЕТАОБЪЕКТНОГО КОМПИЛЯТОРА QT

Петров А.С. 1, Миронов М.А. 1, Аникин К.В. 1
1СПБГУАП
 Комментарии
Текст работы размещён без изображений и формул.
Полная версия работы доступна во вкладке "Файлы работы" в формате PDF

Аннотация. О существовании библиотеки Qt и фреймворка QtCreator, предоставляемого вместе с ней, на настоящий момент знает подавляющее число программистов, особенно тех, которые работают на языке программирования С++. Бесспорно, такая популярность вызвана грамотным подходом к проектированию библиотеки, широкие возможности, предоставляемые QtCreator и ставка разработчиков на удобство работы пользователем с их продуктом. Все это реализовано с помощью нестандартных для C++ инструментов. Примерами этих инструментов являются: QtQuick, QtDesigner, QML и т.д. Одним из инструментов, лежащих в основе Qt, является мета-объектный компилятор (mocmeta-objectcompiler). В данной статье рассматриваются положительные и отрицательные стороны Qt, способы работы с moc и его реализация. Цель исследования – раскрытие реализации mocQt и инструментов, предоставляемых им, а так же обзор достоинств и недостатков, связанных с Qt. Научная новизна исследования заключается в предоставлении реальных примеров кода, генерируемого moc, с пояснениями к каждому блоку. В результате были рассмотрены удобства Qt, его плюсы и минусы, рассмотрено его использование, реализация moc и возможности, предоставляемые moc.

Ключевые слова: программирование, С++, Qt, компилятор, moc, препроцессор, метаобъектный компилятор.

Abstract. The vast majority of programmers, especially those who work in the C++programming language, are currently aware of the existence of the Qt library and the QtCreator framework provided with It. Undoubtedly, this popularity is caused by a competent approach to library design, the extensive features provided by QtCreator, and the developers ' bet on user-friendliness with their product. All this is implemented using non-standard C++ tools. Examples of these tools are: QtQuick, QtDesigner, QML, etc. One of the tools underlying Qt is the meta-object compiler (moc – meta-object compiler). This article discusses the positive and negative aspects of Qt, how to work with moc, and its implementation. The purpose of the study is to reveal the implementation of moc Qt and the tools provided by it, as well as an overview of the advantages and disadvantages associated with Qt. The scientific novelty of the research is to provide real-world examples of code generated by the moc, with explanations for each block. As a result, we reviewed the convenience of Qt, its pros and cons, its use, the implementation of moc, and the features provided by moc.

Введение

Язык С++ является одним из передовых языков программирования в настоящее время. Поэтому разрабатывается множество библиотек для удобства работы с ним. Одной из таких библиотек является Qt. Причем, помимо самой библиотеки Qt предоставляет фреймворк QtCreator, упрощающий работу программистов. Одним из основных инструментов Qt является метаобъектная система. Про работу с этой библиотекой были написаны несколько книг, например, [1], [2], но ресурсов, посвященных moc на данный момент очень малое количество. Данная статья призвана исправить это.

Приведем удобства, предоставляемые мета-объектной системой Qt, а так же плюсы и минусы использования Qt [3] как такового.

Удобства, предоставляемые мета-объектной системой Qt: сигнал-слотовые соединения, предоставляющие удобный интерфейс взаимодействия объектов программы; система свойств, которая является схожей сущностью со свойствами компиляторов, но выигрывает тем, что является не зависимой от типа компилятора, платформы и т.д.; поддержка связи между С++ и QML кодами, что позволяет легко связывать backend и frontend стороны приложения; invokemethods или возможность вызова методов из любого потока; облегченная работа с переферией и базами данных.

Помимо представленных выше преимуществ за которые отвечает мета-объектная система, существуют следующие аспекты фреймворка Qt, которые также, несомненно, показывают его в лучшем свете: удобство создания GUI; заранее определенные контейнеры, алгоритмы, интерфейсы и т.д., дополняющие стандартную библиотеку языка С++; кроссплатформенность; нативная IDE; документация; низкий порог вхождения.

Недостатки фреймворка Qt: большой объем памяти, занимаемый готовыми приложениями; некоторые алгоритмы и контейнеры, призванные заменить стандартные, проигрывают последним в производительности(qSort, Q_FOREACH, QList); затянутая компиляция проекта; низкий порог вхождения.

Использование moc

Moc – это дополнительный препроцессор для кода, написанного на языке С++. Перед тем, как вызывается стандартный препроцессор компилятора все заголовочные файлы анализирует moc. Если в файле попадаются объявления классов, содержащих макрос Q_OBJECT(пример – листинг 1), то создается дополнительный файл исходных кодов, компилируемый вместе с проектом, в котором находится реализация мета-объектных возможностей каждой сущности. Файлы, созданные moc должны компилироваться и компоноваться вместе с остальными исходными кодами программы. Утилита qmake позволяет делать это автоматически. При ее запуске генерируются те части общего make-файла программы, которые содержат правила для компиляции и компоновки файлов, содержащие сущности, которые взаимодействуют с метаобъектной системой [4].

Листинг 1. Подключение пользовательского класса к мета-объектной системе.

class MyClass : public QObject ///< Наследование от QObject

{

Q_OBJECT ///< Макрос, раскрывающийся в дополнительном .cpp файле

public:

MyClass(QObject *parent = nullptr); ///< Параметр – указатель на объект-родитель. Необходимо для очистки памяти по идеологии Qt

~MyClass();

signals:

void mySignal();

public slots:

voidmySlot();

};

Помимо сигнал-слотовых возможностей, метаобъектная система предоставляет собственную реализацию работы со свойствами.

Листинг 2. Работа со свойствами mocQt:

class MyClass : public QObject

{

Q_OBJECT

Q_PROPERTY(MyFeature feature READ feature WRITE setFeature) ///< Имя свойства и методы доступа к нему

Q_ENUMS(MyFeature) ///< Определение типов, используемых в свойствах

public:

enum MyFeature {LUCKY, KIND, CLEVER};

MyClass(QObject *parent = nullptr);

~MyClass();

void setFeature(MyFeature feature) { m_ feature = feature; }

MyFeature feature () const { return m_ feature; }

private:

MyFeature m_feature;

};

Макрос Q_PROPERTY позволяет задать свойство, а так же методы доступа к нему(доступные методы: READ – обязательно требуется определить, WRITE, RESET, NOTIFY, DESIGNABLE, SCRIPTABLE, STORED, USER, CONSTANT, FINAL). Макрос Q_ENUM служит для регистрации пользовательских типов, используемых в подсистеме свойств. Похожей цели служит макрос Q_FLAGS, но он определяет перечисления, которые будут использоваться в качестве флагов, т.е. соединены через логическое ИЛИ.

Еще одним полезным инструментом выступает Q_CLASSINFO, приведенный в листинге 3. Этот макрос предоставляет пользователю возможность задавать метаинформацию о классе в виде пар: ключ – значение. Q_CLASSINFO будет особенно полезен при отладке и дампе метаинформации о классе как на этапе компиляции, так и во время работы программы.

Листинг 3. Q_CLASSINFO:

class MyClass : public QObject

{

Q_OBJECT

Q_CLASSINFO("Author", "Alexandr Petrov")

public:

MyClass(QObject *parent = nullptr);

~MyClass();

};

Также, moc добавляет некоторые опции для командной строки, указанные в таблице 1.

Таблица 1. Опции командной строки, предоставляемые moc

Опция

Описание

-o<file>

Заменить стандартный вывод на вывод в указанный файл

-f[<file>]

Задать генерирование директивы #include в вывод. Используется по умолчанию для заголовочных файлов. Часть [<file>] необязательна

-i

Не генерировать директивы #include в выводе

-nw

Отменить генерирование предупреждений

-p<path>

Для каждого имени директивы #include добавить префикс <path>/

-D<macro>[=<def>]

Задать макрос с необаятельным определением

-U<macro>

Отменить определение макроса

@<file>

Считать опции командной строки из файла

-h

Вывести список опций

-v

Вывести текущую версию moc

Для того, чтобы в коде обозначить место, которое должно пропускаться moc-ом необходимо обернуть его так, как показано в листинге 4.

Листинг 4. Пропуск части кода метаобъектным компилятором.

#ifndef Q_MOC_RUN /* some code */ #endif

Ограничения, связанные с работой moc [5]: шаблонные классы не могут быть обработаны moc; классы, работающие с moc, обязаны быть наследниками QObject, более того, если имеет место множественное наследование, то QObject должен выступать первым в списке родителей; параметрами сигналов и слотов не могут быть указатели на функции; для параметров сигналов и слотов любые типы данных должны быть полностью уточнены. То есть необходимо максимально подробно указать желаемый тип, используя оператор разрешения видимости (::); вложенные классы не могут содержать сигналы и слоты; типами возвращаемых значений сигналов и слотов не могут быть ссылки.

Реализация moc

Основной макрос Q_OBJECT имеет следующее определение:

Листинг 5. Определение макроса Q_OBJECT:

#define Q_OBJECT \

public: \

QT_WARNING_PUSH \

Q_OBJECT_NO_OVERRIDE_WARNING \

static const QMetaObject staticMetaObject; \

virtual const QMetaObject *metaObject() const; \

virtual void *qt_metacast(const char *); \

virtual int qt_metacall(QMetaObject::Call, int, void **); \

QT_TR_FUNCTIONS \

private: \

Q_OBJECT_NO_ATTRIBUTES_WARNING \

Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \

QT_WARNING_POP \

struct QPrivateSignal {}; \

QT_ANNOTATE_CLASS(qt_qobject, "")

Как видно, этот макрос добавляет к классу объявление некоторых методов, необходимых для работы метаобъектной системы. Макросы, используемые вначале определении Q_OBJECT, являются проверкой на правильность использования системы. Затем объявляется статический член класса staticMetaObject, хранящий всю метаинформацию о классе и геттер для него. qt_metacast выступает в качестве инструмента для преобразования типов. Макрос QT_TR_FUNCTIONS – это набор методов, позволяющих работать с локализацией. qt_metacall и qt_static_metacall – это основа метаобъектной системы, отправная точка для вызова методов. QPrivateSignal позволяет определить сигналы, которые будут недоступны из вне класса.

Основные части файла, сгенерированного moc:

В любом moc_*.cpp файле началом выступает код из листинга 5, который проверяет используемую версию Qt.

Листинг 6. Начало moc_ файлов.

#include ...///< Подключение заголовочных файлов

#if !defined(Q_MOC_OUTPUT_REVISION)

#error "The header file 'mainwindow.h' doesn't include <QObject>."

#elif Q_MOC_OUTPUT_REVISION != 67

#error …///< Вывод ошибки компиляции

#endif

Затем, объявляется структура, которая хранит все строковые данные о классе. stringdata0 хранит все названия, разделенные ‘\0’, а data хранит указатели на данные, внутри stringdata0.

Листинг 7. Объявление структуры, хранящей данные класса.

QT_BEGIN_MOC_NAMESPACE

QT_WARNING_PUSH

QT_WARNING_DISABLE_DEPRECATED

struct qt_meta_stringdata_MainWindow_t {

QByteArrayData data[1];

char stringdata0[11];

};

Следом, объявлен макрос, позволяющие получить сдвиг в stringdata0.

Листинг 8. Макрос получения сдвига.

#define QT_MOC_LITERAL(idx, ofs, len) \

Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \

qptrdiff(offsetof(qt_meta_stringdata_MainWindow_t, stringdata0) + ofs \

- idx * sizeof(QByteArrayData)) \

)

Листинг 9. Заполнение структуры stringdata.

static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {

{

/// Подобных макросов будет по количеству строк в stringdata0

QT_MOC_LITERAL(0, 0, 10) // "MainWindow"

},

"MainWindow"

};

#undef QT_MOC_LITERAL

Следующая структура описывает всю метаинформацию о классе:

Листинг 10. Способ хранения метаинформации.

static const uint qt_meta_data_MainWindow[] = {

// content:

8, // Ревизия

0, // идентификатор имени класса

0, 0, // Описание дополнительных параметров

0, 0, // Количество методов и сдвиг

0, 0, // Количество свойств и сдвиг

0, 0, // Количество перечислений и сдвиг

0, 0, // Количество конструкторов, объявленных, как invokable

0, // Флаги

0, // Количество сигналов

0 // eod

};

После идет описание сигналов, слотов и методов по шаблонному способу.

Таблица 2. Способ описания сигналов и слотов

 

Идентификатор в stringdata0

Количество параметров

Сдвиг для параметров

Тэги

Дополнительная служебная информация в виде флага

Пример

1

0

44

2

0x06

Следом за описанием сигналов и слотов идет описание из параметров. Первый параметр – возвращаемый тип, затем параметры метода с идентификаторами их имен.

Листинг 11. Описание параметров.

// signals: parameters

QMetaType::Void,

QMetaType::Void, QMetaType::QString, 2

QMetaType::Float, // slots: parameters

QMetaType::Int, QMetaType::Bool, 10 // methods: parameters

Описание свойств и перечислений может выглядеть так:

Листинг 12. Описание свойств и перечислений.

// properties: name, type, flags

12, QMetaType::Int, 0x00495103,

// properties: notify_signal_id

2,

//enums: name, lags, count, data

13, 0x0, 2, 68,

//enum data: key, value

14, uint(MyClass::FirstValue),

14, uint(MyClass::SecondValue),

0 // eod

После всех описаний и объявлений происходит определение метода qt_static_metacall. Он позволяет вызывать для объектов своего класса методы, связанные с метаобъектной системой (сигналы, слоты, invokable методы и т.д.).

Листинг 13. Определение метода qt_static_metacall.

void MainWindow::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)

{

if (_c == QMetaObject::InvokeMetaMethod) {

auto *_t = static_cast<MainWindow *>(_o);

Q_UNUSED(_t)

switch (_id) {

case 0: _t->sendData((*reinterpret_cast< structChangeMWindPData(*)>(_a[1]))); break;

case 1: _t->startTCP(); break;

case 2: _t->slotApplyClicked(); break;

default: ;

}

} else if (_c == QMetaObject::IndexOfMethod) {

int *result = reinterpret_cast<int *>(_a[0]);

{

using _t = void (MainWindow::*)(structChangeMWindPData );

if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::sendData)) {

*result = 0;

return;

}

}

{

using _t = void (MainWindow::*)();

if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MainWindow::startTCP)) {

*result = 1;

return;

}

}

}

}

Листинг 12. Определение статического хранения метаобъекта.

QT_INIT_METAOBJECT const QMetaObject MainWindow::staticMetaObject = { {

QMetaObject::SuperData::link<QMainWindow::staticMetaObject>(),

qt_meta_stringdata_MainWindow.data,

qt_meta_data_MainWindow,

qt_static_metacall,

nullptr,

nullptr

} };

const QMetaObject *MainWindow::metaObject() const

{

return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;

}

Метод metaObject() возвращает статический или динамический метаобъект класса. Возможность получить динамический объект связана с QML, так как в нем память для них аллоцируется динамически. Если не был использован QML вернется статический объект.

qt_metacall является еще одним уровнем абстракции для qt_static_metacall

Листинг 14. Определение метода qt_metacall.

int MainWindow::qt_metacall(QMetaObject::Call _c, int _id, void **_a)

{

_id = QMainWindow::qt_metacall(_c, _id, _a);

if (_id < 0)

return _id;

if (_c == QMetaObject::InvokeMetaMethod) {

if (_id < 9)

qt_static_metacall(this, _c, _id, _a);

_id -= 9;

} else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {

if (_id < 9)

*reinterpret_cast<int*>(_a[0]) = -1;

_id -= 9;

}

return _id;

}

В конце файла, сгенерированного moc, приводятся определения сигналов, используемых в классе.

Листинг 15. Определение сигналов.

// SIGNAL 0

void MainWindow::sendData(structChangeMWindPData _t1)

{

void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };

QMetaObject::activate(this, &staticMetaObject, 0, _a);

}

Возможности, предоставляемые QMetaObject

С помощью метаобъекта класса во время выполнения приложения можно получить: имя класса; имена методов; свойства и определенные для них методы; перечисления, а также строковые представления их значений; возможность создать новый объект; возможность вызова методов; возможность преобразования типов с помощью qobject_cast.

Заключение

В данной статье было проведено исследование moc системы Qt, показаны примеры ее использования, а также реализация в самой библиотеке. Помимо этого, рассмотрены возможности, предоставляемые moc, и достоинства и недостатки, связанные с Qt.

Список литературы

Mark Summerfield. AdvancedQtprogramming. Addition-Wesley. 2010.

Макс Шлее. Qt 5.10. Профессиональное программирование на С++. БХВ-Петербург, 2018.

http://scrutator.me/post/2011/12/08/Qt-pitfalls.aspx

https://doc.qt.io/qt-5/moc.html

https://habr.com/ru/company/infopulse/blog/327176/

Просмотров работы: 189