Files
pip/doc/pages/code_model.md
2026-03-12 14:46:57 +03:00

13 KiB
Raw Blame History

~english \page code_model Code generation ~russian \page code_model Генерация кода

~english

Introduction

Code generation helps when you need string representation of entities (classes, enums, etc.) or automated serialization/deserialization of structures and classes. For example, you may need a list of "name" = "value" pairs from an enumeration for a UI, or to traverse nested structures with metadata. You can describe a structure of any complexity, assign field IDs, and get ready-made operators for \a PIBinaryStream with versioning and backward compatibility.

pip_cmg

PIP provides the \c pip_cmg utility: it takes source files, include paths, and options, and produces a .h/.cpp pair. Depending on options, the output may include: entity metadata; serialization operators; and the ability to get a \a PIVariant for any member by name.

Processing options: \c -s (do not follow #include); \c -I<include_dir> (add include path); \c -D (add macro; \c PICODE is always defined).

Creation options: \c -A (create all); \c -M (metadata); \c -E (enums); \c -S (serialization operators); \c -G (get value by name); \c -o <output_file> (output base name without extension).

CMake

The \c pip_code_model CMake macro invokes \c pip_cmg and keeps the model up to date. Call format: \c pip_code_model(<out_var> file0 [file1 ...] [OPTIONS ...] [NAME name]). Parameters: \c out_var receives generated file paths; \c file... are sources; \c OPTIONS are passed to \c pip_cmg (e.g. \c "-Es"); \c NAME sets the model file base (default \c "ccm_${PROJECT_NAME}"). The macro adds PIP include paths. Run \c pip_cmg -v for current options.

Details

Metadata: attach \c PIMETA(...) to types, members or enums; read at runtime via \a PICODEINFO::classes() and \a PICODEINFO::enums(). Serialization: struct-level \c PIMETA(simple-stream) or \c PIMETA(no-stream), or per-member chunk ids (default); see \ref chunk_stream. Add the generated .h/.cpp to your target and \c #include the generated header; metadata loads before \c main().

~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::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