Compare commits
10 Commits
7ce411b643
...
40170d553d
| Author | SHA1 | Date | |
|---|---|---|---|
| 40170d553d | |||
| 9f48a74677 | |||
| 785fca07f1 | |||
| 4433568545 | |||
| 7495f540d2 | |||
| c6fed622ee | |||
| 66c4e5190b | |||
| 0712460db0 | |||
| 99ea8d1646 | |||
| 1f16ef6b37 |
+43
-26
@@ -6,6 +6,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# include source includes
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
# find python interpreter
|
||||
find_package(Python3 COMPONENTS Interpreter REQUIRED)
|
||||
@@ -16,38 +17,54 @@ file(MAKE_DIRECTORY ${GENERATED_DIR})
|
||||
set(RPC_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_rpc.py)
|
||||
set(RPC_TEMPLATES ${CMAKE_CURRENT_SOURCE_DIR}/tools/templates)
|
||||
|
||||
# inputs to parse
|
||||
set(RPC_INPUTS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/MyService.h
|
||||
)
|
||||
# helper to generate RPC code for an annotated header/source pair
|
||||
function(autocode_annotated_file OUT_BASENAME HEADER SOURCE)
|
||||
set(out_proxy_h "${GENERATED_DIR}/${OUT_BASENAME}.proxy.h")
|
||||
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")
|
||||
|
||||
# command to run generator
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${GENERATED_DIR}/MyService.proxy.h
|
||||
${GENERATED_DIR}/MyService.proxy.cpp
|
||||
${GENERATED_DIR}/MyService.skeleton.h
|
||||
${GENERATED_DIR}/MyService.skeleton.cpp
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Generating RPC stubs..."
|
||||
COMMAND ${Python3_EXECUTABLE} ${RPC_GENERATOR} --out-dir ${GENERATED_DIR}
|
||||
--compile-commands ${CMAKE_BINARY_DIR}/compile_commands.json
|
||||
--templates ${RPC_TEMPLATES}
|
||||
${RPC_INPUTS}
|
||||
DEPENDS ${RPC_GENERATOR} ${RPC_TEMPLATES} ${RPC_INPUTS}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Running RPC code generator"
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${out_proxy_h}
|
||||
${out_proxy_cpp}
|
||||
${out_skeleton_h}
|
||||
${out_skeleton_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
|
||||
--templates ${RPC_TEMPLATES}
|
||||
--header ${HEADER}
|
||||
--source ${SOURCE}
|
||||
--out-base ${OUT_BASENAME}
|
||||
DEPENDS ${RPC_GENERATOR} ${RPC_TEMPLATES} ${HEADER} ${SOURCE}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Running RPC code generator for ${OUT_BASENAME}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# accumulate all generated files into a global property so we can
|
||||
# have one umbrella target that depends on all of them
|
||||
set_property(GLOBAL APPEND PROPERTY AUTOCODE_GENERATED_FILES
|
||||
${out_proxy_h}
|
||||
${out_proxy_cpp}
|
||||
${out_skeleton_h}
|
||||
${out_skeleton_cpp}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
include_directories(${GENERATED_DIR})
|
||||
|
||||
add_custom_target(rpc_generated DEPENDS
|
||||
${GENERATED_DIR}/MyService.proxy.h
|
||||
${GENERATED_DIR}/MyService.proxy.cpp
|
||||
${GENERATED_DIR}/MyService.skeleton.h
|
||||
${GENERATED_DIR}/MyService.skeleton.cpp
|
||||
# declare all annotated files here
|
||||
autocode_annotated_file(MyService
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/MyService.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/MyService.cpp
|
||||
)
|
||||
|
||||
# umbrella target that depends on all generated RPC files
|
||||
get_property(_all_generated GLOBAL PROPERTY AUTOCODE_GENERATED_FILES)
|
||||
add_custom_target(rpc_generated DEPENDS ${_all_generated})
|
||||
|
||||
# Server
|
||||
add_executable(server
|
||||
src/server.cpp
|
||||
|
||||
@@ -21,9 +21,19 @@ project/
|
||||
├── README.md
|
||||
├── src
|
||||
│ ├── client.cpp
|
||||
│ ├── common
|
||||
│ │ ├── ipc
|
||||
│ │ │ ├── IpcChannel.h
|
||||
│ │ │ ├── IpcMessage.h
|
||||
│ │ │ └── IpcPipeChannel.h
|
||||
│ │ ├── proxy
|
||||
│ │ │ └── ProxyMarshaller.h
|
||||
│ │ └── rpc
|
||||
│ │ ├── rpc_export.h
|
||||
│ │ ├── RpcInvoker.h
|
||||
│ │ └── RpcSerializer.h
|
||||
│ ├── MyService.cpp
|
||||
│ ├── MyService.h
|
||||
│ ├── rpc_export.h
|
||||
│ └── server.cpp
|
||||
├── tools
|
||||
│ ├── generate_rpc.py
|
||||
@@ -37,6 +47,24 @@ project/
|
||||
|
||||
---
|
||||
|
||||
## Архитектура (кратко)
|
||||
|
||||
* **Уровень IPC-сообщений**: `IpcMessage`
|
||||
* Построение: `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>();`
|
||||
* Внутри хранится строка с простыми типовыми тегами (`i` для `int`, `s` для `std::string`), что позволяет в будущем перейти на бинарный формат без изменения API.
|
||||
* **Уровень канала**: `RpcChannel` + `IpcPipeChannel`
|
||||
* `IpcChannel` — абстракция транспорта: `send(const IpcMessage&)`, `receive() -> IpcMessage`.
|
||||
* `IpcPipeChannel` — реализация поверх двух FIFO (`/tmp/fifo_to_server`, `/tmp/fifo_to_client`), которая внутри работает со строками, но наружу — только с `IpcMessage`.
|
||||
* **Уровень RPC-ядра**:
|
||||
* `ProxyMarshaller` — собирает `IpcMessage` из имени метода и аргументов, отправляет через `RpcChannel` и читает ответ.
|
||||
* `RpcInvoker` — по имени метода (первое поле сообщения) находит зарегистрированную функцию-член и вызывает её, читая аргументы через `get<T>()`.
|
||||
* **Сгенерированные обёртки**:
|
||||
* `*.proxy.*` — используют `ProxyMarshaller` и `RpcChannel`, не зависят от конкретного транспорта.
|
||||
* `*.skeleton.*` — используют `RpcInvoker` и принимают/возвращают `IpcMessage` для диспетчеризации вызовов.
|
||||
|
||||
---
|
||||
|
||||
## Зависимости
|
||||
|
||||
* CMake ≥ 3.10
|
||||
@@ -130,17 +158,21 @@ build/generated/
|
||||
1. В одном терминале запускаем сервер:
|
||||
|
||||
```bash
|
||||
./server
|
||||
./build/server
|
||||
```
|
||||
|
||||
Сервер создаёт (при необходимости) именованные пайпы `/tmp/fifo_to_server` и `/tmp/fifo_to_client`, читает запросы из `fifo_to_server` и пишет ответы в `fifo_to_client` через `IpcPipeChannel`.
|
||||
|
||||
2. В другом терминале — клиент:
|
||||
|
||||
```bash
|
||||
./client
|
||||
./build/client
|
||||
```
|
||||
|
||||
Клиент также открывает эти FIFO, но логически пишет в `fifo_to_server` и читает из `fifo_to_client` (через тот же `IpcPipeChannel`), а пользовательский код видит только вызов `MyServiceProxy`.
|
||||
|
||||
**Ожидаемый вывод:**
|
||||
|
||||
```
|
||||
```text
|
||||
RESULT: 15
|
||||
```
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "IpcMessage.h"
|
||||
|
||||
// Абстракция IPC‑канала, работающего с IpcMessage.
|
||||
// Живёт отдельно от RPC‑кода, чтобы транспорт не зависел от конкретной RPC‑реализации.
|
||||
class IpcChannel {
|
||||
public:
|
||||
virtual ~IpcChannel() = default;
|
||||
|
||||
virtual void send(const IpcMessage& msg) = 0;
|
||||
virtual IpcMessage receive() = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
// Примитивное IPC‑сообщение с API add<T>() / get<T>().
|
||||
// Под капотом пока текстовый формат с типовыми тегами, но снаружи интерфейс не завязан на std::string.
|
||||
|
||||
class IpcMessage {
|
||||
public:
|
||||
IpcMessage() = default;
|
||||
|
||||
explicit IpcMessage(const std::string& raw)
|
||||
: raw_(raw) {}
|
||||
|
||||
IpcMessage(const IpcMessage& other)
|
||||
: raw_(other.raw_) {}
|
||||
|
||||
IpcMessage& operator=(const IpcMessage& other) {
|
||||
if (this != &other) {
|
||||
raw_ = other.raw_;
|
||||
in_.clear();
|
||||
in_initialized_ = false;
|
||||
out_.str(std::string{});
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Конструирование исходящего сообщения.
|
||||
template<typename T>
|
||||
void add(const T& v);
|
||||
|
||||
// Чтение входящего сообщения по частям.
|
||||
template<typename T>
|
||||
T get();
|
||||
|
||||
const std::string& raw() const {
|
||||
return raw_;
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return raw_.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string raw_;
|
||||
std::ostringstream out_;
|
||||
std::istringstream in_;
|
||||
bool in_initialized_{false};
|
||||
|
||||
void ensureInput() {
|
||||
if (!in_initialized_) {
|
||||
in_.str(raw_);
|
||||
in_initialized_ = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ===== Специализации по типам =====
|
||||
|
||||
// int
|
||||
template<>
|
||||
inline void IpcMessage::add<int>(const int& v) {
|
||||
out_ << 'i' << ' ' << v << ' ';
|
||||
raw_ = out_.str();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline int IpcMessage::get<int>() {
|
||||
ensureInput();
|
||||
char tag{};
|
||||
in_ >> tag;
|
||||
// в PoC просто доверяем, что тип совпадает
|
||||
int v{};
|
||||
in_ >> v;
|
||||
return v;
|
||||
}
|
||||
|
||||
// std::string
|
||||
template<>
|
||||
inline void IpcMessage::add<std::string>(const std::string& v) {
|
||||
// формат: 's' <len> <bytes...>
|
||||
out_ << 's' << ' ' << v.size() << ' ';
|
||||
out_.write(v.data(), static_cast<std::streamsize>(v.size()));
|
||||
out_ << ' ';
|
||||
raw_ = out_.str();
|
||||
}
|
||||
|
||||
template<>
|
||||
inline std::string IpcMessage::get<std::string>() {
|
||||
ensureInput();
|
||||
char tag{};
|
||||
in_ >> tag;
|
||||
// ожидаем 's'
|
||||
std::size_t len{};
|
||||
in_ >> len;
|
||||
// съесть одиночный пробел перед данными
|
||||
in_.get();
|
||||
std::string res(len, '\0');
|
||||
in_.read(&res[0], static_cast<std::streamsize>(len));
|
||||
// съесть завершающий пробел
|
||||
in_.get();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "IpcChannel.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// IPC‑канал поверх именованных pipe.
|
||||
// Инкапсулирует работу с файловыми дескрипторами и обмен сообщениями IpcMessage.
|
||||
// readPipe — тот FIFO, который этот endpoint читает; writePipe — тот, в который пишет.
|
||||
|
||||
class IpcPipeChannel : public IpcChannel {
|
||||
public:
|
||||
IpcPipeChannel(const char* readPipe, const char* writePipe) {
|
||||
// Канал не создаёт FIFO, только открывает.
|
||||
// Открываем оба конца как O_RDWR, чтобы избежать блокировок на open(O_RDONLY/O_WRONLY).
|
||||
// При этом логически читаем только из readPipe, а пишем только в writePipe.
|
||||
fdIn_ = ::open(readPipe, O_RDWR);
|
||||
fdOut_ = ::open(writePipe, O_RDWR);
|
||||
}
|
||||
|
||||
~IpcPipeChannel() override {
|
||||
if (fdIn_ >= 0) {
|
||||
::close(fdIn_);
|
||||
}
|
||||
if (fdOut_ >= 0) {
|
||||
::close(fdOut_);
|
||||
}
|
||||
}
|
||||
|
||||
void send(const IpcMessage& msg) override {
|
||||
if (fdOut_ < 0) {
|
||||
return;
|
||||
}
|
||||
const std::string& data = msg.raw();
|
||||
::write(fdOut_, data.c_str(), data.size());
|
||||
::write(fdOut_, "\n", 1);
|
||||
}
|
||||
|
||||
IpcMessage receive() override {
|
||||
if (fdIn_ < 0) {
|
||||
return IpcMessage{};
|
||||
}
|
||||
char buf[4096];
|
||||
const int n = ::read(fdIn_, buf, sizeof(buf) - 1);
|
||||
if (n <= 0) {
|
||||
return IpcMessage{};
|
||||
}
|
||||
buf[n] = 0;
|
||||
return IpcMessage(std::string(buf));
|
||||
}
|
||||
|
||||
private:
|
||||
int fdIn_{-1};
|
||||
int fdOut_{-1};
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <ipc/IpcChannel.h>
|
||||
|
||||
class ProxyMarshaller {
|
||||
public:
|
||||
explicit ProxyMarshaller(IpcChannel& ch) : channel(ch) {}
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
Ret call(const std::string& method, const Args&... args) {
|
||||
IpcMessage msg;
|
||||
|
||||
// имя метода
|
||||
msg.add(method);
|
||||
|
||||
// аргументы
|
||||
(msg.add(args), ...);
|
||||
|
||||
// отправить
|
||||
channel.send(msg);
|
||||
|
||||
// получить ответ
|
||||
IpcMessage resp = channel.receive();
|
||||
return resp.template get<Ret>();
|
||||
}
|
||||
|
||||
private:
|
||||
IpcChannel& channel;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "RpcSerializer.h"
|
||||
#include <ipc/IpcMessage.h>
|
||||
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
@@ -15,26 +14,24 @@ public:
|
||||
const std::string& name,
|
||||
Ret (Obj::*method)(Args...)) {
|
||||
handlers[name] =
|
||||
[instance, method](const std::string& req) -> std::string {
|
||||
std::istringstream in(req);
|
||||
[instance, method](const IpcMessage& req) -> IpcMessage {
|
||||
IpcMessage msg = req;
|
||||
|
||||
// пропустить имя метода
|
||||
std::string skip;
|
||||
in >> skip;
|
||||
(void)msg.template get<std::string>();
|
||||
|
||||
// читать аргументы и вызвать метод
|
||||
return callMethod<Ret, Obj, Args...>(instance, method, in);
|
||||
return callMethod<Ret, Obj, Args...>(instance, method, msg);
|
||||
};
|
||||
}
|
||||
|
||||
std::string dispatch(const std::string& request) {
|
||||
std::istringstream in(request);
|
||||
std::string method;
|
||||
in >> method;
|
||||
IpcMessage dispatch(const IpcMessage& request) {
|
||||
IpcMessage tmp = request;
|
||||
const std::string method = tmp.get<std::string>();
|
||||
|
||||
auto it = handlers.find(method);
|
||||
if (it == handlers.end()) {
|
||||
return "ERR";
|
||||
return IpcMessage{};
|
||||
}
|
||||
|
||||
return it->second(request);
|
||||
@@ -42,25 +39,25 @@ public:
|
||||
|
||||
private:
|
||||
template<typename Ret, typename Obj, typename... Args>
|
||||
static std::string callMethod(Obj* obj,
|
||||
Ret (Obj::*method)(Args...),
|
||||
std::istringstream& in) {
|
||||
auto tuple = readArgs<Args...>(in);
|
||||
static IpcMessage callMethod(Obj* obj,
|
||||
Ret (Obj::*method)(Args...),
|
||||
IpcMessage& msg) {
|
||||
auto tuple = readArgs<Args...>(msg);
|
||||
Ret result =
|
||||
std::apply(method, std::tuple_cat(std::make_tuple(obj), tuple));
|
||||
|
||||
std::ostringstream out;
|
||||
RpcSerializer::write(out, result);
|
||||
return out.str();
|
||||
IpcMessage out;
|
||||
out.add(result);
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static std::tuple<Args...> readArgs(std::istringstream& in) {
|
||||
return std::tuple<Args...>{RpcSerializer::read<Args>(in)...};
|
||||
static std::tuple<Args...> readArgs(IpcMessage& msg) {
|
||||
return std::tuple<Args...>{msg.template get<Args>()...};
|
||||
}
|
||||
|
||||
std::unordered_map<std::string,
|
||||
std::function<std::string(const std::string&)>>
|
||||
std::function<IpcMessage(const IpcMessage&)>>
|
||||
handlers;
|
||||
};
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <rpc_export.h>
|
||||
#include <rpc/rpc_export.h>
|
||||
|
||||
// annotate with clang attribute or via comment annotation recognized by libclang
|
||||
// Use ANNOTATE attribute supported by clang: __attribute__((annotate("export")))
|
||||
|
||||
+14
-37
@@ -1,48 +1,25 @@
|
||||
#include "MyService.proxy.h"
|
||||
#include "rpc/RpcChannel.h"
|
||||
#include "ipc/IpcPipeChannel.h"
|
||||
#include "proxy/ProxyMarshaller.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
class RpcChannelFifoClient : public RpcChannel {
|
||||
public:
|
||||
RpcChannelFifoClient(const char* inPipe, const char* outPipe) {
|
||||
fdOut = open(inPipe, O_WRONLY);
|
||||
fdIn = open(outPipe, O_RDONLY);
|
||||
}
|
||||
|
||||
void send(const std::string& data) override {
|
||||
::write(fdOut, data.c_str(), data.size());
|
||||
::write(fdOut, "\n", 1);
|
||||
}
|
||||
|
||||
std::string receive() override {
|
||||
char buf[4096];
|
||||
int n = ::read(fdIn, buf, sizeof(buf) - 1);
|
||||
if (n <= 0) {
|
||||
return {};
|
||||
}
|
||||
buf[n] = 0;
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
private:
|
||||
int fdIn{};
|
||||
int fdOut{};
|
||||
};
|
||||
|
||||
int main() {
|
||||
mkfifo("/tmp/rpc_in", 0666);
|
||||
mkfifo("/tmp/rpc_out", 0666);
|
||||
// Создание FIFO — часть пользовательского IPC‑кода.
|
||||
mkfifo("/tmp/fifo_to_server", 0666);
|
||||
mkfifo("/tmp/fifo_to_client", 0666);
|
||||
|
||||
RpcChannelFifoClient ch("/tmp/rpc_in", "/tmp/rpc_out");
|
||||
MyServiceProxy proxy(ch);
|
||||
// IPC‑уровень: канал поверх pipe.
|
||||
// Клиент пишет в fifo, который читает сервер (fifo_to_server),
|
||||
// и читает из fifo, в который пишет сервер (fifo_to_client).
|
||||
IpcPipeChannel ch("/tmp/fifo_to_client", "/tmp/fifo_to_server");
|
||||
|
||||
int r = proxy.add(7, 8);
|
||||
// RPC‑уровень: создаём marshaller поверх канала и передаём его в прокси.
|
||||
ProxyMarshaller marshaller(ch);
|
||||
MyServiceProxy proxy(marshaller);
|
||||
|
||||
int r = proxy.add(7, 9);
|
||||
std::cout << "RESULT: " << r << std::endl;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class RpcChannel {
|
||||
public:
|
||||
virtual ~RpcChannel() = default;
|
||||
|
||||
virtual void send(const std::string& data) = 0;
|
||||
virtual std::string receive() = 0;
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
#include "RpcChannel.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
class RpcChannelFifo : public RpcChannel {
|
||||
public:
|
||||
RpcChannelFifo(const char* inPipe, const char* outPipe) {
|
||||
fdOut = open(inPipe, O_WRONLY);
|
||||
fdIn = open(outPipe, O_RDONLY);
|
||||
}
|
||||
|
||||
void send(const std::string& data) override {
|
||||
::write(fdOut, data.c_str(), data.size());
|
||||
::write(fdOut, "\n", 1);
|
||||
}
|
||||
|
||||
std::string receive() override {
|
||||
char buf[4096];
|
||||
int n = ::read(fdIn, buf, sizeof(buf) - 1);
|
||||
if (n <= 0) {
|
||||
return {};
|
||||
}
|
||||
buf[n] = 0;
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
private:
|
||||
int fdIn{};
|
||||
int fdOut{};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "RpcChannel.h"
|
||||
#include "RpcSerializer.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
class RpcClient {
|
||||
public:
|
||||
explicit RpcClient(RpcChannel& ch) : channel(ch) {}
|
||||
|
||||
template<typename Ret, typename... Args>
|
||||
Ret call(const std::string& method, const Args&... args) {
|
||||
std::ostringstream out;
|
||||
|
||||
// имя метода
|
||||
RpcSerializer::write(out, method);
|
||||
|
||||
// аргументы
|
||||
(RpcSerializer::write(out, args), ...);
|
||||
|
||||
// отправить
|
||||
channel.send(out.str());
|
||||
|
||||
// получить ответ
|
||||
std::string resp = channel.receive();
|
||||
std::istringstream in(resp);
|
||||
|
||||
return RpcSerializer::read<Ret>(in);
|
||||
}
|
||||
|
||||
private:
|
||||
RpcChannel& channel;
|
||||
};
|
||||
|
||||
|
||||
+10
-38
@@ -1,57 +1,29 @@
|
||||
#include "MyService.h"
|
||||
#include "MyService.skeleton.h"
|
||||
|
||||
#include "rpc/RpcChannel.h"
|
||||
#include "ipc/IpcPipeChannel.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
class RpcChannelFifoServer : public RpcChannel {
|
||||
public:
|
||||
RpcChannelFifoServer(const char* inPipe, const char* outPipe) {
|
||||
fdIn = open(inPipe, O_RDONLY);
|
||||
fdOut = open(outPipe, O_WRONLY);
|
||||
}
|
||||
|
||||
void send(const std::string& data) override {
|
||||
::write(fdOut, data.c_str(), data.size());
|
||||
::write(fdOut, "\n", 1);
|
||||
}
|
||||
|
||||
std::string receive() override {
|
||||
char buf[4096];
|
||||
int n = ::read(fdIn, buf, sizeof(buf) - 1);
|
||||
if (n <= 0) {
|
||||
return {};
|
||||
}
|
||||
buf[n] = 0;
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
private:
|
||||
int fdIn{};
|
||||
int fdOut{};
|
||||
};
|
||||
|
||||
int main() {
|
||||
mkfifo("/tmp/rpc_in", 0666);
|
||||
mkfifo("/tmp/rpc_out", 0666);
|
||||
// Создание FIFO — часть пользовательского IPC‑кода.
|
||||
mkfifo("/tmp/fifo_to_server", 0666);
|
||||
mkfifo("/tmp/fifo_to_client", 0666);
|
||||
|
||||
RpcChannelFifoServer ch("/tmp/rpc_in", "/tmp/rpc_out");
|
||||
// IPC‑уровень: канал поверх pipe.
|
||||
// Сервер читает из fifo_to_server и пишет в fifo_to_client.
|
||||
IpcPipeChannel ch("/tmp/fifo_to_server", "/tmp/fifo_to_client");
|
||||
|
||||
// RPC‑уровень: скелет поверх того же канала.
|
||||
MyService realObj;
|
||||
MyServiceSkeleton skeleton(realObj);
|
||||
|
||||
while (true) {
|
||||
std::string req = ch.receive();
|
||||
IpcMessage req = ch.receive();
|
||||
if (req.empty()) {
|
||||
break;
|
||||
}
|
||||
std::string resp = skeleton.dispatch(req);
|
||||
IpcMessage resp = skeleton.dispatch(req);
|
||||
ch.send(resp);
|
||||
}
|
||||
}
|
||||
|
||||
+77
-20
@@ -73,11 +73,22 @@ def load_compile_flags(compile_commands_path, src_path):
|
||||
if skip_next:
|
||||
skip_next = False
|
||||
continue
|
||||
if tok.endswith('g++') or tok.endswith('clang++') or tok.endswith('clang') or tok.endswith('g++') or tok.endswith('cc'):
|
||||
# skip the compiler binary itself
|
||||
if (
|
||||
tok.endswith("g++")
|
||||
or tok.endswith("clang++")
|
||||
or tok.endswith("clang")
|
||||
or tok.endswith("cc")
|
||||
or tok.endswith("c++")
|
||||
):
|
||||
continue
|
||||
if tok == "-c":
|
||||
skip_next = True
|
||||
continue
|
||||
# drop output file flag: -o <file>
|
||||
if tok == "-o":
|
||||
skip_next = True
|
||||
continue
|
||||
filtered.append(tok)
|
||||
return filtered
|
||||
return []
|
||||
@@ -183,7 +194,13 @@ def main():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--out-dir", "-o", required=True)
|
||||
p.add_argument("--compile-commands", "-c", default=None)
|
||||
p.add_argument("inputs", nargs="+")
|
||||
p.add_argument("--header", required=True, help="C++ header file to scan for exported RPC classes")
|
||||
p.add_argument("--source", required=True, help="C++ source file to use for resolving compile flags (from compile_commands.json)")
|
||||
p.add_argument(
|
||||
"--out-base",
|
||||
required=True,
|
||||
help="Base name for generated files: <out-base>.proxy.[h|cpp], <out-base>.skeleton.[h|cpp]",
|
||||
)
|
||||
p.add_argument("--templates", "-t", default=os.path.join(os.path.dirname(__file__), "templates"))
|
||||
args = p.parse_args()
|
||||
|
||||
@@ -199,28 +216,68 @@ def main():
|
||||
except Exception as e:
|
||||
print("WARNING: cannot set libclang path:", e)
|
||||
|
||||
# determine compile flags from the source file (which is what appears in compile_commands.json)
|
||||
if not os.path.exists(args.header):
|
||||
print("ERROR: header file not found:", args.header)
|
||||
return 1
|
||||
if not os.path.exists(args.source):
|
||||
print("ERROR: source file not found:", args.source)
|
||||
return 1
|
||||
|
||||
compile_args = load_compile_flags(args.compile_commands, args.source)
|
||||
if not any(a.startswith("-x") for a in compile_args):
|
||||
compile_args = ["-x", "c++", "-std=c++17"] + compile_args
|
||||
|
||||
print("Parsing", args.header, "with args:", compile_args)
|
||||
|
||||
index = Index.create()
|
||||
all_classes = []
|
||||
classes = parse_file(index, args.header, compile_args)
|
||||
|
||||
for inp in args.inputs:
|
||||
if not os.path.exists(inp):
|
||||
print("WARN: input file not found:", inp)
|
||||
continue
|
||||
compile_args = load_compile_flags(args.compile_commands, inp)
|
||||
# ensure -x c++ if missing
|
||||
if not any(a.startswith("-x") for a in compile_args):
|
||||
compile_args = ["-x", "c++", "-std=c++17"] + compile_args
|
||||
print("Parsing", inp, "with args:", compile_args)
|
||||
classes = parse_file(index, inp, compile_args)
|
||||
all_classes.extend(classes)
|
||||
|
||||
if not all_classes:
|
||||
print("No exported classes/methods found. Nothing to generate.")
|
||||
if not classes:
|
||||
print("No exported classes/methods found in header. Nothing to generate.")
|
||||
return 0
|
||||
|
||||
# render templates
|
||||
render_templates(all_classes, out_dir, args.templates)
|
||||
print("Generated files for classes:", ", ".join(c.name for c in all_classes))
|
||||
# For this PoC we expect a single exported service per header.
|
||||
# If there are multiple, refuse to guess which one should define the filenames.
|
||||
if len(classes) > 1:
|
||||
print(
|
||||
"ERROR: multiple exported classes found in header; "
|
||||
"current generator expects exactly one when using --out-base."
|
||||
)
|
||||
for c in classes:
|
||||
print(" -", c.name)
|
||||
return 1
|
||||
|
||||
# render templates using the single discovered class but the user‑provided base name
|
||||
cls = classes[0]
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(args.templates),
|
||||
autoescape=False,
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
)
|
||||
|
||||
proxy_h = env.get_template("proxy.h.j2")
|
||||
proxy_cpp = env.get_template("proxy.cpp.j2")
|
||||
skeleton_h = env.get_template("skeleton.h.j2")
|
||||
skeleton_cpp = env.get_template("skeleton.cpp.j2")
|
||||
|
||||
base = args.out_base
|
||||
|
||||
with open(f"{out_dir}/{base}.proxy.h", "w") as f:
|
||||
f.write(proxy_h.render(cls=cls))
|
||||
|
||||
with open(f"{out_dir}/{base}.proxy.cpp", "w") as f:
|
||||
f.write(proxy_cpp.render(cls=cls))
|
||||
|
||||
with open(f"{out_dir}/{base}.skeleton.h", "w") as f:
|
||||
f.write(skeleton_h.render(cls=cls))
|
||||
|
||||
with open(f"{out_dir}/{base}.skeleton.cpp", "w") as f:
|
||||
f.write(skeleton_cpp.render(cls=cls))
|
||||
|
||||
print("Generated files for class", cls.name, "into base", base)
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
#include "{{ cls.name }}.proxy.h"
|
||||
#include "rpc/RpcClient.h"
|
||||
|
||||
class {{ cls.name }}Proxy::Impl {
|
||||
public:
|
||||
explicit Impl(RpcChannel& ch)
|
||||
: client(ch) {}
|
||||
|
||||
RpcClient client;
|
||||
};
|
||||
|
||||
{{ cls.name }}Proxy::{{ cls.name }}Proxy(RpcChannel& ch)
|
||||
: impl(new Impl(ch)) {}
|
||||
{{ cls.name }}Proxy::{{ cls.name }}Proxy(ProxyMarshaller& marshaller)
|
||||
: impl(marshaller) {}
|
||||
|
||||
{% for m in cls.methods %}
|
||||
{{ m.return_type }} {{ cls.name }}Proxy::{{ m.name }}({% for a in m.args %}{{ a.type }} {{ a.name }}{% if not loop.last %}, {% endif %}{% endfor %}) {
|
||||
return impl->client.call<{{ m.return_type }}>("{{ cls.name }}.{{ m.name }}"{% for a in m.args %}, {{ a.name }}{% endfor %});
|
||||
return impl.call<{{ m.return_type }}>("{{ cls.name }}.{{ m.name }}"{% for a in m.args %}, {{ a.name }}{% endfor %});
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "rpc/RpcChannel.h"
|
||||
#include "proxy/ProxyMarshaller.h"
|
||||
|
||||
class {{ cls.name }}Proxy {
|
||||
public:
|
||||
explicit {{ cls.name }}Proxy(RpcChannel& ch);
|
||||
explicit {{ cls.name }}Proxy(ProxyMarshaller& marshaller);
|
||||
{% for m in cls.methods %}
|
||||
{{ m.return_type }} {{ m.name }}({% for a in m.args %}{{ a.type }} {{ a.name }}{% if not loop.last %}, {% endif %}{% endfor %});
|
||||
{% endfor %}
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl* impl;
|
||||
ProxyMarshaller& impl;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
std::string {{ cls.name }}Skeleton::dispatch(const std::string& req) {
|
||||
IpcMessage {{ cls.name }}Skeleton::dispatch(const IpcMessage& req) {
|
||||
return invoker.dispatch(req);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
#include "{{ cls.name }}.h"
|
||||
#include "rpc/RpcInvoker.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class {{ cls.name }}Skeleton {
|
||||
public:
|
||||
explicit {{ cls.name }}Skeleton({{ cls.name }}& obj);
|
||||
|
||||
std::string dispatch(const std::string& req);
|
||||
IpcMessage dispatch(const IpcMessage& req);
|
||||
|
||||
private:
|
||||
RpcInvoker invoker;
|
||||
|
||||
Reference in New Issue
Block a user