implement client C binding
This commit is contained in:
+36
-1
@@ -1,8 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(SimpleRPCExample CXX)
|
||||
project(SimpleRPCExample CXX C)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
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)
|
||||
@@ -26,6 +28,8 @@ function(autocode_annotated_file OUT_BASENAME HEADER SOURCE)
|
||||
set(out_proxy_cpp "${GENERATED_DIR}/${OUT_BASENAME}.proxy.cpp")
|
||||
set(out_skeleton_h "${GENERATED_DIR}/${OUT_BASENAME}.skeleton.h")
|
||||
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(
|
||||
OUTPUT
|
||||
@@ -33,6 +37,8 @@ function(autocode_annotated_file OUT_BASENAME HEADER SOURCE)
|
||||
${out_proxy_cpp}
|
||||
${out_skeleton_h}
|
||||
${out_skeleton_cpp}
|
||||
${out_client_c_h}
|
||||
${out_client_c_cpp}
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Generating RPC stubs for ${OUT_BASENAME}..."
|
||||
COMMAND ${Python3_EXECUTABLE} ${RPC_GENERATOR} --out-dir ${GENERATED_DIR}
|
||||
--compile-commands ${CMAKE_BINARY_DIR}/compile_commands.json
|
||||
@@ -53,6 +59,8 @@ function(autocode_annotated_file OUT_BASENAME HEADER SOURCE)
|
||||
${out_proxy_cpp}
|
||||
${out_skeleton_h}
|
||||
${out_skeleton_cpp}
|
||||
${out_client_c_h}
|
||||
${out_client_c_cpp}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
@@ -84,3 +92,30 @@ add_executable(client
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
Простой Proof-of-Concept реализации RPC для C++ с автоматической генерацией прокси и скелета по аннотациям в исходниках.
|
||||
Простой Proof-of-Concept реализации RPC для C++ с автоматической генерацией прокси и скелета по аннотациям в исходниках. Включает C-биндинги для клиентской стороны, позволяющие использовать RPC из C-приложений.
|
||||
|
||||
Проект демонстрирует:
|
||||
|
||||
* Парсинг исходников C++ с помощью `libclang` для поиска аннотированных классов и методов.
|
||||
* Автоматическую генерацию файлов:
|
||||
* `*.proxy.h/cpp` — клиентский прокси для вызова удалённых методов.
|
||||
* `*.proxy.h/cpp` — клиентский прокси для вызова удалённых методов (C++).
|
||||
* `*.skeleton.h/cpp` — серверный скелет для приёма запросов и вызова реальных методов.
|
||||
* `*Client_c.h/cpp` — C-биндинги для клиентской стороны (автоматически генерируются для каждого `RPC_EXPORT` класса).
|
||||
* Минимальный протокол передачи данных через **именованные каналы (FIFO)**.
|
||||
* Поддержка только типов `int` для аргументов и возвращаемого значения (PoC).
|
||||
|
||||
@@ -33,19 +34,25 @@ project/
|
||||
│ ├── rpc_export.h
|
||||
│ ├── RpcRegistry.h # реестр RPC-объектов (скелетонов)
|
||||
│ ├── RpcInvoker.h # тонкий фасад над RpcRegistry
|
||||
│ └── RpcValue.h
|
||||
│ ├── RpcValue.h
|
||||
│ ├── rpc_client_c.h # базовый C API для RPC клиента
|
||||
│ └── rpc_client_c_impl.h # внутренний заголовок для C++ (не для C)
|
||||
├── src/
|
||||
│ ├── client.cpp
|
||||
│ ├── client.cpp # C++ клиент
|
||||
│ ├── client_c.c # C клиент
|
||||
│ ├── MyService.cpp
|
||||
│ ├── MyService.h
|
||||
│ └── server.cpp
|
||||
│ ├── server.cpp
|
||||
│ └── rpc_client_c.cpp # реализация базовой C-обёртки
|
||||
├── tools/
|
||||
│ ├── generate_rpc.py
|
||||
│ └── templates/
|
||||
│ ├── proxy.cpp.j2
|
||||
│ ├── proxy.h.j2
|
||||
│ ├── skeleton.cpp.j2
|
||||
│ └── skeleton.h.j2
|
||||
│ ├── skeleton.h.j2
|
||||
│ ├── client_c.h.j2 # шаблон для C заголовка
|
||||
│ └── client_c.cpp.j2 # шаблон для C реализации
|
||||
└── build/ # создаётся при сборке
|
||||
```
|
||||
|
||||
@@ -73,7 +80,7 @@ project/
|
||||
* `RpcInvoker` — тонкий фасад: по `ObjectId` берёт объект из `RpcRegistry` и зовёт `obj->invoke(method, args)`.
|
||||
|
||||
* **Сгенерированные обёртки**:
|
||||
* `*.proxy.*` — шаблонные классы, зависящие только от абстрактного `impl` с методом
|
||||
* `*.proxy.*` — шаблонные классы (C++), зависящие только от абстрактного `impl` с методом
|
||||
`impl.callTyped<Ret>(method, args...)` и не знающие про конкретный транспорт.
|
||||
В PoC роль `impl` выполняет `IpcMarshaller`, которому при создании передаётся `ObjectId`.
|
||||
Для разных удалённых объектов создаются разные экземпляры маршаллера (с разными `ObjectId`).
|
||||
@@ -81,6 +88,11 @@ project/
|
||||
* внутри держат ссылку/указатель на реальный объект (`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`,
|
||||
@@ -91,7 +103,7 @@ project/
|
||||
## Зависимости
|
||||
|
||||
* CMake ≥ 3.10
|
||||
* GCC или Clang с поддержкой C++17
|
||||
* GCC или Clang с поддержкой C++17 и C11
|
||||
* Python 3.8+ с пакетами:
|
||||
|
||||
```bash
|
||||
@@ -134,7 +146,7 @@ public:
|
||||
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build
|
||||
```
|
||||
|
||||
Директива `CMAKE_EXPORT_COMPILE_COMMANDS=ON` нужна для корректного парсинга в `libclang`.
|
||||
Директива `CMAKE_EXPORT_COMPILE_COMMANDS=ON` нужна для корректного парсинга в `libclang`. Впрочем, в CMakeLists.txt эта директива уже должна быть включена.
|
||||
|
||||
2. Собираем проект:
|
||||
|
||||
@@ -142,11 +154,13 @@ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
В результате получаем два бинарника:
|
||||
В результате получаем бинарники:
|
||||
|
||||
```text
|
||||
./build/server
|
||||
./build/client
|
||||
./build/server # сервер
|
||||
./build/client # C++ клиент
|
||||
./build/client_c # C клиент
|
||||
./build/librpc_client_c.a # статическая библиотека C-биндингов
|
||||
```
|
||||
|
||||
---
|
||||
@@ -174,9 +188,13 @@ build/generated/
|
||||
├─ MyService.proxy.h
|
||||
├─ MyService.proxy.cpp
|
||||
├─ 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-биндинги будут созданы автоматически при сборке.
|
||||
|
||||
---
|
||||
|
||||
## Запуск
|
||||
@@ -192,17 +210,41 @@ build/generated/
|
||||
На стороне сервера `RpcRegistry` регистрирует два объекта `MyService`, обёрнутых в `MyServiceSkeleton`,
|
||||
под `ObjectId = 0` и `ObjectId = 1`.
|
||||
|
||||
2. В другом терминале — клиент:
|
||||
2. В другом терминале — клиент (C++ или C):
|
||||
|
||||
**C++ клиент:**
|
||||
```bash
|
||||
./build/client
|
||||
```
|
||||
|
||||
Клиент также открывает эти FIFO, но логически пишет в `fifo_to_server` и читает из `fifo_to_client`
|
||||
(через тот же `IpcPipeChannel`), а пользовательский код видит только два прокси `MyServiceProxy`,
|
||||
привязанных к разным `ObjectId`.
|
||||
**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
|
||||
$ ./server &
|
||||
@@ -210,12 +252,50 @@ $ ./client
|
||||
OBJ1: 16 OBJ2: 16
|
||||
$ ./client
|
||||
OBJ1: 32 OBJ2: 48
|
||||
$ ./client
|
||||
$ ./client_c
|
||||
OBJ1: 48 OBJ2: 96
|
||||
$ ./client
|
||||
$ ./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-биндинги генерируются автоматически при сборке, без необходимости изменять код генератора.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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"
|
||||
@@ -262,6 +262,8 @@ def main():
|
||||
proxy_cpp = env.get_template("proxy.cpp.j2")
|
||||
skeleton_h = env.get_template("skeleton.h.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
|
||||
|
||||
@@ -277,6 +279,12 @@ def main():
|
||||
with open(f"{out_dir}/{base}.skeleton.cpp", "w") as f:
|
||||
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)
|
||||
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
|
||||
Reference in New Issue
Block a user