# Minimal C++ RPC PoC with Auto-Code Generation Простой Proof-of-Concept реализации RPC для C++ с автоматической генерацией прокси и скелета по аннотациям в исходниках. Проект демонстрирует: * Парсинг исходников C++ с помощью `libclang` для поиска аннотированных классов и методов. * Автоматическую генерацию файлов: * `*.proxy.h/cpp` — клиентский прокси для вызова удалённых методов. * `*.skeleton.h/cpp` — серверный скелет для приёма запросов и вызова реальных методов. * Минимальный протокол передачи данных через **именованные каналы (FIFO)**. * Поддержка только типов `int` для аргументов и возвращаемого значения (PoC). --- ## Структура проекта ``` project/ ├── CMakeLists.txt ├── README.md ├── include/ │ ├── ipc/ │ │ ├── BaseIpcMessage.h # шаблонный класс сообщения │ │ ├── IpcChannel.h │ │ ├── IpcCodec.h │ │ ├── IpcConfig.h # type alias: using IpcMessage = BaseIpcMessage │ │ ├── IpcDispatcher.h │ │ ├── IpcPipeChannel.h │ │ ├── IpcMarshaller.h │ │ └── IpcSerializer.h # сериализаторы (TextIpcSerializer) │ └── rpc/ │ ├── rpc_export.h │ ├── RpcInvoker.h │ └── RpcValue.h ├── src/ │ ├── client.cpp │ ├── MyService.cpp │ ├── MyService.h │ └── server.cpp ├── tools/ │ ├── generate_rpc.py │ └── templates/ │ ├── proxy.cpp.j2 │ ├── proxy.h.j2 │ ├── skeleton.cpp.j2 │ └── skeleton.h.j2 └── build/ # создаётся при сборке ``` --- ## Архитектура (кратко) * **Уровень IPC-сообщений**: `IpcMessage` (type alias для `BaseIpcMessage`) * Построение: `msg.add("MyService.add"); msg.add(7); msg.add(8);` * Разбор: `auto name = msg.get(); auto a = msg.get(); auto b = msg.get();` * Сериализация вынесена в отдельные сериализаторы (`TextIpcSerializer` и т.д.) * Тип сырых данных параметризован через сериализатор (по умолчанию `std::string`, можно использовать `std::vector` для бинарных форматов) * Выбор сериализатора делается один раз в `IpcConfig.h` через type alias * **Уровень канала**: `IpcChannel` + `IpcPipeChannel` * `IpcChannel` — абстракция транспорта: `send(const IpcMessage&)`, `receive() -> IpcMessage`. * `IpcPipeChannel` — реализация поверх двух FIFO (`/tmp/fifo_to_server`, `/tmp/fifo_to_client`), которая внутри работает со строками, но наружу — только с `IpcMessage`. * **Уровень RPC-ядра**: * `IpcMarshaller` — собирает `IpcMessage` из имени метода и аргументов, отправляет через `IpcChannel` и читает ответ. * `RpcInvoker` — по имени метода (первое поле сообщения) находит зарегистрированную функцию-член и вызывает её, читая аргументы через `get()`. * **Сгенерированные обёртки**: * `*.proxy.*` — шаблонные классы, зависящие только от абстрактного `impl` с методом `impl.callTyped(method, args...)` и не знающие про конкретный транспорт. * `*.skeleton.*` — используют `RpcInvoker` и принимают/возвращают `RpcValue` для диспетчеризации вызовов. --- ## Зависимости * CMake ≥ 3.10 * GCC или Clang с поддержкой C++17 * Python 3.8+ с пакетами: ```bash pip3 install jinja2 clang ``` * libclang (для Python) Если GCC используется только для сборки — libclang нужен только для генератора. **Важно**: Сам `libclang` и пакет `clang` для Python должны быть одной версии. --- ## Аннотации для экспорта Управление тем, какие атрибуты каких классов следует экспортировать происходит с помощью аннотаций в исходном коде. См. файл [MyService.h](src/MyService.h): ```c class RPC_EXPORT MyService { public: RPC_EXPORT int add(int a, int b); int minus(int a, int b); }; ``` * Экспортируем класс `MyService`: * Экспортируем метод `MyService::add`; * Но **не** экспортируем метод `MyService::minus`. --- ## Сборка проекта 1. Конфигурируем CMake: ```bash cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build ``` Директива `CMAKE_EXPORT_COMPILE_COMMANDS=ON` нужна для корректного парсинга в `libclang`. 2. Собираем проект: ```bash cmake --build build ``` В результате получаем два бинарника: ``` ./build/server ./build/client ``` --- ## Генерация кода Автоматическая генерация прокси и скелета происходит **при сборке** через Python-скрипт `tools/generate_rpc.py`. Пример ручного запуска генератора: ```bash python3 tools/generate_rpc.py \ --out-dir build/generated \ --compile-commands build/compile_commands.json \ src/MyService.h ``` Сгенерированные файлы попадут в: ``` build/generated/ ├─ MyService.proxy.h ├─ MyService.proxy.cpp ├─ MyService.skeleton.h └─ MyService.skeleton.cpp ``` --- ## Запуск 1. В одном терминале запускаем сервер: ```bash ./build/server ``` Сервер создаёт (при необходимости) именованные пайпы `/tmp/fifo_to_server` и `/tmp/fifo_to_client`, читает запросы из `fifo_to_server` и пишет ответы в `fifo_to_client` через `IpcPipeChannel`. 2. В другом терминале — клиент: ```bash ./build/client ``` Клиент также открывает эти FIFO, но логически пишет в `fifo_to_server` и читает из `fifo_to_client` (через тот же `IpcPipeChannel`), а пользовательский код видит только вызов `MyServiceProxy`. **Ожидаемый вывод:** ```text RESULT: 15 ```