Files
pip/doc/pages/code_model.md
2022-08-03 14:14:24 +03:00

11 KiB
Raw Blame History

~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 - добавить макрос, 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::classesInfo->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::enumsInfo->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::enumsInfo->value("MyEnum", 0); if (ei) { ei->members.forEach([](const PICodeInfo::EnumeratorInfo & e){piCout << e.name << "=" << e.value;}); } \endcode