298 lines
11 KiB
Markdown
298 lines
11 KiB
Markdown
\~english \page code_model Code generation
|
||
\~russian \page code_model Генерация кода
|
||
|
||
\~english
|
||
|
||
\~russian
|
||
|
||
# Введение
|
||
|
||
Кодогенерация помогает в случаях, когда нужен доступ к строковому представлению
|
||
сущностей (классов, перечислений, ...), либо автоматизированная де/сериализация
|
||
структур и классов.
|
||
|
||
Например, необходимо для создания интерфейса получить в готовом виде список
|
||
пар "имя" = "значение" от какого-либо перечисления, либо обойти список
|
||
вложенных в класс структур с дополнительными метками. Или просто описать
|
||
структуру любой сложности, пометить поля номерами и получить готовые операторы
|
||
де/сериализации для PIBinaryStream с возможностью будущих изменений и сохранением
|
||
обратной совместимости.
|
||
|
||
# pip_cmg
|
||
|
||
PIP предоставляет утилиту, которая берет на вход файлы исходного кода, пути
|
||
включения, параметры и макросы, и на выходе создает h/cpp пару файлов с
|
||
необходимым функционалом. В зависимости от параметров, в этих файлах будут
|
||
присутствовать секции:
|
||
* метаинформации о сущностях;
|
||
* операторы де/сериализации;
|
||
* возможность получить PIVariant любого члена структуры по имени.
|
||
|
||
Параметры обработки:
|
||
* -s - не следовать "#include" внутри файлов;
|
||
* -I<include_dir> - добавить папку включения (например, -I.. -I../some_dir -I/usr/include);
|
||
* -D<define> - добавить макрос, PICODE всегда объявлен (например, -DMY_DEFINE добавит макрос MY_DEFINE).
|
||
|
||
Параметры создания:
|
||
* -A - создать всё;
|
||
* -M - создать метаинформацию (имена и типы всех членов, иерархия включения);
|
||
* -E - создать перечисления (списки перечислений);
|
||
* -S - создать операторы де/сериализации;
|
||
* -G - создать методы получения значений переменных по именам;
|
||
* -o <output_file> - имя файлов модели без расширения (например, "ccm" - создадутся файлы "ccm.h" и "ccm.cpp")
|
||
|
||
# CMake
|
||
|
||
Для автоматизации кодогенерации существует CMake макрос pip_code_model, который сам вызывает pip_cmg и
|
||
следит за актуальностью модели.
|
||
|
||
Формат вызова макроса:
|
||
\code{.cmake}
|
||
pip_code_model(<out_var> file0 [file1 ...] [OPTIONS opt0 [opt1 ...] ] [NAME name])
|
||
\endcode
|
||
|
||
Параметры:
|
||
* out_var - имя переменной, куда будут записаны абсолютные пути сгенерённых файлов;
|
||
* file... - файлы для генерации, допускаются относительные или абсолютные пути;
|
||
* OPTIONS - передаваемые в pip_cmg параметры, например, "-Es";
|
||
* NAME - базовое имя файлов модели, если не указано, то используется "ccm_${PROJECT_NAME}".
|
||
|
||
Этот макрос сам включает все пути для PIP.
|
||
|
||
Для получения актуальных параметров pip_cmg можно вызывать "pip_cmg -v".
|
||
|
||
# Подробности
|
||
|
||
## Метаинформация
|
||
|
||
Метаинформация - это текстовое представление всех членов и методов структуры или класса C++.
|
||
Для доступа к ним используется PICODEINFO::classes().value("name"), который возвращает
|
||
указатель на структуру PICodeInfo::ClassInfo, содержащую всю информацию о сущности.
|
||
|
||
В любой структуре PICodeInfo есть поле "MetaMap meta", содержащее произвольные
|
||
данные, видимые для кодогенератора, но невидимые для компилятора.
|
||
Для этого используется макрос PIMETA(), который необходимо вписать после объявления
|
||
переменной, метода либо сущности, например:
|
||
\code{.cpp}
|
||
struct MyStruct: Header PIMETA(type=in,port=5005) {
|
||
ushort calcChecksum() const PIMETA(show=true);
|
||
bool checkChecksum() const;
|
||
void setChecksum();
|
||
uchar block_id PIMETA(type=int) = 0;
|
||
};
|
||
enum FOV { // Поле зрения
|
||
fovWide PIMETA(label="Широкое",angle=90),
|
||
fovNormal PIMETA(label="Нормальное",angle=60),
|
||
fovNarrow PIMETA(label="Узкое",angle=30)
|
||
};
|
||
\endcode
|
||
В этом примере в каждом месте, где указана PIMETA, её можно будет получить через "MetaMap meta".
|
||
|
||
## Перечисления
|
||
|
||
Перечисления записываются отдельно, для доступа к ним используется PICODEINFO::enums().value("name"),
|
||
который возвращает указатель на структуру PICodeInfo::EnumInfo, содержащую всю информацию о перечеслении.
|
||
|
||
## Операторы де/сериализации
|
||
|
||
Эти операторы создаются в h файле для всех сутрктур и классов, в которых есть хотя бы один член,
|
||
доступный для работы. Операторы работают с PIBinaryStream в двух вариантах - простом или через PIChunkStream.
|
||
|
||
Для каждой структуры можно указать режим де/сериализации с помощью фиксированного поля в PIMETA:
|
||
* нет указаний - работа через PIChunkStream;
|
||
* simple-stream - работа просто через PIBinaryStream;
|
||
* no-stream - не создавать операторы.
|
||
|
||
Например, для структуры
|
||
\code{.cpp}
|
||
struct DateTime {
|
||
uchar seconds;
|
||
uchar minutes;
|
||
uchar hours;
|
||
uchar days;
|
||
uchar months;
|
||
uchar years;
|
||
};
|
||
\endcode
|
||
создадутся операторы
|
||
\code{.cpp}
|
||
BINARY_STREAM_WRITE(DateTime) {
|
||
PIChunkStream cs;
|
||
cs << cs.chunk(1, v.seconds);
|
||
cs << cs.chunk(2, v.minutes);
|
||
cs << cs.chunk(3, v.hours);
|
||
cs << cs.chunk(4, v.days);
|
||
cs << cs.chunk(5, v.months);
|
||
cs << cs.chunk(6, v.years);
|
||
s << cs.data();
|
||
return s;
|
||
}
|
||
BINARY_STREAM_READ (DateTime) {
|
||
PIByteArray csba; s >> csba;
|
||
PIChunkStream cs(csba);
|
||
while (!cs.atEnd()) {
|
||
switch (cs.read()) {
|
||
case 1: cs.get(v.seconds); break;
|
||
case 2: cs.get(v.minutes); break;
|
||
case 3: cs.get(v.hours); break;
|
||
case 4: cs.get(v.days); break;
|
||
case 5: cs.get(v.months); break;
|
||
case 6: cs.get(v.years); break;
|
||
}
|
||
}
|
||
return s;
|
||
}
|
||
\endcode
|
||
, где порядок id последовательнен.
|
||
|
||
Для структуры
|
||
\code{.cpp}
|
||
struct DateTime PIMETA(simple-stream) {
|
||
uchar seconds;
|
||
uchar minutes;
|
||
uchar hours;
|
||
uchar days;
|
||
uchar months;
|
||
uchar years;
|
||
};
|
||
\endcode
|
||
создадутся операторы
|
||
\code{.cpp}
|
||
BINARY_STREAM_WRITE(DateTime) {
|
||
s << v.seconds;
|
||
s << v.minutes;
|
||
s << v.hours;
|
||
s << v.days;
|
||
s << v.months;
|
||
s << v.years;
|
||
return s;
|
||
}
|
||
BINARY_STREAM_READ (DateTime) {
|
||
s >> v.seconds;
|
||
s >> v.minutes;
|
||
s >> v.hours;
|
||
s >> v.days;
|
||
s >> v.months;
|
||
s >> v.years;
|
||
return s;
|
||
}
|
||
\endcode
|
||
|
||
Для структуры
|
||
\code{.cpp}
|
||
struct DateTime PIMETA(no-stream) {
|
||
uchar seconds;
|
||
uchar minutes;
|
||
uchar hours;
|
||
uchar days;
|
||
uchar months;
|
||
uchar years;
|
||
};
|
||
\endcode
|
||
не создадутся операторы
|
||
|
||
В режиме работы через PIChunkStream также можно указать индивидуальные id,
|
||
что очень полезно для сохранения обратной совместимости структур разных версий:
|
||
Для структуры
|
||
\code{.cpp}
|
||
struct DateTime {
|
||
PIMETA(id=10) uchar seconds;
|
||
PIMETA(id=11) uchar minutes;
|
||
PIMETA(id=12) uchar hours;
|
||
PIMETA(id=20) uchar days;
|
||
PIMETA(id=21) uchar months;
|
||
PIMETA(id=22) uchar years;
|
||
};
|
||
\endcode
|
||
\code{.cpp}
|
||
BINARY_STREAM_WRITE(DateTime) {
|
||
PIChunkStream cs;
|
||
cs << cs.chunk(10, v.seconds);
|
||
cs << cs.chunk(11, v.minutes);
|
||
cs << cs.chunk(12, v.hours);
|
||
cs << cs.chunk(20, v.days);
|
||
cs << cs.chunk(21, v.months);
|
||
cs << cs.chunk(22, v.years);
|
||
s << cs.data();
|
||
return s;
|
||
}
|
||
BINARY_STREAM_READ (DateTime) {
|
||
PIByteArray csba; s >> csba;
|
||
PIChunkStream cs(csba);
|
||
while (!cs.atEnd()) {
|
||
switch (cs.read()) {
|
||
case 10: cs.get(v.seconds); break;
|
||
case 11: cs.get(v.minutes); break;
|
||
case 12: cs.get(v.hours); break;
|
||
case 20: cs.get(v.days); break;
|
||
case 21: cs.get(v.months); break;
|
||
case 22: cs.get(v.years); break;
|
||
}
|
||
}
|
||
return s;
|
||
}
|
||
\endcode
|
||
|
||
Если в этом режиме какую-либо переменную надо проигнорировать, то вместо
|
||
числа id можно указать "-":
|
||
\code{.cpp}
|
||
struct DateTime {
|
||
PIMETA(id=10) uchar seconds;
|
||
PIMETA(id=11) uchar minutes;
|
||
PIMETA(id=-) uchar hours;
|
||
PIMETA(id=20) uchar days;
|
||
PIMETA(id=21) uchar months;
|
||
PIMETA(id=-) uchar years;
|
||
};
|
||
\endcode
|
||
\code{.cpp}
|
||
BINARY_STREAM_WRITE(DateTime) {
|
||
PIChunkStream cs;
|
||
cs << cs.chunk(10, v.seconds);
|
||
cs << cs.chunk(11, v.minutes);
|
||
cs << cs.chunk(20, v.days);
|
||
cs << cs.chunk(21, v.months);
|
||
s << cs.data();
|
||
return s;
|
||
}
|
||
BINARY_STREAM_READ (DateTime) {
|
||
PIByteArray csba; s >> csba;
|
||
PIChunkStream cs(csba);
|
||
while (!cs.atEnd()) {
|
||
switch (cs.read()) {
|
||
case 10: cs.get(v.seconds); break;
|
||
case 11: cs.get(v.minutes); break;
|
||
case 20: cs.get(v.days); break;
|
||
case 21: cs.get(v.months); break;
|
||
}
|
||
}
|
||
return s;
|
||
}
|
||
\endcode
|
||
|
||
# Интеграция в проект
|
||
|
||
При использовании CMake достаточно включить содержимое переменной out_var в приложение
|
||
или библиотеку, и включить через "#include" сгенерированный заголовочный файл в нужном месте.
|
||
После этого перечисления и метаинформация будут загружены в момент запуска, до int main(),
|
||
а операторы станут доступны через заголовочный файл.
|
||
|
||
CMakeLists.txt:
|
||
\code{.cmake}
|
||
project(myapp)
|
||
pip_code_model(CCM "structures.h" OPTIONS "-EMs")
|
||
add_executable(${PROJECT_NAME} ${CPPS} ${CCM})
|
||
\endcode
|
||
|
||
C++:
|
||
\code{.cpp}
|
||
#include "ccm_myapp.h"
|
||
|
||
...
|
||
|
||
PICodeInfo::EnumInfo * ei = PICODEINFO::enums().value("MyEnum", 0);
|
||
if (ei) {
|
||
ei->members.forEach([](const PICodeInfo::EnumeratorInfo & e){piCout << e.name << "=" << e.value;});
|
||
}
|
||
\endcode
|