Files
structrail-design/.trae/documents/cross-language-sdk-guide.md
2026-02-04 01:07:37 +08:00

171 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 跨语言 SDK 实现指南:数据传递与序列化
## 背景
Structrail 平台的核心目标是提供一套**语言无关**的数据结构可视化协议。这意味着我们的 SDK 需要在不同特性的编程语言中(从高动态的 Python/JS 到静态底层的 C/C++)提供一致的功能体验。
其中,`preset` 指令(一次性同步数组/容器状态)在静态类型语言(特别是 C 语言)中的实现是最大的挑战。本文档旨在论证其可行性,并为未来各语言 SDK 的实现提供参考规范。
## 核心挑战:数组的异构性
在动态语言TS, Python数组是自描述的对象包含长度和元素类型信息。
在静态托管语言Java, C#, Go数组/列表也是对象,具备反射或自省能力。
在 C/C++ 中,原生数组仅仅是内存地址(指针),且 C 语言完全丢失了长度和类型信息。
## 通用解决方案SDK 接口分层
为了抹平差异,我们建议 SDK 接口设计遵循\*\*“渐进式暴露”\*\*原则:
1. **Level 1: 智能推断接口**(针对 TS, Python, Java 等)
* 用户直接传数组对象。
* SDK 内部自动获取长度、遍历序列化。
2. **Level 2: 显式元数据接口**(针对 C++, Go, Rust 等)
* 用户传递数据指针 + 长度。
* 利用泛型/模板自动推导元素序列化逻辑。
3. **Level 3: 手动序列化接口**(针对 C 语言)
* 用户传递数据指针 + 长度 + 元素大小 + 序列化策略。
***
## 各语言实现参考
### 1. TypeScript / JavaScript (已实现)
利用语言的动态特性,直接接受 `any[]`
```typescript
// SDK
preset(data: T[]) {
// JSON.stringify 天然支持数组序列化
this.emit('preset', { data });
}
```
### 2. Java / C# / Python
利用标准库的反射或序列化能力。
**Java 示例**:
```java
// SDK
public <T> void preset(List<T> data) {
// 使用 Jackson 或 Gson 库
String json = gson.toJson(data);
this.emit("preset", json);
}
```
### 3. C++ (Modern C++)
利用模板和 STL 容器特性。
**C++ 示例**:
```cpp
// 针对 std::vector 的重载
template<typename T>
void preset(const std::vector<T>& data) {
json j = data; // 使用 nlohmann/json 库,自动支持 STL 容器
emit("preset", j);
}
// 针对原生数组的重载 (C++20 std::span 最佳,或传指针+长度)
template<typename T>
void preset(const T* arr, size_t size) {
std::vector<T> vec(arr, arr + size);
preset(vec);
}
```
### 4. C 语言 (重点攻坚)
C 语言没有泛型,没有反射,没有对象。我们需要用户手动提供元数据。
#### 方案 A: 宏魔法 (Generic Selection) - 推荐用于基础类型
利用 C11 `_Generic` 关键字模拟函数重载,提升基础类型的使用体验。
```c
// 底层 API
void _tracer_preset_int(tracer_t* t, int* arr, size_t len);
void _tracer_preset_float(tracer_t* t, float* arr, size_t len);
// 用户宏接口
#define tracer_preset(t, arr, len) _Generic((arr), \
int*: _tracer_preset_int, \
float*: _tracer_preset_float \
)(t, arr, len)
// 用户调用
int nums[] = {1, 2, 3};
tracer_preset(t, nums, 3); // 自动匹配 int 版本
```
#### 方案 B: 格式化字符串 (Printf Style) - 推荐用于复杂类型
对于结构体或未覆盖的基础类型,采用类似 `printf` 的描述符。
```c
/**
* @param elem_fmt: 元素类型描述符
* "d": int
* "f": float
* "s": string
* "{x:d,y:d}": struct Point {int x; int y}
*/
void tracer_preset_fmt(tracer_t* t, void* data, size_t len, size_t elem_size, const char* elem_fmt);
// 用户调用
struct Point pts[] = {{1,2}, {3,4}};
tracer_preset_fmt(t, pts, 2, sizeof(struct Point), "{x:d,y:d}");
```
#### 方案 C: 回调函数 (Callback) - 兜底方案
万能方案,用户自己负责把元素转成字符串。
```c
typedef void (*serializer_func)(void* elem, char* buffer);
void tracer_preset_cb(tracer_t* t, void* data, size_t len, size_t elem_size, serializer_func cb);
// 用户调用
// 1. 用户定义序列化器
void my_int_serializer(void* elem, char* buffer) {
sprintf(buffer, "%d", *(int*)elem);
}
// 2. 传递给 SDK
int nums[] = {1, 2, 3};
tracer_preset_cb(t, nums, 3, sizeof(int), my_int_serializer);
```
## 协议一致性保障
无论采用哪种语言实现,生成的 JSON 指令必须严格一致:
```json
{
"type": "ArrayTracer",
"tracer": "uuid-...",
"action": "preset",
"params": {
"data": [1, 2, 3, 4] // 或者是 [{"x":1,"y":2}, ...]
}
}
```
这意味着 C 语言 SDK 内部必须手动拼接 JSON 字符串(`[` + `elem` + `,` + `elem` + `]`)。
## 结论
1. **`preset`** **指令是可移植的**:即使在最底层的 C 语言中,通过适当的 API 封装(宏或格式化串),也能实现与高级语言近似的开发体验。
2. **不要为了 C 语言阉割协议**:不需要因为 C 语言处理数组麻烦,就放弃 `preset` 而强迫所有语言都用 `patch` 循环。SDK 应该把复杂性封装在内部,留给用户简洁的接口。