Compare commits
4 Commits
e7aa646a80
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| f261621c68 | |||
| 168b5fdfea | |||
| 9f5969342e | |||
| 47092fc6f1 |
+40
-2
@@ -1,8 +1,13 @@
|
|||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(SimpleRPCExample CXX)
|
project(SimpleRPCExample CXX C)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# Generate compile_commands.json for code generation tools
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
# include source includes
|
# include source includes
|
||||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
@@ -23,6 +28,8 @@ function(autocode_annotated_file OUT_BASENAME HEADER SOURCE)
|
|||||||
set(out_proxy_cpp "${GENERATED_DIR}/${OUT_BASENAME}.proxy.cpp")
|
set(out_proxy_cpp "${GENERATED_DIR}/${OUT_BASENAME}.proxy.cpp")
|
||||||
set(out_skeleton_h "${GENERATED_DIR}/${OUT_BASENAME}.skeleton.h")
|
set(out_skeleton_h "${GENERATED_DIR}/${OUT_BASENAME}.skeleton.h")
|
||||||
set(out_skeleton_cpp "${GENERATED_DIR}/${OUT_BASENAME}.skeleton.cpp")
|
set(out_skeleton_cpp "${GENERATED_DIR}/${OUT_BASENAME}.skeleton.cpp")
|
||||||
|
set(out_client_c_h "${GENERATED_DIR}/${OUT_BASENAME}Client_c.h")
|
||||||
|
set(out_client_c_cpp "${GENERATED_DIR}/${OUT_BASENAME}Client_c.cpp")
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT
|
OUTPUT
|
||||||
@@ -30,6 +37,8 @@ function(autocode_annotated_file OUT_BASENAME HEADER SOURCE)
|
|||||||
${out_proxy_cpp}
|
${out_proxy_cpp}
|
||||||
${out_skeleton_h}
|
${out_skeleton_h}
|
||||||
${out_skeleton_cpp}
|
${out_skeleton_cpp}
|
||||||
|
${out_client_c_h}
|
||||||
|
${out_client_c_cpp}
|
||||||
COMMAND ${CMAKE_COMMAND} -E echo "Generating RPC stubs for ${OUT_BASENAME}..."
|
COMMAND ${CMAKE_COMMAND} -E echo "Generating RPC stubs for ${OUT_BASENAME}..."
|
||||||
COMMAND ${Python3_EXECUTABLE} ${RPC_GENERATOR} --out-dir ${GENERATED_DIR}
|
COMMAND ${Python3_EXECUTABLE} ${RPC_GENERATOR} --out-dir ${GENERATED_DIR}
|
||||||
--compile-commands ${CMAKE_BINARY_DIR}/compile_commands.json
|
--compile-commands ${CMAKE_BINARY_DIR}/compile_commands.json
|
||||||
@@ -37,7 +46,7 @@ function(autocode_annotated_file OUT_BASENAME HEADER SOURCE)
|
|||||||
--header ${HEADER}
|
--header ${HEADER}
|
||||||
--source ${SOURCE}
|
--source ${SOURCE}
|
||||||
--out-base ${OUT_BASENAME}
|
--out-base ${OUT_BASENAME}
|
||||||
DEPENDS ${RPC_GENERATOR} ${RPC_TEMPLATES} ${HEADER} ${SOURCE}
|
DEPENDS ${RPC_GENERATOR} ${RPC_TEMPLATES}/* ${HEADER} ${SOURCE}
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
COMMENT "Running RPC code generator for ${OUT_BASENAME}"
|
COMMENT "Running RPC code generator for ${OUT_BASENAME}"
|
||||||
VERBATIM
|
VERBATIM
|
||||||
@@ -50,6 +59,8 @@ function(autocode_annotated_file OUT_BASENAME HEADER SOURCE)
|
|||||||
${out_proxy_cpp}
|
${out_proxy_cpp}
|
||||||
${out_skeleton_h}
|
${out_skeleton_h}
|
||||||
${out_skeleton_cpp}
|
${out_skeleton_cpp}
|
||||||
|
${out_client_c_h}
|
||||||
|
${out_client_c_cpp}
|
||||||
)
|
)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
@@ -81,3 +92,30 @@ add_executable(client
|
|||||||
)
|
)
|
||||||
|
|
||||||
add_dependencies(client rpc_generated)
|
add_dependencies(client rpc_generated)
|
||||||
|
|
||||||
|
# C bindings library
|
||||||
|
add_library(rpc_client_c STATIC
|
||||||
|
src/rpc_client_c.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(rpc_client_c PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||||
|
${GENERATED_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
# C client
|
||||||
|
add_executable(client_c
|
||||||
|
src/client_c.c
|
||||||
|
${GENERATED_DIR}/MyServiceClient_c.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(client_c PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||||
|
${GENERATED_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(client_c
|
||||||
|
rpc_client_c
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(client_c rpc_generated)
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
# Minimal C++ RPC PoC with Auto-Code Generation
|
# Minimal C++ RPC PoC with Auto-Code Generation
|
||||||
|
|
||||||
Простой Proof-of-Concept реализации RPC для C++ с автоматической генерацией прокси и скелета по аннотациям в исходниках.
|
Простой Proof-of-Concept реализации RPC для C++ с автоматической генерацией прокси и скелета по аннотациям в исходниках. Включает C-биндинги для клиентской стороны, позволяющие использовать RPC из C-приложений.
|
||||||
|
|
||||||
Проект демонстрирует:
|
Проект демонстрирует:
|
||||||
|
|
||||||
* Парсинг исходников C++ с помощью `libclang` для поиска аннотированных классов и методов.
|
* Парсинг исходников C++ с помощью `libclang` для поиска аннотированных классов и методов.
|
||||||
* Автоматическую генерацию файлов:
|
* Автоматическую генерацию файлов:
|
||||||
* `*.proxy.h/cpp` — клиентский прокси для вызова удалённых методов.
|
* `*.proxy.h/cpp` — клиентский прокси для вызова удалённых методов (C++).
|
||||||
* `*.skeleton.h/cpp` — серверный скелет для приёма запросов и вызова реальных методов.
|
* `*.skeleton.h/cpp` — серверный скелет для приёма запросов и вызова реальных методов.
|
||||||
|
* `*Client_c.h/cpp` — C-биндинги для клиентской стороны (автоматически генерируются для каждого `RPC_EXPORT` класса).
|
||||||
* Минимальный протокол передачи данных через **именованные каналы (FIFO)**.
|
* Минимальный протокол передачи данных через **именованные каналы (FIFO)**.
|
||||||
* Поддержка только типов `int` для аргументов и возвращаемого значения (PoC).
|
* Поддержка только типов `int` для аргументов и возвращаемого значения (PoC).
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@
|
|||||||
|
|
||||||
## Структура проекта
|
## Структура проекта
|
||||||
|
|
||||||
```
|
```text
|
||||||
project/
|
project/
|
||||||
├── CMakeLists.txt
|
├── CMakeLists.txt
|
||||||
├── README.md
|
├── README.md
|
||||||
@@ -31,20 +32,27 @@ project/
|
|||||||
│ │ └── IpcSerializer.h # сериализаторы (TextIpcSerializer)
|
│ │ └── IpcSerializer.h # сериализаторы (TextIpcSerializer)
|
||||||
│ └── rpc/
|
│ └── rpc/
|
||||||
│ ├── rpc_export.h
|
│ ├── rpc_export.h
|
||||||
│ ├── RpcInvoker.h
|
│ ├── RpcRegistry.h # реестр RPC-объектов (скелетонов)
|
||||||
│ └── RpcValue.h
|
│ ├── RpcInvoker.h # тонкий фасад над RpcRegistry
|
||||||
|
│ ├── RpcValue.h
|
||||||
|
│ ├── rpc_client_c.h # базовый C API для RPC клиента
|
||||||
|
│ └── rpc_client_c_impl.h # внутренний заголовок для C++ (не для C)
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── client.cpp
|
│ ├── client.cpp # C++ клиент
|
||||||
|
│ ├── client_c.c # C клиент
|
||||||
│ ├── MyService.cpp
|
│ ├── MyService.cpp
|
||||||
│ ├── MyService.h
|
│ ├── MyService.h
|
||||||
│ └── server.cpp
|
│ ├── server.cpp
|
||||||
|
│ └── rpc_client_c.cpp # реализация базовой C-обёртки
|
||||||
├── tools/
|
├── tools/
|
||||||
│ ├── generate_rpc.py
|
│ ├── generate_rpc.py
|
||||||
│ └── templates/
|
│ └── templates/
|
||||||
│ ├── proxy.cpp.j2
|
│ ├── proxy.cpp.j2
|
||||||
│ ├── proxy.h.j2
|
│ ├── proxy.h.j2
|
||||||
│ ├── skeleton.cpp.j2
|
│ ├── skeleton.cpp.j2
|
||||||
│ └── skeleton.h.j2
|
│ ├── skeleton.h.j2
|
||||||
|
│ ├── client_c.h.j2 # шаблон для C заголовка
|
||||||
|
│ └── client_c.cpp.j2 # шаблон для C реализации
|
||||||
└── build/ # создаётся при сборке
|
└── build/ # создаётся при сборке
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -53,27 +61,49 @@ project/
|
|||||||
## Архитектура (кратко)
|
## Архитектура (кратко)
|
||||||
|
|
||||||
* **Уровень IPC-сообщений**: `IpcMessage` (type alias для `BaseIpcMessage<TextIpcSerializer>`)
|
* **Уровень IPC-сообщений**: `IpcMessage` (type alias для `BaseIpcMessage<TextIpcSerializer>`)
|
||||||
* Построение: `msg.add<std::string>("MyService.add"); msg.add<int>(7); msg.add<int>(8);`
|
* Построение (PoC):
|
||||||
* Разбор: `auto name = msg.get<std::string>(); auto a = msg.get<int>(); auto b = msg.get<int>();`
|
`msg.add<int>(objectId); msg.add<std::string>("MyService.add"); msg.add<int>(7); msg.add<int>(8);`
|
||||||
|
* Разбор:
|
||||||
|
`auto id = msg.get<int>(); auto name = msg.get<std::string>(); auto a = msg.get<int>(); auto b = msg.get<int>();`
|
||||||
* Сериализация вынесена в отдельные сериализаторы (`TextIpcSerializer` и т.д.)
|
* Сериализация вынесена в отдельные сериализаторы (`TextIpcSerializer` и т.д.)
|
||||||
* Тип сырых данных параметризован через сериализатор (по умолчанию `std::string`, можно использовать `std::vector<std::byte>` для бинарных форматов)
|
* Тип сырых данных параметризован через сериализатор (по умолчанию `std::string`, можно использовать `std::vector<std::byte>` для бинарных форматов)
|
||||||
* Выбор сериализатора делается один раз в `IpcConfig.h` через type alias
|
* Выбор сериализатора делается один раз в `IpcConfig.h` через type alias
|
||||||
|
|
||||||
* **Уровень канала**: `IpcChannel` + `IpcPipeChannel`
|
* **Уровень канала**: `IpcChannel` + `IpcPipeChannel`
|
||||||
* `IpcChannel` — абстракция транспорта: `send(const IpcMessage&)`, `receive() -> IpcMessage`.
|
* `IpcChannel` — абстракция транспорта: `send(const IpcMessage&)`, `receive() -> IpcMessage`.
|
||||||
* `IpcPipeChannel` — реализация поверх двух FIFO (`/tmp/fifo_to_server`, `/tmp/fifo_to_client`), которая внутри работает со строками, но наружу — только с `IpcMessage`.
|
* `IpcPipeChannel` — реализация поверх двух FIFO (`/tmp/fifo_to_server`, `/tmp/fifo_to_client`), которая внутри работает со строками, но наружу — только с `IpcMessage`.
|
||||||
|
|
||||||
* **Уровень RPC-ядра**:
|
* **Уровень RPC-ядра**:
|
||||||
* `IpcMarshaller` — собирает `IpcMessage` из имени метода и аргументов, отправляет через `IpcChannel` и читает ответ.
|
* `IpcMarshaller` — собирает `IpcMessage` из идентификатора объекта (`ObjectId`), имени метода и аргументов, отправляет через `IpcChannel` и читает ответ.
|
||||||
* `RpcInvoker` — по имени метода (первое поле сообщения) находит зарегистрированную функцию-член и вызывает её, читая аргументы через `get<T>()`.
|
* `RpcRegistry` — владеет RPC-объектами (`IRpcObject`), каждому выдаётся целочисленный `ObjectId` (PoC: `int`, совместимый с `BaseIpcMessage`).
|
||||||
|
* `IRpcObject` — базовый интерфейс для серверных RPC-объектов (обычно скелетоны), реализующий виртуальный `invoke(method, args)`.
|
||||||
|
* `RpcInvoker` — тонкий фасад: по `ObjectId` берёт объект из `RpcRegistry` и зовёт `obj->invoke(method, args)`.
|
||||||
|
|
||||||
* **Сгенерированные обёртки**:
|
* **Сгенерированные обёртки**:
|
||||||
* `*.proxy.*` — шаблонные классы, зависящие только от абстрактного `impl` с методом `impl.callTyped<Ret>(method, args...)` и не знающие про конкретный транспорт.
|
* `*.proxy.*` — шаблонные классы (C++), зависящие только от абстрактного `impl` с методом
|
||||||
* `*.skeleton.*` — используют `RpcInvoker` и принимают/возвращают `RpcValue` для диспетчеризации вызовов.
|
`impl.callTyped<Ret>(method, args...)` и не знающие про конкретный транспорт.
|
||||||
|
В PoC роль `impl` выполняет `IpcMarshaller`, которому при создании передаётся `ObjectId`.
|
||||||
|
Для разных удалённых объектов создаются разные экземпляры маршаллера (с разными `ObjectId`).
|
||||||
|
* `*.skeleton.*` — реализуют `IRpcObject`:
|
||||||
|
* внутри держат ссылку/указатель на реальный объект (`MyService`);
|
||||||
|
* в `invoke()` по имени метода ищут соответствующий хендлер в статической `std::unordered_map<std::string, Handler>`;
|
||||||
|
* хендлеры (`call_<method>`) распаковывают `RpcArgs`, вызывают реальный метод на `MyService` и упаковывают результат в `RpcValue`.
|
||||||
|
* `*Client_c.*` — C-биндинги для клиентской стороны:
|
||||||
|
* автоматически генерируются для каждого класса с аннотацией `RPC_EXPORT`;
|
||||||
|
* предоставляют чистый C API с функциями вида `{service}_client_create()`, `{service}_{method}()`;
|
||||||
|
* все исключения преобразуются в код ошибки `-1`;
|
||||||
|
* используют базовую C-обёртку `rpc_client_c.h` для работы с IPC.
|
||||||
|
|
||||||
|
Так достигается поддержка **нескольких объектов одного и того же сервиса** на сервере:
|
||||||
|
каждому объекту соответствует свой skeleton, зарегистрированный в `RpcRegistry` под уникальным `ObjectId`,
|
||||||
|
а клиентский код адресует конкретный объект через `IpcMarshaller`, «прошитый» этим `ObjectId`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Зависимости
|
## Зависимости
|
||||||
|
|
||||||
* CMake ≥ 3.10
|
* CMake ≥ 3.10
|
||||||
* GCC или Clang с поддержкой C++17
|
* GCC или Clang с поддержкой C++17 и C11
|
||||||
* Python 3.8+ с пакетами:
|
* Python 3.8+ с пакетами:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -116,7 +146,7 @@ public:
|
|||||||
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build
|
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build
|
||||||
```
|
```
|
||||||
|
|
||||||
Директива `CMAKE_EXPORT_COMPILE_COMMANDS=ON` нужна для корректного парсинга в `libclang`.
|
Директива `CMAKE_EXPORT_COMPILE_COMMANDS=ON` нужна для корректного парсинга в `libclang`. Впрочем, в CMakeLists.txt эта директива уже должна быть включена.
|
||||||
|
|
||||||
2. Собираем проект:
|
2. Собираем проект:
|
||||||
|
|
||||||
@@ -124,11 +154,13 @@ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build
|
|||||||
cmake --build build
|
cmake --build build
|
||||||
```
|
```
|
||||||
|
|
||||||
В результате получаем два бинарника:
|
В результате получаем бинарники:
|
||||||
|
|
||||||
```
|
```text
|
||||||
./build/server
|
./build/server # сервер
|
||||||
./build/client
|
./build/client # C++ клиент
|
||||||
|
./build/client_c # C клиент
|
||||||
|
./build/librpc_client_c.a # статическая библиотека C-биндингов
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -143,19 +175,26 @@ cmake --build build
|
|||||||
python3 tools/generate_rpc.py \
|
python3 tools/generate_rpc.py \
|
||||||
--out-dir build/generated \
|
--out-dir build/generated \
|
||||||
--compile-commands build/compile_commands.json \
|
--compile-commands build/compile_commands.json \
|
||||||
src/MyService.h
|
--templates tools/templates \
|
||||||
|
--header src/MyService.h \
|
||||||
|
--source src/MyService.cpp \
|
||||||
|
--out-base MyService
|
||||||
```
|
```
|
||||||
|
|
||||||
Сгенерированные файлы попадут в:
|
Сгенерированные файлы попадут в:
|
||||||
|
|
||||||
```
|
```text
|
||||||
build/generated/
|
build/generated/
|
||||||
├─ MyService.proxy.h
|
├─ MyService.proxy.h
|
||||||
├─ MyService.proxy.cpp
|
├─ MyService.proxy.cpp
|
||||||
├─ MyService.skeleton.h
|
├─ MyService.skeleton.h
|
||||||
└─ MyService.skeleton.cpp
|
├─ MyService.skeleton.cpp
|
||||||
|
├─ MyServiceClient_c.h # C заголовок
|
||||||
|
└─ MyServiceClient_c.cpp # C реализация
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Примечание**: C-биндинги (`*Client_c.*`) автоматически генерируются для каждого класса с аннотацией `RPC_EXPORT`. При добавлении нового сервиса достаточно пометить его `RPC_EXPORT` — C-биндинги будут созданы автоматически при сборке.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Запуск
|
## Запуск
|
||||||
@@ -166,18 +205,97 @@ build/generated/
|
|||||||
./build/server
|
./build/server
|
||||||
```
|
```
|
||||||
|
|
||||||
Сервер создаёт (при необходимости) именованные пайпы `/tmp/fifo_to_server` и `/tmp/fifo_to_client`, читает запросы из `fifo_to_server` и пишет ответы в `fifo_to_client` через `IpcPipeChannel`.
|
Сервер создаёт (при необходимости) именованные пайпы `/tmp/fifo_to_server` и `/tmp/fifo_to_client`,
|
||||||
|
читает запросы из `fifo_to_server` и пишет ответы в `fifo_to_client` через `IpcPipeChannel`.
|
||||||
|
На стороне сервера `RpcRegistry` регистрирует два объекта `MyService`, обёрнутых в `MyServiceSkeleton`,
|
||||||
|
под `ObjectId = 0` и `ObjectId = 1`.
|
||||||
|
|
||||||
2. В другом терминале — клиент:
|
2. В другом терминале — клиент (C++ или C):
|
||||||
|
|
||||||
|
**C++ клиент:**
|
||||||
```bash
|
```bash
|
||||||
./build/client
|
./build/client
|
||||||
```
|
```
|
||||||
|
|
||||||
Клиент также открывает эти FIFO, но логически пишет в `fifo_to_server` и читает из `fifo_to_client` (через тот же `IpcPipeChannel`), а пользовательский код видит только вызов `MyServiceProxy`.
|
**C клиент:**
|
||||||
|
```bash
|
||||||
|
./build/client_c
|
||||||
|
```
|
||||||
|
|
||||||
**Ожидаемый вывод:**
|
Оба клиента открывают те же FIFO, но логически пишут в `fifo_to_server` и читают из `fifo_to_client`
|
||||||
|
(через тот же `IpcPipeChannel`).
|
||||||
|
|
||||||
|
**C++ клиент** использует шаблонные прокси `MyServiceProxy`, привязанные к разным `ObjectId`.
|
||||||
|
|
||||||
|
**C клиент** использует C API:
|
||||||
|
```c
|
||||||
|
// Создание клиента и маршаллеров
|
||||||
|
RpcClient* client = rpc_client_create("/tmp/fifo_to_client", "/tmp/fifo_to_server");
|
||||||
|
IpcMarshallerHandle* m1 = rpc_client_create_marshaller(client, 0);
|
||||||
|
IpcMarshallerHandle* m2 = rpc_client_create_marshaller(client, 1);
|
||||||
|
|
||||||
|
// Создание сервисных клиентов
|
||||||
|
MyServiceClient* obj1 = myservice_client_create(m1);
|
||||||
|
MyServiceClient* obj2 = myservice_client_create(m2);
|
||||||
|
|
||||||
|
// Вызовы методов (возвращают -1 при ошибке)
|
||||||
|
myservice_add(obj1, 7, 9);
|
||||||
|
int counter = myservice_get(obj1);
|
||||||
|
myservice_add(obj2, 0, counter);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ожидаемый вывод (одинаковый для обоих клиентов):**
|
||||||
|
|
||||||
```text
|
```text
|
||||||
RESULT: 15
|
$ ./server &
|
||||||
|
$ ./client
|
||||||
|
OBJ1: 16 OBJ2: 16
|
||||||
|
$ ./client
|
||||||
|
OBJ1: 32 OBJ2: 48
|
||||||
|
$ ./client_c
|
||||||
|
OBJ1: 48 OBJ2: 96
|
||||||
|
$ ./client_c
|
||||||
|
OBJ1: 64 OBJ2: 160
|
||||||
```
|
```
|
||||||
|
|
||||||
|
где:
|
||||||
|
- первый объект (`ObjectId = 0`) увеличивает свой счётчик через `add(7, 9)` → счётчик = 16;
|
||||||
|
- второй объект (`ObjectId = 1`) увеличивает свой счётчик на `obj1.get()`, то есть также до 16.
|
||||||
|
|
||||||
|
**Обработка ошибок в C API**: все функции возвращают `-1` при ошибке (исключения преобразуются автоматически).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Использование C API
|
||||||
|
|
||||||
|
### Базовая обёртка (`rpc_client_c.h`)
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Создание RPC клиента (обёртка над IpcPipeChannel)
|
||||||
|
RpcClient* rpc_client_create(const char* read_pipe, const char* write_pipe);
|
||||||
|
|
||||||
|
// Создание маршаллера для конкретного объекта
|
||||||
|
IpcMarshallerHandle* rpc_client_create_marshaller(RpcClient* client, int object_id);
|
||||||
|
|
||||||
|
// Освобождение ресурсов
|
||||||
|
void rpc_marshaller_destroy(IpcMarshallerHandle* marshaller);
|
||||||
|
void rpc_client_destroy(RpcClient* client);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Генерируемые функции для сервисов
|
||||||
|
|
||||||
|
Для каждого класса с `RPC_EXPORT` автоматически генерируются функции:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Создание клиента сервиса
|
||||||
|
MyServiceClient* myservice_client_create(IpcMarshallerHandle* marshaller);
|
||||||
|
void myservice_client_destroy(MyServiceClient* client);
|
||||||
|
|
||||||
|
// Вызовы методов (возвращают -1 при ошибке)
|
||||||
|
int myservice_add(MyServiceClient* client, int a, int b);
|
||||||
|
int myservice_get(MyServiceClient* client);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Именование функций**: имя класса преобразуется в нижний регистр (`MyService` → `myservice`), методы сохраняют оригинальное имя.
|
||||||
|
|
||||||
|
**Автоматическая генерация**: при добавлении нового `RPC_EXPORT` класса C-биндинги генерируются автоматически при сборке, без необходимости изменять код генератора.
|
||||||
|
|||||||
+15
-2
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ipc/IpcConfig.h>
|
#include <ipc/IpcConfig.h>
|
||||||
|
#include <rpc/RpcRegistry.h>
|
||||||
#include <rpc/RpcValue.h>
|
#include <rpc/RpcValue.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -10,11 +11,18 @@
|
|||||||
// (RpcValue/RpcArgs).
|
// (RpcValue/RpcArgs).
|
||||||
namespace IpcCodec {
|
namespace IpcCodec {
|
||||||
|
|
||||||
// Запрос: имя метода + вектор аргументов.
|
// Используем ObjectId из специализированного RPC-реестра.
|
||||||
inline IpcMessage encodeRequest(const std::string& method,
|
using ObjectId = RpcRegistry::ObjectId;
|
||||||
|
|
||||||
|
// Запрос: ObjectId + имя метода + вектор аргументов.
|
||||||
|
inline IpcMessage encodeRequest(ObjectId objectId,
|
||||||
|
const std::string& method,
|
||||||
const RpcArgs& args) {
|
const RpcArgs& args) {
|
||||||
IpcMessage msg;
|
IpcMessage msg;
|
||||||
|
|
||||||
|
// ObjectId (PoC: приводим к int)
|
||||||
|
msg.add(static_cast<int>(objectId));
|
||||||
|
|
||||||
// имя метода
|
// имя метода
|
||||||
msg.add(method);
|
msg.add(method);
|
||||||
|
|
||||||
@@ -27,10 +35,15 @@ inline IpcMessage encodeRequest(const std::string& method,
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline void decodeRequest(const IpcMessage& msg,
|
inline void decodeRequest(const IpcMessage& msg,
|
||||||
|
ObjectId& objectId,
|
||||||
std::string& method,
|
std::string& method,
|
||||||
RpcArgs& args) {
|
RpcArgs& args) {
|
||||||
IpcMessage copy = msg;
|
IpcMessage copy = msg;
|
||||||
|
|
||||||
|
// ObjectId (PoC: читаем как int и приводим к ObjectId)
|
||||||
|
int rawId = copy.get<int>();
|
||||||
|
objectId = static_cast<ObjectId>(rawId);
|
||||||
|
|
||||||
// имя метода
|
// имя метода
|
||||||
method = copy.get<std::string>();
|
method = copy.get<std::string>();
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,12 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IpcCodec::ObjectId objectId;
|
||||||
std::string method;
|
std::string method;
|
||||||
RpcArgs args;
|
RpcArgs args;
|
||||||
IpcCodec::decodeRequest(req, method, args);
|
IpcCodec::decodeRequest(req, objectId, method, args);
|
||||||
|
|
||||||
RpcValue result = invoker_.dispatch(method, args);
|
RpcValue result = invoker_.dispatch(objectId, method, args);
|
||||||
IpcMessage resp = IpcCodec::encodeResponse(result);
|
IpcMessage resp = IpcCodec::encodeResponse(result);
|
||||||
channel_.send(resp);
|
channel_.send(resp);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -9,13 +9,14 @@
|
|||||||
// и IpcCodec, но снаружи предъявляет только callTyped<T>(...).
|
// и IpcCodec, но снаружи предъявляет только callTyped<T>(...).
|
||||||
class IpcMarshaller {
|
class IpcMarshaller {
|
||||||
public:
|
public:
|
||||||
explicit IpcMarshaller(IpcChannel& ch)
|
explicit IpcMarshaller(IpcChannel& ch, IpcCodec::ObjectId objectId = 0)
|
||||||
: channel(ch) {}
|
: channel(ch)
|
||||||
|
, objectId_(objectId) {}
|
||||||
|
|
||||||
// Базовый type-erased вызов: принимает вектор RpcValue и возвращает RpcValue.
|
// Базовый type-erased вызов: принимает вектор RpcValue и возвращает RpcValue.
|
||||||
RpcValue call(const std::string& method, const RpcArgs& args) {
|
RpcValue call(const std::string& method, const RpcArgs& args) {
|
||||||
// упаковать запрос в IpcMessage
|
// упаковать запрос в IpcMessage
|
||||||
IpcMessage msg = IpcCodec::encodeRequest(method, args);
|
IpcMessage msg = IpcCodec::encodeRequest(objectId_, method, args);
|
||||||
|
|
||||||
// отправить
|
// отправить
|
||||||
channel.send(msg);
|
channel.send(msg);
|
||||||
@@ -39,6 +40,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
IpcChannel& channel;
|
IpcChannel& channel;
|
||||||
|
IpcCodec::ObjectId objectId_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+13
-36
@@ -1,56 +1,33 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <rpc/RpcRegistry.h>
|
||||||
#include <rpc/RpcValue.h>
|
#include <rpc/RpcValue.h>
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
|
// Инвокер — тонкий фасад над RpcRegistry:
|
||||||
|
// по ObjectId находит объект и делегирует вызов его IRpcObject::invoke().
|
||||||
class RpcInvoker {
|
class RpcInvoker {
|
||||||
public:
|
public:
|
||||||
template<typename Obj, typename Ret, typename... Args>
|
using ObjectId = RpcRegistry::ObjectId; // согласован с IpcCodec::ObjectId
|
||||||
void registerMethod(Obj* instance,
|
|
||||||
const std::string& name,
|
|
||||||
Ret (Obj::*method)(Args...)) {
|
|
||||||
handlers[name] =
|
|
||||||
[instance, method](const RpcArgs& args) -> RpcValue {
|
|
||||||
auto tuple = readArgs<Args...>(args);
|
|
||||||
Ret result = std::apply(
|
|
||||||
method, std::tuple_cat(std::make_tuple(instance), tuple));
|
|
||||||
|
|
||||||
// PoC: считаем, что Ret == int.
|
explicit RpcInvoker(RpcRegistry& registry)
|
||||||
return RpcValue::fromInt(result);
|
: registry_(registry) {}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
RpcValue dispatch(const std::string& method, const RpcArgs& args) const {
|
RpcValue dispatch(ObjectId objectId,
|
||||||
auto it = handlers.find(method);
|
const std::string& method,
|
||||||
if (it == handlers.end()) {
|
const RpcArgs& args) const {
|
||||||
|
IRpcObject* obj = registry_.get(objectId);
|
||||||
|
if (!obj) {
|
||||||
// PoC: в случае ошибки возвращаем 0.
|
// PoC: в случае ошибки возвращаем 0.
|
||||||
return RpcValue::fromInt(0);
|
return RpcValue::fromInt(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return it->second(args);
|
return obj->invoke(method, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename... Args, std::size_t... I>
|
RpcRegistry& registry_;
|
||||||
static std::tuple<Args...>
|
|
||||||
readArgsImpl(const RpcArgs& args, std::index_sequence<I...>) {
|
|
||||||
return std::tuple<Args...>{
|
|
||||||
static_cast<Args>(args[I].asInt())...}; // PoC: только int
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
static std::tuple<Args...> readArgs(const RpcArgs& args) {
|
|
||||||
return readArgsImpl<Args...>(
|
|
||||||
args, std::make_index_sequence<sizeof...(Args)>{});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unordered_map<std::string,
|
|
||||||
std::function<RpcValue(const RpcArgs&)>>
|
|
||||||
handlers;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <rpc/RpcValue.h>
|
||||||
|
|
||||||
|
// Базовый интерфейс для всех RPC-объектов, живущих в реестре.
|
||||||
|
// Каждая реализация (обычно *Skeleton) знает, как вызвать реальные методы.
|
||||||
|
struct IRpcObject {
|
||||||
|
virtual ~IRpcObject() = default;
|
||||||
|
|
||||||
|
virtual RpcValue invoke(const std::string& method,
|
||||||
|
const RpcArgs& args) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Специализированный реестр объектов именно для RPC-уровня.
|
||||||
|
// Владеет скелетонами (IRpcObject) через unique_ptr. Сами доменные объекты
|
||||||
|
// (MyService и т.п.) живут снаружи и могут быть обёрнуты в shared_ptr.
|
||||||
|
class RpcRegistry {
|
||||||
|
public:
|
||||||
|
// PoC: ObjectId храним как int-совместимый тип, чтобы его можно было
|
||||||
|
// передавать через BaseIpcMessage, который поддерживает только int/string.
|
||||||
|
using ObjectId = int;
|
||||||
|
|
||||||
|
RpcRegistry() = default;
|
||||||
|
|
||||||
|
// Зарегистрировать объект-обёртку (обычно Skeleton) и вернуть его
|
||||||
|
// идентификатор. По сути, это registerSkeleton<T>(...).
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
ObjectId registerObject(Args&&... args) {
|
||||||
|
static_assert(std::is_base_of_v<IRpcObject, T>,
|
||||||
|
"T must inherit IRpcObject");
|
||||||
|
|
||||||
|
const ObjectId id = nextId_++;
|
||||||
|
objects_.emplace(
|
||||||
|
id, std::make_unique<T>(std::forward<Args>(args)...));
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Уничтожить объект по идентификатору.
|
||||||
|
void destroyObject(ObjectId id) {
|
||||||
|
objects_.erase(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Найти объект по идентификатору.
|
||||||
|
IRpcObject* get(ObjectId id) const {
|
||||||
|
auto it = objects_.find(id);
|
||||||
|
return it == objects_.end() ? nullptr : it->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<ObjectId, std::unique_ptr<IRpcObject>> objects_;
|
||||||
|
ObjectId nextId_ = 0; // первый объект будет иметь id = 0 (PoC)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Непрозрачный тип для RPC клиента (обёртка над IpcPipeChannel)
|
||||||
|
typedef struct RpcClient RpcClient;
|
||||||
|
|
||||||
|
// Непрозрачный тип для маршаллера (обёртка над IpcMarshaller)
|
||||||
|
typedef struct IpcMarshallerHandle IpcMarshallerHandle;
|
||||||
|
|
||||||
|
// Создание RPC клиента
|
||||||
|
// read_pipe - путь к FIFO для чтения ответов
|
||||||
|
// write_pipe - путь к FIFO для отправки запросов
|
||||||
|
// Возвращает указатель на клиент или NULL при ошибке
|
||||||
|
RpcClient* rpc_client_create(const char* read_pipe, const char* write_pipe);
|
||||||
|
|
||||||
|
// Уничтожение RPC клиента
|
||||||
|
void rpc_client_destroy(RpcClient* client);
|
||||||
|
|
||||||
|
// Создание маршаллера для конкретного объекта
|
||||||
|
// client - RPC клиент
|
||||||
|
// object_id - идентификатор удалённого объекта
|
||||||
|
// Возвращает указатель на маршаллер или NULL при ошибке
|
||||||
|
IpcMarshallerHandle* rpc_client_create_marshaller(RpcClient* client, int object_id);
|
||||||
|
|
||||||
|
// Уничтожение маршаллера
|
||||||
|
void rpc_marshaller_destroy(IpcMarshallerHandle* marshaller);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Внутренний заголовок для C++ файлов, использующих C-биндинги
|
||||||
|
// Не должен включаться в C файлы
|
||||||
|
|
||||||
|
#include "ipc/IpcMarshaller.h"
|
||||||
|
#include "ipc/IpcPipeChannel.h"
|
||||||
|
|
||||||
|
struct RpcClient {
|
||||||
|
IpcPipeChannel* channel;
|
||||||
|
|
||||||
|
RpcClient(IpcPipeChannel* ch) : channel(ch) {}
|
||||||
|
~RpcClient() {
|
||||||
|
delete channel;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IpcMarshallerHandle {
|
||||||
|
IpcMarshaller* marshaller;
|
||||||
|
|
||||||
|
IpcMarshallerHandle(IpcMarshaller* m) : marshaller(m) {}
|
||||||
|
~IpcMarshallerHandle() {
|
||||||
|
delete marshaller;
|
||||||
|
}
|
||||||
|
};
|
||||||
+17
-6
@@ -16,11 +16,22 @@ int main() {
|
|||||||
// и читает из fifo, в который пишет сервер (fifo_to_client).
|
// и читает из fifo, в который пишет сервер (fifo_to_client).
|
||||||
IpcPipeChannel ch("/tmp/fifo_to_client", "/tmp/fifo_to_server");
|
IpcPipeChannel ch("/tmp/fifo_to_client", "/tmp/fifo_to_server");
|
||||||
|
|
||||||
// RPC‑уровень: создаём IpcMarshaller поверх канала и передаём его в прокси.
|
// RPC‑уровень: создаём два маршаллера с разными ObjectId и два прокси.
|
||||||
IpcMarshaller marshaller(ch);
|
// По договорённости на сервере:
|
||||||
MyServiceProxy<IpcMarshaller> proxy(marshaller);
|
// ObjectId = 0 -> первый MyService
|
||||||
|
// ObjectId = 1 -> второй MyService
|
||||||
|
IpcMarshaller m1(ch, 0);
|
||||||
|
IpcMarshaller m2(ch, 1);
|
||||||
|
MyServiceProxy<IpcMarshaller> obj1(m1);
|
||||||
|
MyServiceProxy<IpcMarshaller> obj2(m2);
|
||||||
|
|
||||||
proxy.add(7, 9);
|
// obj1 увеличивает свой счётчик.
|
||||||
int counter = proxy.get();
|
obj1.add(7, 9);
|
||||||
std::cout << "RESULT: " << counter << std::endl;
|
|
||||||
|
// obj2 увеличивает счётчик на текущее значение счётчика obj1.
|
||||||
|
obj2.add(0, obj1.get());
|
||||||
|
|
||||||
|
int c1 = obj1.get();
|
||||||
|
int c2 = obj2.get();
|
||||||
|
std::cout << "OBJ1: " << c1 << " OBJ2: " << c2 << std::endl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#include "MyServiceClient_c.h"
|
||||||
|
#include "rpc/rpc_client_c.h"
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Создание FIFO — часть пользовательского IPC‑кода.
|
||||||
|
mkfifo("/tmp/fifo_to_server", 0666);
|
||||||
|
mkfifo("/tmp/fifo_to_client", 0666);
|
||||||
|
|
||||||
|
// Создание RPC клиента
|
||||||
|
RpcClient* client = rpc_client_create("/tmp/fifo_to_client", "/tmp/fifo_to_server");
|
||||||
|
if (!client) {
|
||||||
|
fprintf(stderr, "Failed to create RPC client\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создание маршаллеров для двух объектов
|
||||||
|
// По договорённости на сервере:
|
||||||
|
// ObjectId = 0 -> первый MyService
|
||||||
|
// ObjectId = 1 -> второй MyService
|
||||||
|
IpcMarshallerHandle* m1 = rpc_client_create_marshaller(client, 0);
|
||||||
|
IpcMarshallerHandle* m2 = rpc_client_create_marshaller(client, 1);
|
||||||
|
|
||||||
|
if (!m1 || !m2) {
|
||||||
|
fprintf(stderr, "Failed to create marshallers\n");
|
||||||
|
rpc_client_destroy(client);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создание клиентов для MyService
|
||||||
|
MyServiceClient* obj1 = myservice_client_create(m1);
|
||||||
|
MyServiceClient* obj2 = myservice_client_create(m2);
|
||||||
|
|
||||||
|
if (!obj1 || !obj2) {
|
||||||
|
fprintf(stderr, "Failed to create service clients\n");
|
||||||
|
rpc_marshaller_destroy(m1);
|
||||||
|
rpc_marshaller_destroy(m2);
|
||||||
|
rpc_client_destroy(client);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// obj1 увеличивает свой счётчик.
|
||||||
|
int result = myservice_add(obj1, 7, 9);
|
||||||
|
if (result == -1) {
|
||||||
|
fprintf(stderr, "Error calling myservice_add on obj1\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// obj2 увеличивает счётчик на текущее значение счётчика obj1.
|
||||||
|
int counter1 = myservice_get(obj1);
|
||||||
|
if (counter1 == -1) {
|
||||||
|
fprintf(stderr, "Error calling myservice_get on obj1\n");
|
||||||
|
} else {
|
||||||
|
result = myservice_add(obj2, 0, counter1);
|
||||||
|
if (result == -1) {
|
||||||
|
fprintf(stderr, "Error calling myservice_add on obj2\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int c1 = myservice_get(obj1);
|
||||||
|
int c2 = myservice_get(obj2);
|
||||||
|
|
||||||
|
if (c1 == -1 || c2 == -1) {
|
||||||
|
fprintf(stderr, "Error getting final counters\n");
|
||||||
|
} else {
|
||||||
|
printf("OBJ1: %d OBJ2: %d\n", c1, c2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Освобождение ресурсов
|
||||||
|
myservice_client_destroy(obj1);
|
||||||
|
myservice_client_destroy(obj2);
|
||||||
|
rpc_marshaller_destroy(m1);
|
||||||
|
rpc_marshaller_destroy(m2);
|
||||||
|
rpc_client_destroy(client);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#include "rpc/rpc_client_c.h"
|
||||||
|
#include "rpc/rpc_client_c_impl.h"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
RpcClient* rpc_client_create(const char* read_pipe, const char* write_pipe) {
|
||||||
|
try {
|
||||||
|
IpcPipeChannel* channel = new IpcPipeChannel(read_pipe, write_pipe);
|
||||||
|
// Проверяем, что канал открылся успешно
|
||||||
|
// (IpcPipeChannel может вывести ошибку, но не бросает исключение)
|
||||||
|
// Для простоты считаем, что если конструктор завершился, всё ОК
|
||||||
|
return new RpcClient(channel);
|
||||||
|
} catch (...) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rpc_client_destroy(RpcClient* client) {
|
||||||
|
if (client) {
|
||||||
|
delete client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcMarshallerHandle* rpc_client_create_marshaller(RpcClient* client, int object_id) {
|
||||||
|
if (!client || !client->channel) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
IpcMarshaller* marshaller = new IpcMarshaller(*client->channel, object_id);
|
||||||
|
return new IpcMarshallerHandle(marshaller);
|
||||||
|
} catch (...) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rpc_marshaller_destroy(IpcMarshallerHandle* marshaller) {
|
||||||
|
if (marshaller) {
|
||||||
|
delete marshaller;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
+15
-4
@@ -4,6 +4,7 @@
|
|||||||
#include "ipc/IpcPipeChannel.h"
|
#include "ipc/IpcPipeChannel.h"
|
||||||
#include "ipc/IpcDispatcher.h"
|
#include "ipc/IpcDispatcher.h"
|
||||||
#include "rpc/RpcInvoker.h"
|
#include "rpc/RpcInvoker.h"
|
||||||
|
#include "rpc/RpcRegistry.h"
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
@@ -16,10 +17,20 @@ int main() {
|
|||||||
// Сервер читает из fifo_to_server и пишет в fifo_to_client.
|
// Сервер читает из fifo_to_server и пишет в fifo_to_client.
|
||||||
IpcPipeChannel ch("/tmp/fifo_to_server", "/tmp/fifo_to_client");
|
IpcPipeChannel ch("/tmp/fifo_to_server", "/tmp/fifo_to_client");
|
||||||
|
|
||||||
// RPC‑уровень: инвокер и скелет, который лишь регистрирует методы.
|
// RPC‑уровень: реестр объектов и инвокер, который знает, как их вызывать.
|
||||||
RpcInvoker invoker;
|
RpcRegistry registry;
|
||||||
MyService realObj;
|
RpcInvoker invoker(registry);
|
||||||
MyServiceSkeleton skeleton(realObj, invoker);
|
|
||||||
|
// PoC: создаём два "реальных" объекта и оборачиваем их в Skeleton'ы,
|
||||||
|
// которыми владеет RpcRegistry.
|
||||||
|
MyService obj1;
|
||||||
|
MyService obj2;
|
||||||
|
RpcRegistry::ObjectId id1 =
|
||||||
|
registry.registerObject<MyServiceSkeleton>(obj1); // id1 == 0
|
||||||
|
RpcRegistry::ObjectId id2 =
|
||||||
|
registry.registerObject<MyServiceSkeleton>(obj2); // id2 == 1
|
||||||
|
(void)id1;
|
||||||
|
(void)id2;
|
||||||
|
|
||||||
// IPC‑диспетчер, который декодирует IpcMessage в RPC-вызовы и обратно.
|
// IPC‑диспетчер, который декодирует IpcMessage в RPC-вызовы и обратно.
|
||||||
IpcDispatcher dispatcher(ch, invoker);
|
IpcDispatcher dispatcher(ch, invoker);
|
||||||
|
|||||||
@@ -262,6 +262,8 @@ def main():
|
|||||||
proxy_cpp = env.get_template("proxy.cpp.j2")
|
proxy_cpp = env.get_template("proxy.cpp.j2")
|
||||||
skeleton_h = env.get_template("skeleton.h.j2")
|
skeleton_h = env.get_template("skeleton.h.j2")
|
||||||
skeleton_cpp = env.get_template("skeleton.cpp.j2")
|
skeleton_cpp = env.get_template("skeleton.cpp.j2")
|
||||||
|
client_c_h = env.get_template("client_c.h.j2")
|
||||||
|
client_c_cpp = env.get_template("client_c.cpp.j2")
|
||||||
|
|
||||||
base = args.out_base
|
base = args.out_base
|
||||||
|
|
||||||
@@ -277,6 +279,12 @@ def main():
|
|||||||
with open(f"{out_dir}/{base}.skeleton.cpp", "w") as f:
|
with open(f"{out_dir}/{base}.skeleton.cpp", "w") as f:
|
||||||
f.write(skeleton_cpp.render(cls=cls))
|
f.write(skeleton_cpp.render(cls=cls))
|
||||||
|
|
||||||
|
with open(f"{out_dir}/{base}Client_c.h", "w") as f:
|
||||||
|
f.write(client_c_h.render(cls=cls))
|
||||||
|
|
||||||
|
with open(f"{out_dir}/{base}Client_c.cpp", "w") as f:
|
||||||
|
f.write(client_c_cpp.render(cls=cls))
|
||||||
|
|
||||||
print("Generated files for class", cls.name, "into base", base)
|
print("Generated files for class", cls.name, "into base", base)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#include "{{ cls.name }}Client_c.h"
|
||||||
|
#include "rpc/rpc_client_c_impl.h"
|
||||||
|
#include "ipc/IpcMarshaller.h"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
struct {{ cls.name }}Client {
|
||||||
|
IpcMarshaller* marshaller;
|
||||||
|
|
||||||
|
{{ cls.name }}Client(IpcMarshaller* m) : marshaller(m) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
{{ cls.name }}Client* {{ cls.name|lower }}_client_create(IpcMarshallerHandle* marshaller) {
|
||||||
|
if (!marshaller || !marshaller->marshaller) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return new {{ cls.name }}Client(marshaller->marshaller);
|
||||||
|
} catch (...) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void {{ cls.name|lower }}_client_destroy({{ cls.name }}Client* client) {
|
||||||
|
if (client) {
|
||||||
|
delete client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{% for m in cls.methods %}
|
||||||
|
{{ m.return_type }} {{ cls.name|lower }}_{{ m.name }}({{ cls.name }}Client* client{% for a in m.args %}, {{ a.type }} {{ a.name }}{% endfor %}) {
|
||||||
|
if (!client || !client->marshaller) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return client->marshaller->template callTyped<{{ m.return_type }}>("{{ cls.name }}.{{ m.name }}"{% for a in m.args %}, {{ a.name }}{% endfor %});
|
||||||
|
} catch (...) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "rpc/rpc_client_c.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Непрозрачный тип для клиента {{ cls.name }}
|
||||||
|
typedef struct {{ cls.name }}Client {{ cls.name }}Client;
|
||||||
|
|
||||||
|
// Создание клиента {{ cls.name }}
|
||||||
|
// marshaller - маршаллер, созданный через rpc_client_create_marshaller
|
||||||
|
// Возвращает указатель на клиент или NULL при ошибке
|
||||||
|
{{ cls.name }}Client* {{ cls.name|lower }}_client_create(IpcMarshallerHandle* marshaller);
|
||||||
|
|
||||||
|
// Уничтожение клиента {{ cls.name }}
|
||||||
|
void {{ cls.name|lower }}_client_destroy({{ cls.name }}Client* client);
|
||||||
|
|
||||||
|
{% for m in cls.methods %}
|
||||||
|
// Вызов метода {{ m.name }}
|
||||||
|
// client - клиент {{ cls.name }}
|
||||||
|
{% for a in m.args %}
|
||||||
|
// {{ a.name }} - {{ a.type }} аргумент
|
||||||
|
{% endfor %}
|
||||||
|
// Возвращает результат или -1 при ошибке
|
||||||
|
{{ m.return_type }} {{ cls.name|lower }}_{{ m.name }}({{ cls.name }}Client* client{% for a in m.args %}, {{ a.type }} {{ a.name }}{% endfor %});
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -1,16 +1,41 @@
|
|||||||
#include "{{ cls.name }}.skeleton.h"
|
#include "{{ cls.name }}.skeleton.h"
|
||||||
|
|
||||||
{{ cls.name }}Skeleton::{{ cls.name }}Skeleton({{ cls.name }}& obj, RpcInvoker& inv)
|
{{ cls.name }}Skeleton::{{ cls.name }}Skeleton({{ cls.name }}& obj)
|
||||||
: invoker(inv) {
|
: obj_(obj) {
|
||||||
{% for m in cls.methods %}
|
|
||||||
invoker.registerMethod(&obj,
|
|
||||||
"{{ cls.name }}.{{ m.name }}",
|
|
||||||
&{{ cls.name }}::{{ m.name }});
|
|
||||||
{% endfor %}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RpcValue {{ cls.name }}Skeleton::dispatch(const std::string& method,
|
RpcValue {{ cls.name }}Skeleton::invoke(const std::string& method,
|
||||||
const RpcArgs& args) {
|
const RpcArgs& args) {
|
||||||
return invoker.dispatch(method, args);
|
const auto& table = handlers();
|
||||||
|
auto it = table.find(method);
|
||||||
|
if (it == table.end()) {
|
||||||
|
// Неизвестный метод — PoC: возвращаем 0.
|
||||||
|
return RpcValue::fromInt(0);
|
||||||
|
}
|
||||||
|
Handler fn = it->second;
|
||||||
|
return (this->*fn)(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::unordered_map<std::string, {{ cls.name }}Skeleton::Handler>&
|
||||||
|
{{ cls.name }}Skeleton::handlers() {
|
||||||
|
static const std::unordered_map<std::string, Handler> kHandlers = {
|
||||||
|
{% for m in cls.methods %}
|
||||||
|
{ "{{ cls.name }}.{{ m.name }}", &{{ cls.name }}Skeleton::call_{{ m.name }} }{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
};
|
||||||
|
return kHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% for m in cls.methods %}
|
||||||
|
RpcValue {{ cls.name }}Skeleton::call_{{ m.name }}(const RpcArgs& args) {
|
||||||
|
// PoC: единственный тип аргументов и результата — int.
|
||||||
|
int idx = 0;
|
||||||
|
{% for a in m.args %}
|
||||||
|
int {{ a.name }} = args[idx++].asInt();
|
||||||
|
{% endfor %}
|
||||||
|
int result = obj_.{{ m.name }}({% for a in m.args %}{{ a.name }}{% if not loop.last %}, {% endif %}{% endfor %});
|
||||||
|
return RpcValue::fromInt(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "{{ cls.name }}.h"
|
#include "{{ cls.name }}.h"
|
||||||
#include "rpc/RpcInvoker.h"
|
#include "rpc/RpcRegistry.h"
|
||||||
|
#include "rpc/RpcValue.h"
|
||||||
|
|
||||||
class {{ cls.name }}Skeleton {
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
// Skeleton-обёртка над {{ cls.name }}, реализующая IRpcObject::invoke().
|
||||||
|
class {{ cls.name }}Skeleton : public IRpcObject {
|
||||||
public:
|
public:
|
||||||
explicit {{ cls.name }}Skeleton({{ cls.name }}& obj, RpcInvoker& invoker);
|
explicit {{ cls.name }}Skeleton({{ cls.name }}& obj);
|
||||||
|
|
||||||
// IPC-независимый диспетчер: принимает имя метода и RpcArgs.
|
RpcValue invoke(const std::string& method,
|
||||||
RpcValue dispatch(const std::string& method, const RpcArgs& args);
|
const RpcArgs& args) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RpcInvoker& invoker;
|
using Handler = RpcValue ({{ cls.name }}Skeleton::*)(const RpcArgs&);
|
||||||
|
|
||||||
|
// Статическая таблица method-name -> member-function.
|
||||||
|
static const std::unordered_map<std::string, Handler>& handlers();
|
||||||
|
|
||||||
|
{% for m in cls.methods %}
|
||||||
|
RpcValue call_{{ m.name }}(const RpcArgs& args);
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{{ cls.name }}& obj_;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user