Компилятор Qt удалённых объектов
Обзор REPC
Компилятор Реплики (repc) генерирует заголовочные файлы QObject на основе файла определения API. Файл (называемый "rep-файлом") использует специфичный текстовый синтаксис для описания API. По соглашению, эти файлы имеют расширение .rep (сокращение от Replica). При обработке этих файлов repc генерирует как заголовочные файлы источника, так и реплики.
Модуль Qt удалённых объектов также включает функции CMake и переменные qmake, которые можно добавить в файл проекта для автоматического запуска repc и добавления полученных файлов в список файлов, обрабатываемых компилятором метаобъектов во время процесса сборки, упрощая использование Qt удалённых объектов в ваших проектах.
Хотя Qt удалённые объекты поддерживают обмен любым QObject через сеть (используя enableRemoting со стороны источника и acquireDynamic со стороны реплики), есть несколько преимуществ в определении ваших объектов с помощью repc. Во-первых, хотя DynamicReplicas полезны, с ними работать сложнее. API неизвестен до инициализации объекта, а использование API из C++ требует поиска строк через методы QMetaObject. Во-вторых, знание интерфейса на этапе компиляции позволяет обнаружить проблемы на этапе компиляции, а не во время выполнения. В-третьих, формат rep поддерживает значения по умолчанию, что может быть полезно, если вы не можете гарантировать доступность источника при инициализации реплики.
См. документацию здесь для получения информации об использовании сгенерированных файлов в вашем коде. Здесь мы сосредоточимся на формате и параметрах repc.
Формат rep-файла
Формат rep-файла — это простой специфичный язык для домена (DSL) для описания интерфейса, поддерживаемого Qt удалёнными объектами (QtRO). Поскольку QtRO — это объектная система, эти интерфейсы определяются API, доступными через объекты, то есть классы с свойствами, сигналами и слотами.
Тип класса
Каждый класс, определённый в rep-файле, становится QObject в сгенерированных заголовочных файлах с сгенерированным для вас описанным API.
Для определения класса используйте ключевое слово class, за которым следует имя вашего типа, а затем заключите ваш API в скобки, как показано ниже
class MyType
{
//PROP/CLASS/MODEL/SIGNAL/SLOT/ENUM declarations to define your API
}; Свойство
Элементы Q_PROPERTY создаются с помощью ключевого слова PROP в rep-файле. Синтаксис — это ключевое слово PROP за которым следует определение в скобках, где определение — это тип, имя и (необязательно) значение по умолчанию или атрибуты.
PROP(bool simpleBool) // boolean named simpleBool
PROP(bool defaultFalseBool=false) // boolean named defaultFalseBool, with false
// as the default value
PROP(int lifeUniverseEverything=42) // int value that defaults to 42
PROP(QByteArray myBinaryInfo) // Qt types are fine, may need #include
// additional headers in your rep file
PROP(QString name CONSTANT) // Property with the CONSTANT attribute
PROP(QString setable READWRITE) // Property with the READWRITE attribute
// note: Properties default to READPUSH
// (see description below)
PROP(SomeOtherType myCustomType) // Custom types work. Needs #include for the
// appropriate header for your type, make
// sure your type is known to the metabject
// system, and make sure it supports Queued
// Connections (see Q_DECLARE_METATYPE and
// qRegisterMetaType) Дополнительную информацию о создании пользовательских типов можно найти здесь.
По умолчанию свойства будут иметь геттеры и «push»-слот, а также сигнал notify, который будет испускаться при изменении значения. Qt удалённые объекты требуют сигнала notify в объекте источника для запуска отправки обновлений присоединённым репликам. В более ранних версиях QtRO свойства по умолчанию были чтением/записью, то есть имели геттеры и сеттеры. Однако из-за асинхронного характера QtRO это иногда приводило к неинтуитивному поведению. Установка атрибута READWRITE в PROP обеспечит старое поведение (геттер и сеттер).
// In .rep file, old (setter) behavior
PROP(int myVal READWRITE) // Old behavior with setMyVal(int myVal) method
// In code... Assume myVal is initially set to 0 in Source
int originalValue = rep->myVal(); // Will be 0
rep->setMyVal(10); // Call setter, expecting a blocking/
// non-asynchronous return
if (rep->myVal() == 10) ... // Test will usually fail Если необходимо заблокировать выполнение до изменения значения, необходимо выполнить следующее.
// In .rep file, old (setter) behavior
PROP(int myVal READWRITE) // Old behavior with setMyVal(int myVal) method
// In code... Assume myVal is initially set to 0 in Source
bool originalValue = rep->myVal(); // Will be 0
// We can wait for the change using \l QSignalSpy
QSignalSpy spy(rep, SIGNAL(myValChanged(int)));
rep->setMyVal(10); // Call setter, expecting a blocking/
// non-asynchronous return
spy.wait(); // spy.wait() blocks until changed signal
// is received
if (rep->myVal() == 10) ... // Test will succeed assuming
// 1. Source object is connected
// 2. Nobody else (Source or other Replica)
// sets the myVal to something else (race
// condition)
// Rather than use QSignalSpy, the event-driven practice would be to connect the
// myValChanged notify signal to a slot that responds to the changes. QtRO теперь по умолчанию использует READPUSH, что обеспечивает автоматически сгенерированный слот для запроса изменения свойства.
// In .rep file, defaults to READPUSH
PROP(bool myVal) // No setMyVal(int myVal) on Replica, has
// pushMyVal(int myVal) instead
// In code... Assume myVal is initially set to 0 in Source
bool originalValue = rep->myVal(); // Will be 0
// We can wait for the change using \l QSignalSpy
QSignalSpy spy(rep, SIGNAL(myValChanged(int)));
rep->pushMyVal(10); // Call push method, no expectation that change
// is applied upon method completion.
// Some way of waiting for change to be received by the Replica is still necessary,
// but hopefully not a surprise with the new pushMyVal() Slot.
spy.wait(); // spy.wait() blocks until changed signal
// is received
if (rep->myVal() == 10) ... // Test will succeed assuming
// 1. Source object is connected
// 2. Nobody else (Source or other Replica)
// set the myVal to something else (race
// condition) Вы также можете использовать ключевые слова CONSTANT, READONLY, PERSISTED, READWRITE, READPUSH, или SOURCEONLYSETTER в объявлении PROP, что повлияет на реализацию свойства. READPUSH — это значение по умолчанию, если не указано другое.
PROP(int lifeUniverseEverything=42 CONSTANT) PROP(QString name READONLY)
Обратите внимание на некоторые нюансы. У свойства CONSTANT PROP объявлен Q_PROPERTY как CONSTANT со стороны источника. Однако реплики не могут узнать правильное значение до инициализации, что означает, что значению свойства разрешается изменяться во время инициализации. Для READONLY у источника не будет ни сеттера, ни push-слота, и со стороны реплики не будет сгенерирован push-слот. Добавление атрибута PERSISTED к свойству PROP заставит это свойство использовать экземпляр QRemoteObjectAbstractPersistedStore, установленный на узле (если таковой имеется), для сохранения/восстановления значений PROP.
Другое важное значение — SOURCEONLYSETTER, которое предоставляет другой способ указания асимметричного поведения, где источник (конкретно вспомогательный класс SimpleSource) будет иметь публичный геттер и сеттер для свойства, но он будет только для чтения (с сигналом notify) со стороны реплики. Таким образом, свойство может полностью контролироваться со стороны источника, но может наблюдаться только со стороны реплики. SOURCEONLYSETTER — это режим, используемый repc для экземпляров MODEL и CLASS, что означает, что источник может изменить указываемый объект, но реплика не может предоставить новый объект, так как не генерируется метод set<Prop> или push<Prop>. Обратите внимание, это не влияет на поведение свойств указываемого типа, а только на возможность изменения самого указателя.
КЛАСС
Ключевое слово CLASS генерирует специальные элементы Q_PROPERTY для объектов, производных от QObject. Эти свойства имеют те же семантические характеристики, что и SOURCEONLYSETTER. Синтаксис — это ключевое слово CLASS за которым следует имя свойства, а затем тип подобъекта в скобках.
// In .rep file
class OtherClass
{
PROP(int value)
}
class MainClass
{
CLASS subObject(OtherClass)
} МОДЕЛЬ
Ключевое слово MODEL генерирует специальные элементы Q_PROPERTY для объектов, производных от QAbstractItemModel. Эти свойства имеют те же семантические характеристики, что и SOURCEONLYSETTER. Синтаксис — это ключевое слово MODEL за которым следует имя свойства и скобки, содержащие (разделённые запятыми) роли, которые должны быть доступны реплике.
// In .rep file
class CdClass
{
PROP(QString title READONLY)
MODEL tracks(title, artist, length)
} СИГНАЛ
Методы сигнала создаются с помощью ключевого слова SIGNAL в rep-файле.
Использование — объявление SIGNAL за которым следует желаемая сигнатура в скобках. Возвращаемое значение void следует пропустить.
SIGNAL(test()) SIGNAL(test(QString foo, int bar)) SIGNAL(test(QMap<QString,int> foo)) SIGNAL(test(const QString &foo)) SIGNAL(test(QString &foo))
Как и в Qt очередных соединениях, параметры в сигналах, которые являются ссылками, будут скопированы при передаче репликам.
СЛОТ
Методы слота создаются с помощью ключевого слова SLOT в rep-файле.
Использование — объявление SLOT за которым следует желаемая сигнатура в скобках. Возвращаемое значение можно включить в объявление. Если возвращаемое значение опущено, в сгенерированных файлах будет использоваться void.
SLOT(test()) SLOT(void test(QString foo, int bar)) SLOT(test(QMap<QString,int> foo)) SLOT(test(QMap<QString,int> foo, QMap<QString,int> bar)) SLOT(test(QMap<QList<QString>,int> foo)) SLOT(test(const QString &foo)) SLOT(test(QString &foo)) SLOT(test(const QMap<QList<QString>,int> &foo)) SLOT(test(const QString &foo, int bar))
Как и в Qt очередных соединениях и QtRO SIGNALS, параметры в слотах, которые являются ссылками, будут скопированы при передаче репликам.
ПЕРЕЧИСЛЕНИЕ
Перечисления (которые используют комбинацию C++ enum и Qt's Q_ENUM в QtRO) описываются с помощью ключевого слова ENUM.
ENUM MyEnum {Foo}
ENUM MyEnum {Foo, Bar}
ENUM MyEnum {Foo, Bar = -1}
ENUM MyEnum {Foo=-1, Bar}
ENUM MyEnum {Foo=0xf, Bar}
ENUM MyEnum {Foo=1, Bar=3, Bas=5} Связанные темы: Тип перечисления, ключевое слово USE_ENUM
Тип POD
Plain Old Data (POD) — это термин для описания простого набора данных, аналогично C++ структуре. Например, если у вас есть API для телефонной книги, вы можете использовать понятие «адрес» в своём интерфейсе (где адрес может включать улицу, город, штат, страну и почтовый индекс). Вы можете использовать ключевое слово POD для определения таких объектов, которые затем могут использоваться в определениях PROP/SIGNAL/SLOT в ваших определениях классов.
Использование — объявление POD за которым следует имя сгенерированного типа, за которым следуют пары тип/имя, разделённые запятыми, где пары тип/имя заключены в скобки.
POD Foo(int bar) POD Foo(int bar, double bas) POD Foo(QMap<QString,int> bar) POD Foo(QList<QString> bar) POD Foo(QMap<QString,int> bar, QMap<double,int> bas)
Полный пример:
POD Foo(QList<QString> bar)
class MyType
{
SIGNAL(sendCustom(Foo foo));
}; Сгенерированный repc код создаёт класс Q_GADGET для каждого POD с соответствующими членами Q_PROPERTY для каждого определённого типа POD.
Тип перечисления
Часто легче и понятнее определять перечисления внутри класса (см. ENUM), но если вам нужен автономный тип перечисления, использование ключевого слова ENUM вне определения класса может быть полезным. Это сгенерирует новый класс в ваших заголовочных файлах, который обрабатывает маршаллинг и т. д. Синтаксис идентичен ENUM, за исключением того, что в данном случае объявление не заключено в объявление class.
Связанные темы: ENUM, ключевое слово USE_ENUM
Ключевое слово USE_ENUM
Ключевое слово USE_ENUM было реализовано до добавления автоматической генерации через ключевое слово ENUM. Оно сохраняется для обратной совместимости.
Связанные темы: ENUM, Тип перечисления
Директивы
rep-файл определяет интерфейс, но интерфейсы часто требуют внешних элементов. Для поддержки этого repc включит любые директивы (одной строки) в верхней части сгенерированных файлов. Это позволяет, например, использовать директивы #include или #define, которые поддерживают необходимую логику или типы данных.
Инструмент repc в настоящее время игнорирует всё от символа "#" до конца строки и добавляет это в сгенерированные файлы. Таким образом, многострочные #if/#else/#endif и многострочные макросы не поддерживаются.
Функции CMake
Ниже приведены функции CMake для генерации типов источника и реплики.
Создаёт файлы заголовков C++ для типов источника и реплики из файлов .rep Qt Remote Objects. |
|
Создаёт файлы заголовков C++ для типов реплики из файлов .rep Qt Remote Objects. |
|
Создаёт файлы заголовков C++ для типов источника из файлов .rep Qt Remote Objects. |
|
Создаёт файлы .rep из файлов заголовков QObject. |
Переменные qmake
REPC_REPLICA
Указывает имена всех файлов .rep в проекте, которые должны использоваться для генерации файлов заголовков реплики.
Например:
REPC_REPLICA = media.rep \
location.rep Сгенерированный файл(ы) будет иметь вид rep_<replica file base>_replica.h.
REPC_SOURCE
Указывает имена всех файлов .rep в проекте, которые должны использоваться для генерации файлов заголовков источника.
Например:
REPC_SOURCE = media.rep \
location.rep Сгенерированный файл(ы) будет иметь вид rep_<replica file base>_source.h.
REPC_MERGED
Указывает имена всех файлов .rep в проекте, которые должны использоваться для генерации объединённых (источник и реплика) файлов заголовков.
Например:
REPC_MERGED = media.rep \
location.rep Сгенерированный файл(ы) будет иметь вид rep_<replica file base>_merged.h.
Примечание: Обычно источники и реплики находятся в отдельных процессах или устройствах, поэтому эта переменная используется нечасто.
QOBJECT_REP
Указывает имена существующих файлов заголовков QObject, которые должны использоваться для генерации соответствующих файлов .rep.
См. также QRemoteObjectAbstractPersistedStore.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.2/qtremoteobjects-repc.html