You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Сергей Маринкевич e7aa646a80 move marshaller to IPC component 2 weeks ago
include move marshaller to IPC component 2 weeks ago
src move marshaller to IPC component 2 weeks ago
tools move marshaller to IPC component 2 weeks ago
.gitignore base wo autocode 2 weeks ago
CMakeLists.txt refactor 2 weeks ago
README.md move marshaller to IPC component 2 weeks ago

README.md

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<TextIpcSerializer>
│   │   ├── 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<TextIpcSerializer>)
    • Построение: msg.add<std::string>("MyService.add"); msg.add<int>(7); msg.add<int>(8);
    • Разбор: auto name = msg.get<std::string>(); auto a = msg.get<int>(); auto b = msg.get<int>();
    • Сериализация вынесена в отдельные сериализаторы (TextIpcSerializer и т.д.)
    • Тип сырых данных параметризован через сериализатор (по умолчанию std::string, можно использовать std::vector<std::byte> для бинарных форматов)
    • Выбор сериализатора делается один раз в 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<T>().
  • Сгенерированные обёртки:
    • *.proxy.* — шаблонные классы, зависящие только от абстрактного impl с методом impl.callTyped<Ret>(method, args...) и не знающие про конкретный транспорт.
    • *.skeleton.* — используют RpcInvoker и принимают/возвращают RpcValue для диспетчеризации вызовов.

Зависимости

  • CMake ≥ 3.10

  • GCC или Clang с поддержкой C++17

  • Python 3.8+ с пакетами:

    pip3 install jinja2 clang
    
  • libclang (для Python) Если GCC используется только для сборки — libclang нужен только для генератора.

Важно: Сам libclang и пакет clang для Python должны быть одной версии.


Аннотации для экспорта

Управление тем, какие атрибуты каких классов следует экспортировать происходит с помощью аннотаций в исходном коде.

См. файл MyService.h:

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:
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build

Директива CMAKE_EXPORT_COMPILE_COMMANDS=ON нужна для корректного парсинга в libclang.

  1. Собираем проект:
cmake --build build

В результате получаем два бинарника:

./build/server
./build/client

Генерация кода

Автоматическая генерация прокси и скелета происходит при сборке через Python-скрипт tools/generate_rpc.py.

Пример ручного запуска генератора:

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. В одном терминале запускаем сервер:
./build/server

Сервер создаёт (при необходимости) именованные пайпы /tmp/fifo_to_server и /tmp/fifo_to_client, читает запросы из fifo_to_server и пишет ответы в fifo_to_client через IpcPipeChannel.

  1. В другом терминале — клиент:
./build/client

Клиент также открывает эти FIFO, но логически пишет в fifo_to_server и читает из fifo_to_client (через тот же IpcPipeChannel), а пользовательский код видит только вызов MyServiceProxy.

Ожидаемый вывод:

RESULT: 15