[c_api] added c api for libs

changed project structure, now relying on shared libraries
This commit is contained in:
amukhamadiev 2026-02-27 22:03:09 +03:00
parent b28c1b8e7f
commit e0ac93c657
15 changed files with 351 additions and 17 deletions

View File

@ -2,7 +2,7 @@
#include <iostream>
#include <string>
#include "export.h"
namespace cloud_point_rpc {
/**
@ -14,7 +14,7 @@ namespace cloud_point_rpc {
* @param port Server Port
* @return int exit code
*/
int run_cli(std::istream &input, std::ostream &output, const std::string &ip,
int CRPC_EXPORT run_cli(std::istream &input, std::ostream &output, const std::string &ip,
int port);
} // namespace cloud_point_rpc

View File

@ -6,7 +6,6 @@
#include <jsonrpccxx/client.hpp>
#include <nlohmann/json.hpp>
#include <vector>
namespace cloud_point_rpc {
class RpcClient : public jsonrpccxx::JsonRpcClient {

View File

@ -5,14 +5,27 @@
#include <map>
#include <nlohmann/json.hpp>
#include <string>
#include "export.h"
extern "C" {
struct rpc_string {
rpc_string(const char* data, uint64_t size) : s(data,size) {}
rpc_string() = default;
std::string s;
};
}
namespace cloud_point_rpc {
class RpcServer {
class CRPC_EXPORT RpcServer {
public:
using Handler = std::function<nlohmann::json(const nlohmann::json &)>;
using callback_t = rpc_string*(*)(rpc_string*);
void register_method(const std::string &name, Handler handler);
void register_method(const std::string &name, callback_t handler);
///@param request_str - json rpc 2.0 formatted string
[[nodiscard]] std::string process(const std::string &request_str);
private:

View File

@ -2,10 +2,10 @@
#include "cloud_point_rpc/config.hpp"
#include <vector>
#include "export.h"
namespace cloud_point_rpc {
class Service {
class CRPC_EXPORT Service {
public:
explicit Service(const TestData &data = {});

View File

@ -5,7 +5,7 @@
#include <cloud_point_rpc/tcp_read.hpp>
#include <glog/logging.h>
#include <string>
#include "export.h"
namespace cloud_point_rpc {
/**
* TCPConnector main purpose is to implement jsonrpccxx::IClientConnector Send
@ -13,7 +13,7 @@ namespace cloud_point_rpc {
* the json "expected size" of a packet in case when the packet was for some
* reason on any level fractured.
*/
class TCPConnector : public jsonrpccxx::IClientConnector {
class CRPC_EXPORT TCPConnector : public jsonrpccxx::IClientConnector {
public:
TCPConnector(const std::string &ip, size_t port) noexcept(false)
: io_context_(), socket_(io_context_) {

View File

@ -7,10 +7,10 @@
#include <glog/logging.h>
#include <string>
#include <thread>
#include "export.h"
namespace cloud_point_rpc {
class TcpServer {
class CRPC_EXPORT TcpServer {
public:
using RequestProcessor = std::function<std::string(const std::string &)>;

14
include/export.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#if defined(_WIN32)
#ifdef CRPC_SERVER_API_EXPORT
#define CRPC_EXPORT __declspec(dllexport)
#else
#define CRPC_EXPORT __declspec(dllimport)
#endif
#elif defined(__GNUC__) // GCC, Clang, etc.
// Linux, macOS, etc.
#define CRPC_EXPORT __attribute__((visibility("default")))
#else
#define CRPC_EXPORT
#pragma warning Unknown dynamic link import/export semantics.
#endif

38
include/server_api.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef CRPC_SERVER_API
#define CRPC_SERVER_API
#include <cstdint>
#include "export.h"
#ifdef __cplusplus
extern "C" {
#endif //cpp
/**
* @brief basically just std::string wrapper:
* struct rpc_string {
* std::string s;
* };
* has internal gc and would be automatically deallocated on deinit call
* but it is better to call destroy manually, to prevent exceeding memory usage
*/
struct CRPC_EXPORT rpc_string;
CRPC_EXPORT const char* crpc_str_get_data(const rpc_string*);
CRPC_EXPORT uint64_t crpc_str_get_size(const rpc_string*);
CRPC_EXPORT rpc_string* crpc_str_create(const char* data, uint64_t size);
CRPC_EXPORT void crpc_str_destroy(rpc_string*);
typedef rpc_string*(*callback_t)(rpc_string*);
CRPC_EXPORT void crpc_init(const char* config_path);
CRPC_EXPORT void crpc_deinit();
CRPC_EXPORT void crpc_add_method(callback_t cb, rpc_string* name);
#ifdef __cplusplus
}
#endif //cpp
#endif //CRPC_SERVER_API

22
include/test_api.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef CRPC_TEST_API
#define CRPC_TEST_API
#include <cstdint>
#include "export.h"
#ifdef __cplusplus
extern "C" {
#endif //cpp
struct CRPC_EXPORT rpc_string;
typedef rpc_string*(*callback_t)(rpc_string*);
CRPC_EXPORT void crpc_test_init();
CRPC_EXPORT void crpc_test_deinit();
CRPC_EXPORT void crpc_test_add_method(callback_t cb, rpc_string* name);
#ifdef __cplusplus
}
#endif //cpp
#endif //CRPC_SERVER_API

View File

@ -1,10 +1,12 @@
add_project_arguments('-DCRPC_SERVER_API_EXPORT -pthread', language: 'cpp')
cloud_point_rpc_sources = files(
'rpc_server.cpp',
'service.cpp',
'cli.cpp'
'server_api.cpp'
)
libcloud_point_rpc = static_library('cloud_point_rpc',
libcloud_point_rpc = shared_library('cloud_point_rpc',
cloud_point_rpc_sources,
include_directories : inc,
dependencies : [json_dep, thread_dep, glog_dep, yaml_dep, asio_dep],
@ -15,14 +17,39 @@ cloud_point_rpc_dep = declare_dependency(
link_with : libcloud_point_rpc,
dependencies : [json_dep, glog_dep, yaml_dep, asio_dep])
# Test lib
libcloud_point_rpc_test = shared_library('test_cloud_point',
'test_api.cpp',
dependencies: cloud_point_rpc_dep,
install : true)
cloud_point_rpc_test_dep = declare_dependency(
include_directories: inc,
link_with: libcloud_point_rpc_test,
dependencies: [cloud_point_rpc_dep]
)
libcloud_point_rpc_cli = shared_library('libcloud_point_rpc_cli',
'cli.cpp',
include_directories : inc,
dependencies : [json_dep, thread_dep, glog_dep, yaml_dep, asio_dep, cloud_point_rpc_dep],
install : true)
cloud_point_rpc_cli_dep = declare_dependency(
include_directories: inc,
link_with: libcloud_point_rpc_cli,
dependencies: [cloud_point_rpc_dep]
)
# Client/CLI tool (legacy stdin/stdout)
executable('cloud_point_rpc_cli',
'main.cpp',
dependencies : cloud_point_rpc_dep,
['main.cpp', ],
dependencies : cloud_point_rpc_cli_dep,
install : true)
# Server executable (TCP)
executable('cloud_point_rpc_server',
'server_main.cpp',
dependencies : cloud_point_rpc_dep,
link_args : '-pthread',
install : true)

View File

@ -21,12 +21,22 @@ void RpcServer::register_method(const std::string &name, Handler handler) {
handlers_[name] = std::move(handler);
}
void RpcServer::register_method(const std::string& name, callback_t handler) {
handlers_[name] = [handler](const nlohmann::json& j) -> nlohmann::json {
rpc_string tmp;
tmp.s = j.dump();
rpc_string* res = handler(&tmp);
return {res->s};
};
}
std::string RpcServer::process(const std::string &request_str) {
LOG(INFO) << request_str;
json request;
try {
request = json::parse(request_str);
} catch (const json::parse_error &) {
LOG(ERROR) << "json parse error" << __func__;
LOG(ERROR) << "json parse error; " << __func__;
return create_error(-32700, "Parse error").dump();
}

68
src/server_api.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "cloud_point_rpc/config.hpp"
#include "cloud_point_rpc/rpc_server.hpp"
#include "cloud_point_rpc/tcp_server.hpp"
#include <glog/logging.h>
#include "server_api.h"
#include <algorithm>
#include <memory>
#include <string>
#include <list>
static std::list<std::unique_ptr<rpc_string>> gc;
cloud_point_rpc::RpcServer rpc_server;
std::unique_ptr<cloud_point_rpc::TcpServer> server = nullptr;
extern "C" {
const char* crpc_str_get_data(const rpc_string* that) {
return that->s.c_str();
}
uint64_t crpc_str_get_size(const rpc_string* that){
return that->s.size();
}
rpc_string* crpc_str_create(const char* data, uint64_t size){
gc.push_back(std::make_unique<rpc_string>(data, size));
return gc.back().get();
}
void crpc_str_destroy(rpc_string* that){
auto it = std::ranges::find(gc, that, &std::unique_ptr<rpc_string>::get);
if(it != gc.end())
gc.erase(it);
}
void crpc_init(const char* config_path) {
google::InitGoogleLogging("CloudPointRPC");
if(config_path == nullptr) {
LOG(INFO) << "config_path was not provided";
}
try {
auto config = cloud_point_rpc::ConfigLoader::load(config_path);
LOG(INFO) << "Loaded config from " << config_path;
server = std::make_unique<cloud_point_rpc::TcpServer>(config.server.ip, config.server.port,
[&](const std::string &request) {
return rpc_server.process(
request);
});
server->start();
} catch (const std::exception &e) {
LOG(ERROR) << "Fatal error: " << e.what();
}
}
void crpc_deinit() {
if(server)
server->join();
server.reset();
gc.clear();
}
void crpc_add_method(callback_t cb, rpc_string* name) {
rpc_server.register_method(name->s, cb);
}
}

97
src/test_api.cpp Normal file
View File

@ -0,0 +1,97 @@
#include "test_api.h"
#include "server_api.h"
#include "cloud_point_rpc/rpc_server.hpp"
#include <glog/logging.h>
#include <mutex>
#include <stop_token>
#include <thread>
#include <list>
#include "server_api.h"
class TestThread {
std::string make_jsonrpc(const std::string& method_name) {
return std::format(R"(
{{
"jsonrpc": "2.0",
"method": "{}",
"params": {{}},
"id": 1
}}
)", method_name);
}
public:
TestThread() = default;
~TestThread() {
join();
}
void routine() {
std::unique_lock lock(mtx);
std::stop_token stoken = thr.get_stop_token();
lock.unlock();
while(!stoken.stop_requested()) {
lock.lock();
DLOG(INFO) << methods.size() << " ; Size of methods";
if(!methods.empty()) {
auto it = methods.begin();
std::advance(it, distance % methods.size());
DLOG(INFO) << *it << " : Method at this position";
LOG(INFO) << server.process(make_jsonrpc(*it));
}
lock.unlock();
using namespace std::chrono_literals;
std::this_thread::sleep_for(100ms);
}
LOG(INFO) << "Stopped";
}
void start() {
thr = std::jthread(&TestThread::routine, this);
}
void join() {
DLOG(INFO) << "Requested thread stop";
thr.request_stop();
if(thr.joinable()) {
thr.join();
}
}
void add_method(callback_t cb, rpc_string* name) {
LOG(INFO) << "Trying to add method: " << name->s;
std::lock_guard lock(mtx);
methods.emplace_back(name->s);
server.register_method(name->s,cb);
}
void call(rpc_string* name) {
std::lock_guard lock(mtx);
LOG(INFO) << server.process(name->s);
}
private:
std::list<std::string> methods;
size_t distance;
std::jthread thr;
std::mutex mtx;
cloud_point_rpc::RpcServer server;
} test;
extern "C" {
void crpc_test_init() {
if(!google::IsGoogleLoggingInitialized())
google::InitGoogleLogging("TestRPC");
try {
test.start();
} catch (const std::exception &e) {
LOG(ERROR) << "Fatal error: " << e.what();
}
}
void crpc_test_deinit() {
test.join();
crpc_deinit();
}
void crpc_test_add_method(callback_t cb, rpc_string* name){
test.add_method(cb, name);
}
}

View File

@ -1,12 +1,13 @@
test_sources = files(
'test_rpc.cpp',
'test_integration.cpp',
'test_tcp.cpp',
'test_cli.cpp',
'test_tcp.cpp'
'test_c_api.cpp'
)
test_exe = executable('unit_tests',
test_sources,
dependencies : [cloud_point_rpc_dep, gtest_dep, gtest_main_dep, gmock_dep])
dependencies : [cloud_point_rpc_dep, cloud_point_rpc_cli_dep, cloud_point_rpc_test_dep, json_dep, gtest_dep, gtest_main_dep, gmock_dep])
test('unit_tests', test_exe)

45
tests/test_c_api.cpp Normal file
View File

@ -0,0 +1,45 @@
#include <future>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <glog/logging.h>
#include "cloud_point_rpc/rpc_server.hpp"
#include "test_api.h"
#include "server_api.h"
class TestCApi : public ::testing::Test {
protected:
void SetUp() override {
EXPECT_NO_THROW(crpc_test_init());
}
void TearDown() override {
crpc_test_deinit();
}
};
TEST_F(TestCApi, Base) {
FLAGS_logtostderr = 1;
rpc_string name;
name.s = "test";
static std::promise<bool> task;
std::future<bool> called = task.get_future();
LOG(INFO) << "Test";
crpc_test_add_method(+[](rpc_string*)-> rpc_string* {
static bool installed = false;
DLOG(INFO) << "Trying to do something";
if(!installed){
LOG(INFO) << "Trying to install";
installed = true;
task.set_value(installed);
}
DLOG(INFO) << "Go out";
return crpc_str_create("res", sizeof("res"));
}, &name);
using namespace std::chrono_literals;
called.wait_for(500ms);
EXPECT_EQ(called.valid(), true);
EXPECT_EQ(called.get(), true);
LOG(INFO) << "DONE";
}