- Add scheduled OpenWiki regeneration and pull-request workflow - Refresh generated wiki metadata, navigation, and source documentation - Add Doxygen configuration and ignore generated documentation output - Publish OpenWiki guidance for Codex and Claude agents TG-3 #ready-for-test
136 lines
7.0 KiB
Markdown
136 lines
7.0 KiB
Markdown
---
|
|
type: API
|
|
title: C API for Unity Integration
|
|
description: C API surface for embedding the RPC server in Unity or other native consumers — lifecycle functions, rpc_string memory management, method registration, and the test API for handler testing.
|
|
tags: [c-api, unity, server-api, test-api, rpc_string, integration]
|
|
---
|
|
|
|
# C API for Unity Integration
|
|
|
|
The C API allows Unity (or any C/C++ consumer) to embed the RPC server as a shared library, register custom RPC handlers as C function pointers, and manage the server lifecycle without touching C++ directly.
|
|
|
|
> **Unity C# design spec**: For the complete Unity-side implementation guide — including `rpc_string` ownership rules, IL2CPP callback pinning, main-thread dispatcher design, and shutdown ordering — see [docs/unity-integration.md](../docs/unity-integration.md). The C API described here is the C++ side of the interface; the C# side is the Unity project's responsibility.
|
|
|
|
Two C API surfaces exist:
|
|
|
|
| API | Header | Library | Purpose |
|
|
|---|---|---|---|
|
|
| Server API | `include/server_api.h` | `libcloud_point_rpc` | Start/stop the TCP server, register RPC methods |
|
|
| Test API | `include/test_api.h` | `test_cloud_point` | Internal test harness: register methods, schedule calls, auto-call loop |
|
|
|
|
Both APIs use the `rpc_string` type and `callback_t` function pointer typedef.
|
|
|
|
## Export macros
|
|
|
|
`include/export.h` defines `CRPC_EXPORT`. On Windows, it resolves to `__declspec(dllexport)` when `CRPC_SERVER_API_EXPORT` is defined (set in `src/meson.build`) and `__declspec(dllimport)` otherwise. On GCC/Clang, it uses `__attribute__((visibility("default")))`. This allows the same headers to be used when building the library and when consuming it.
|
|
|
|
## `rpc_string`
|
|
|
|
Defined in `include/cloud_point_rpc/rpc_server.hpp` inside an `extern "C"` block:
|
|
|
|
```c
|
|
struct rpc_string {
|
|
std::string s; // C++ std::string, but the struct is C-ABI compatible
|
|
};
|
|
```
|
|
|
|
Although the struct contains a `std::string`, it is allocated and managed by the library. Consumers interact with it through opaque pointers and accessor functions:
|
|
|
|
| Function | Description |
|
|
|---|---|
|
|
| `crpc_str_create(data, size)` | Allocate a new `rpc_string` with the given data. Tracked by internal GC. |
|
|
| `crpc_str_destroy(ptr)` | Manually free a `rpc_string`. |
|
|
| `crpc_str_get_data(ptr)` | Get the raw C string pointer. |
|
|
| `crpc_str_get_size(ptr)` | Get the string length. |
|
|
|
|
### Garbage collector
|
|
|
|
`src/server_api.cpp` maintains a static `std::list<std::unique_ptr<rpc_string>> gc` protected by `gc_mtx`. All `crpc_str_create` allocations are tracked in this list. `crpc_deinit()` clears the entire list. `crpc_str_destroy` removes a specific entry. This prevents memory leaks if Unity forgets to call `destroy`, though manual destruction is recommended to avoid excessive memory usage.
|
|
|
|
> **Important**: `rpc_string` pointers returned from callbacks are owned by the library's GC. Do not `free()` them — use `crpc_str_destroy()`.
|
|
|
|
## Server API (`server_api.h`)
|
|
|
|
### Lifecycle
|
|
|
|
```c
|
|
// Initialize and start the server from a config file
|
|
crpc_init("config.yaml");
|
|
|
|
// Or initialize with an explicit address (no config file needed)
|
|
crpc_init_with_address("127.0.0.1", 9095);
|
|
|
|
// ... register methods and serve ...
|
|
|
|
// Stop server and free all GC-tracked rpc_strings
|
|
crpc_deinit();
|
|
```
|
|
|
|
- `crpc_init(config_path)` — Loads YAML config via `ConfigLoader`, creates a `TcpServer` with the configured IP/port, and starts it. Initializes Google Logging if not already initialized. See [Architecture → Config](architecture.md#config).
|
|
- `crpc_init_with_address(ip, port)` — Same but without a config file. Used when the consumer wants to set the address directly.
|
|
- `crpc_deinit()` — Stops the server (resets the `TcpServer` unique_ptr) and clears the `rpc_string` GC list.
|
|
|
|
### Registering methods
|
|
|
|
```c
|
|
rpc_string* my_handler(rpc_string* params_json) {
|
|
// params_json->s contains the JSON params as a string
|
|
// Build your result (JSON or raw string)
|
|
return crpc_str_create("{\"key\":\"value\"}", 15);
|
|
}
|
|
|
|
rpc_string method_name;
|
|
method_name.s = "my-method";
|
|
crpc_add_method(my_handler, &method_name);
|
|
```
|
|
|
|
- `callback_t` is `rpc_string* (*)(rpc_string*)` — a C function pointer.
|
|
- The callback receives the JSON `params` object serialized as a string in `rpc_string->s`.
|
|
- The callback returns a `rpc_string*` whose string is parsed as JSON if possible, or used as a raw string in the `result` field. See [RPC Protocol → Handler registration](rpc-protocol.md#handler-registration).
|
|
- `crpc_add_method` is guarded by `server_mtx` and registers the callback on the global `RpcServer`.
|
|
|
|
### Global state
|
|
|
|
`server_api.cpp` uses file-level statics: `rpc_server` (the global `RpcServer`), `server` (the `TcpServer` unique_ptr), `gc` (the string GC list), and two mutexes (`gc_mtx`, `server_mtx`). This means only one server instance is supported per process.
|
|
|
|
## Test API (`test_api.h`)
|
|
|
|
The test API is built into a separate shared library (`test_cloud_point`) and provides a `TestThread` class (in `src/test_api.cpp`) that runs a background `std::jthread` for testing registered methods without a real TCP connection.
|
|
|
|
### Lifecycle
|
|
|
|
```c
|
|
crpc_test_init(); // Start the test thread + Google Logging
|
|
// ... register methods, schedule calls ...
|
|
crpc_test_deinit(); // Stop thread, call crpc_deinit(), reset state
|
|
```
|
|
|
|
### Methods
|
|
|
|
| Function | Description |
|
|
|---|---|
|
|
| `crpc_test_add_method(cb, name)` | Register a method on the test `RpcServer`. Duplicates are ignored. |
|
|
| `crpc_test_remove_method(name)` | Remove a registered method. Returns 0 on success, -1 if not found. |
|
|
| `crpc_test_schedule_call(name)` | Enqueue a one-shot call to the named method (processed by the test thread). |
|
|
| `crpc_test_change_duration(ms)` | Set the auto-call sleep interval (default 50ms). |
|
|
| `crpc_test_duration()` | Get the current sleep interval. |
|
|
| `crpc_test_auto_call(state)` | Enable (1) or disable (0) auto-calling registered methods on each sleep cycle. |
|
|
|
|
### Test thread behavior
|
|
|
|
The `TestThread::routine()` loop:
|
|
1. If there are queued one-shot calls, process them (build a JSON-RPC request and call `server.process()`).
|
|
2. If auto-call is enabled and methods exist, call the next method in round-robin order.
|
|
3. If auto-call is enabled and the queue is empty, wait on a condition variable for the configured duration (or until stop is requested).
|
|
4. Stop when `jthread` stop is requested via `crpc_test_deinit()`.
|
|
|
|
> **Note**: The test API does **not** start a TCP server. It processes JSON-RPC requests directly through `RpcServer::process()`, logging results. It is designed for testing handler registration and callback behavior in C, as demonstrated in `tests/test_c_api.cpp` and `tests/test_c_api_edge_cases.cpp`.
|
|
|
|
## Source references
|
|
|
|
- `include/server_api.h` — Server C API declarations
|
|
- `src/server_api.cpp` — Server C API implementation, `rpc_string` GC
|
|
- `include/test_api.h` — Test C API declarations
|
|
- `src/test_api.cpp` — `TestThread` implementation and test C API
|
|
- `include/export.h` — `CRPC_EXPORT` macro
|
|
- `include/cloud_point_rpc/rpc_server.hpp` — `rpc_string` struct and `callback_t` typedef |