概述
VLink C API 提供了一套稳定的、语言无关的纯 C 绑定,封装了 VLink C++ 核心库的三种通信模型。通过 C API,可以在 C 语言、Python、Go、Rust 等不支持直接调用 C++ 模板的语言中使用 VLink 的六个通信原语(Publisher/Subscriber、Client/Server、Setter/Getter)的**数据面**能力。
覆盖范围**:C API 只暴露六个原语的数据面接口(创建/销毁、发布/订阅、请求/响应、读写字段);**不覆盖 QoS、Security、Bag 录制、Discovery 等高级功能,这些仍须通过 C++ API 使用。数据载荷类型统一为 Bytes,序列化类型通过 vlink_schema_info_t 的 ser + schema 传入。
**相关文档**:C++ 通信模型参见 03-event-model.md、04-method-model.md、05-field-model.md;传输 URL 格式参见 07-transport.md;C API 示例参见 22-examples.md。
设计思路
为什么提供 C API
| 需求 | 说明 |
| 语言互操作(FFI) | Python ctypes / Go cgo / Rust bindgen 均通过 C ABI 调用动态库 |
| ABI 稳定性 | C++ 模板实例化依赖编译器细节,C 函数符号稳定且跨编译器兼容 |
| 嵌入式/实时系统集成 | 部分 RTOS 环境只支持 C 链接,无 C++ 运行时 |
| 遗留代码集成 | 现有 C 项目可直接链接 VLink,无需改写为 C++ |
| 动态加载 | dlopen / LoadLibrary 加载 VLink 共享库并按名称查找函数 |
设计原则
- **句柄(Handle)模式**:每种节点类型对应一个不透明的 C 结构体,内含 native_handle 指向堆分配的 C++ 对象
- **统一返回值**:所有函数返回 vlink_ret_t 整数,0 为成功,非 0 为错误或条件码
- **回调驱动**:订阅/服务器请求/连接状态变化均通过函数指针回调通知
- **内存所有权明确**:create 与 destroy 成对调用,destroy 负责释放所有内部资源
编译选项
C API 以独立库 vlink-c_api 的形式提供,需单独链接。
CMake 集成
# 查找并链接 C API 库
find_package(vlink REQUIRED)
target_link_libraries(my_c_app PRIVATE vlink::c_api)
编译宏说明
| 宏 | 作用 |
| VLINK_C_API_LIBRARY | 构建动态库时由库自动定义(启用 dllexport) |
| VLINK_C_API_LIBRARY_STATIC | 构建静态库时由库自动定义,使用方也需定义 |
| VLINK_C_API_EXPORT | 函数可见性修饰符,自动根据平台和构建类型展开 |
| VLINK_ENABLE_C_INTERFACE | 库内部编译标志,表明正在构建 C API 模块 |
头文件包含
Pure C API for the VLink communication middleware.
该头文件是纯 C 头文件,使用 #ifdef __cplusplus extern "C" 保护,可在 C 和 C++ 代码中同时使用。
数据类型说明
返回值类型 vlink_ret_t
typedef enum {
vlink_ret_t
Return code for all VLink C API functions.
Definition c_api.h:147
@ VLINK_RET_UNKNOWN_ERROR
Definition c_api.h:148
@ VLINK_RET_NO_ERROR
Definition c_api.h:149
@ VLINK_RET_RUNTIME_ERROR
Definition c_api.h:153
@ VLINK_RET_INVALID_ERROR
Definition c_api.h:151
@ VLINK_RET_MEMORY_ERROR
Definition c_api.h:152
@ VLINK_RET_TRANSFER_ERROR
Definition c_api.h:154
@ VLINK_RET_UNEXPECTED_ERROR
Definition c_api.h:150
句柄类型
所有句柄类型均为 C 结构体,包含一个 void* native_handle 和一个 void* reserved[4] 数组:
typedef struct {
void* native_handle;
void* reserved[4];
Opaque handle for a Publisher node.
Definition c_api.h:197
vlink_server_handle_t 的 reserved 数组有特殊用途:
| 字段 | 用途 |
| reserved[0] | 指向 std::mutex,在请求回调期间持有锁 |
| reserved[1] | 指向响应字节缓冲区(由 vlink_reply 分配) |
| reserved[2] | 响应字节数(存储为指针大小的整数) |
| reserved[3] | 非空表示请求处理进行中(由 vlink_reply 清零) |
回调类型
void(* vlink_connect_callback_t)(const bool is_connected, void *user_data)
Callback invoked when the connection state of a Publisher or Client changes.
Definition c_api.h:280
void(* vlink_req_callback_t)(const uint8_t *data, const size_t size, void *user_data)
Callback invoked when a Server receives an RPC request.
Definition c_api.h:305
void(* vlink_resp_callback_t)(const uint8_t *data, const size_t size, void *user_data)
Callback invoked when a Client receives an RPC response.
Definition c_api.h:315
void(* vlink_msg_callback_t)(const uint8_t *data, const size_t size, void *user_data)
Callback invoked when a Subscriber or Getter receives a message.
Definition c_api.h:289
schema + ser 元数据封装
从这一版开始,C API 的创建接口统一通过 vlink_schema_info_t 输入 ser + schema,在节点初始化前一次性写入底层节点:
typedef enum {
typedef struct {
const char* ser;
vlink_schema_t
Coarse runtime schema family used for raw C API nodes.
Definition c_api.h:167
@ VLINK_SCHEMA_ZEROCOPY
Definition c_api.h:170
@ VLINK_SCHEMA_RAW
Definition c_api.h:169
@ VLINK_SCHEMA_FLATBUFFERS
Definition c_api.h:172
@ VLINK_SCHEMA_PROTOBUF
Definition c_api.h:171
@ VLINK_SCHEMA_UNKNOWN
Definition c_api.h:168
Bundled runtime schema metadata for C API node creation.
Definition c_api.h:184
- vlink_schema_t 的整数值**故意与 C++ enum class SchemaType 保持一致**(见 include/vlink/impl/types.h),这样底层 apply_schema_info 能安全走一次 static_cast;C 客户端应始终使用枚举名,而不是裸整数常量
- ser 表示具体类型名或序列化标识,例如 "demo.proto.PointCloud"、"json"、"vlink::zerocopy::CameraFrame"
- schema 表示粗粒度 schema 家族,用于 discovery、proxy、bag、viewer 等模块做显式路由
- 当前实现依赖 vlink_schema_t 与底层 SchemaType 的整数值保持一致,并通过 static_cast 转换到底层枚举;调用方应始终使用枚举名,而不是自造裸整数
- schema_info == NULL 时表示不显式设置 ser/schema
- schema_info->ser == NULL 或空字符串,且 schema == VLINK_SCHEMA_UNKNOWN 时,等价于不显式设置 ser/schema
- ser 与 schema 必须同时提供或同时省略;只填其中一侧会返回 VLINK_RET_INVALID_ERROR
- schema_info->schema 必须是合法枚举值,否则创建函数返回 VLINK_RET_INVALID_ERROR
全部 C API 函数列表
Publisher 相关
Subscriber 相关
Server 相关
Client 相关
Setter 相关
Getter 相关
API 详细说明
Publisher 相关函数
vlink_create_publisher
const char* url,
VLINK_C_API_EXPORT int vlink_create_publisher(const char *url, const vlink_schema_info_t *schema_info, vlink_publisher_handle_t *handle)
Publisher.
- 在堆上分配一个 Publisher<Bytes> 对象,将指针存入 handle->native_handle
- url:VLink 话题 URL,如 "dds://my/topic",不可为 NULL
- schema_info->ser 与 schema_info->schema 会在底层节点 init() 之前一次性写入
- 若 schema_info == NULL,等价于未设置 ser/schema
- 若 schema_info->schema 不是合法枚举值,返回 VLINK_RET_INVALID_ERROR
- 返回 VLINK_RET_RUNTIME_ERROR 表示底层节点在构造或 init() 过程中抛出异常(例如 URL 非法或对应传输不可用)
vlink_publish
VLINK_C_API_EXPORT int vlink_publish(const vlink_publisher_handle_t handle, const uint8_t *data, const size_t size)
Publishes a message to all matched Subscribers.
vlink_wait_for_subscribers
VLINK_C_API_EXPORT int vlink_wait_for_subscribers(const vlink_publisher_handle_t handle, const int timeout_ms)
Blocks until at least one Subscriber matches or the timeout elapses.
- 阻塞当前线程,直到至少一个 Subscriber 匹配或超时
- timeout_ms < 0 表示无限等待
Subscriber 相关函数
vlink_create_subscriber
VLINK_C_API_EXPORT int vlink_create_subscriber(const char *url, const vlink_schema_info_t *schema_info, vlink_subscriber_handle_t *handle, const vlink_msg_callback_t msg_callback, void *user_data)
Subscriber.
Opaque handle for a Subscriber node.
Definition c_api.h:210
- 创建 Subscriber 并立即调用 listen() 注册回调
- 若 schema_info != NULL,会在 init() 前同步设置 ser/schema
- msg_callback 不可为 NULL(C API 不支持无回调的 Subscriber)
- 回调在 Subscriber 内部接收线程中被调用,注意线程安全
- 回调内的 data 指针在回调返回后可能失效,需要在回调内完成数据消费或深拷贝
Server 相关函数
vlink_create_server
VLINK_C_API_EXPORT int vlink_create_server(const char *url, const vlink_schema_info_t *schema_info, vlink_server_handle_t *handle, const vlink_req_callback_t req_callback, void *user_data)
Server.
Opaque handle for a Server node.
Definition c_api.h:230
- 内部在 reserved[0] 中分配一个 std::mutex
- 若 schema_info != NULL,会在 init() 前同步设置 ser/schema
- 每次请求到来时,持有该 mutex 后调用 req_callback
- **vlink_reply 必须在 req_callback 内部调用**,不可在回调返回后再调用
vlink_reply
VLINK_C_API_EXPORT int vlink_reply(vlink_server_handle_t *handle, const uint8_t *data, const size_t size)
Provides the response data for the current in-progress RPC request.
- 将 data[0..size-1] 深拷贝到 reserved[1] 指向的堆缓冲区
- 将 reserved[2] 设置为 size,将 reserved[3] 清零(标志响应就绪)
- 内部 Server listen 回调在 vlink_reply 返回后,从 reserved[1] 中读取响应并发送
- 若 reserved[3] 为 NULL(即回调外调用),返回 VLINK_RET_RUNTIME_ERROR
vlink_destroy_server
- 除释放 native_handle 外,还会释放 reserved[0](mutex)和 reserved[1](响应缓冲区)
- 析构是同步阻塞的,会等待进行中的请求完成
Client 相关函数
vlink_invoke
const uint8_t* data, const size_t size,
VLINK_C_API_EXPORT int vlink_invoke(const vlink_client_handle_t handle, const uint8_t *data, const size_t size, const vlink_resp_callback_t resp_callback, void *user_data)
Sends an RPC request and registers a callback to receive the response.
Opaque handle for a Client node.
Definition c_api.h:243
- 发送 RPC 请求,data 以浅拷贝方式传递,函数调用期间需保持有效
- resp_callback 可为 NULL(不关心响应结果)
- 响应回调在 Client 内部事件线程中异步调用
- 返回 VLINK_RET_TRANSFER_ERROR 表示未连接到 Server 或发送失败
Getter 相关函数
vlink_create_getter(轮询模式)
VLINK_C_API_EXPORT int vlink_create_getter(const char *url, const vlink_schema_info_t *schema_info, vlink_getter_handle_t *handle, const vlink_msg_callback_t msg_callback, void *user_data)
Getter.
Opaque handle for a Getter node.
Definition c_api.h:269
- 若 schema_info != NULL,会在 init() 前同步设置 ser/schema
- msg_callback 为 NULL 时,Getter 工作在轮询模式,使用 vlink_get 读取最新值
- msg_callback 非 NULL 时,工作在推送模式,每次字段更新时回调被触发
vlink_get(轮询读取)
VLINK_C_API_EXPORT int vlink_get(const vlink_getter_handle_t handle, uint8_t *data, size_t *size)
Retrieves the latest field value into a caller-provided buffer.
错误处理机制
推荐模式
.ser = "demo.proto.PointCloud",
};
fprintf(stderr, "创建 Publisher 失败,错误码: %d\n", ret);
return -1;
}
错误码处理策略
完整 C 语言使用示例
示例一:Publisher + Subscriber(Event 模型)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
static void on_message(const uint8_t* data, const size_t size, void* user_data) {
(void)user_data;
printf("收到消息: %.*s\n", (int)size, (const char*)data);
}
static void on_connect(const bool connected, void* user_data) {
(void)user_data;
printf("订阅者连接状态: %s\n", connected ? "已连接" : "已断开");
}
int main(void) {
int ret;
.ser = "bytes",
};
"dds://example/hello",
&schema,
&sub,
on_message,
NULL
);
fprintf(stderr, "创建 Subscriber 失败: %d\n", ret);
return 1;
}
"dds://example/hello",
&schema,
&pub
);
fprintf(stderr, "创建 Publisher 失败: %d\n", ret);
return 1;
}
fprintf(stderr, "等待订阅者超时\n");
}
for (int i = 0; i < 10; i++) {
char msg[64];
int len = snprintf(msg, sizeof(msg), "Hello VLink #%d", i);
fprintf(stderr, "发布消息失败: %d\n", ret);
}
usleep(100000);
}
return 0;
}
VLINK_C_API_EXPORT int vlink_detect_subscribers(const vlink_publisher_handle_t handle, const vlink_connect_callback_t connect_callback, void *user_data)
Registers a callback fired whenever the Subscriber connection state changes.
VLINK_C_API_EXPORT int vlink_destroy_publisher(vlink_publisher_handle_t *handle)
Destroys a Publisher node and releases all associated resources.
VLINK_C_API_EXPORT int vlink_destroy_subscriber(vlink_subscriber_handle_t *handle)
Destroys a Subscriber node and releases all associated resources.
示例二:Server + Client(Method 模型)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
static void on_request(const uint8_t* data, const size_t size, void* user_data) {
printf("收到请求: %.*s\n", (int)size, (const char*)data);
const char* resp = "OK from C server";
int ret =
vlink_reply(handle, (
const uint8_t*)resp, strlen(resp));
fprintf(stderr, "vlink_reply 失败: %d\n", ret);
}
}
static void on_response(const uint8_t* data, const size_t size, void* user_data) {
(void)user_data;
printf("收到响应: %.*s\n", (int)size, (const char*)data);
}
int main(void) {
int ret;
.ser = "bytes",
};
"dds://example/echo",
&schema,
&srv,
on_request,
&srv
);
fprintf(stderr, "创建 Server 失败: %d\n", ret);
return 1;
}
"dds://example/echo",
&schema,
&cli
);
fprintf(stderr, "创建 Client 失败: %d\n", ret);
return 1;
}
fprintf(stderr, "等待 Server 超时\n");
}
for (int i = 0; i < 3; i++) {
char req[64];
int len = snprintf(req, sizeof(req), "Request #%d", i);
(const uint8_t*)req, (size_t)len,
on_response, NULL);
fprintf(stderr, "invoke 失败: %d\n", ret);
}
usleep(200000);
}
usleep(500000);
return 0;
}
VLINK_C_API_EXPORT int vlink_wait_for_server(const vlink_client_handle_t handle, const int timeout_ms)
Blocks until a Server is available or the timeout elapses.
VLINK_C_API_EXPORT int vlink_destroy_server(vlink_server_handle_t *handle)
Destroys a Server node and frees all internal resources including the internal mutex and any pending ...
VLINK_C_API_EXPORT int vlink_destroy_client(vlink_client_handle_t *handle)
Destroys a Client node and releases all associated resources.
VLINK_C_API_EXPORT int vlink_create_client(const char *url, const vlink_schema_info_t *schema_info, vlink_client_handle_t *handle)
Client.
示例三:Setter + Getter(Field 模型,轮询模式)
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
int main(void) {
int ret;
.ser = "bytes",
};
fprintf(stderr, "创建 Setter 失败: %d\n", ret);
return 1;
}
fprintf(stderr, "创建 Getter 失败: %d\n", ret);
return 1;
}
uint32_t value = 42;
ret =
vlink_set(setter, (
const uint8_t*)&value,
sizeof(value));
fprintf(stderr, "vlink_set 失败: %d\n", ret);
}
usleep(100000);
uint8_t buf[256];
size_t buf_size = sizeof(buf);
uint32_t read_val;
memcpy(&read_val, buf, sizeof(read_val));
printf("读取到字段值: %u\n", read_val);
fprintf(stderr, "缓冲区不足,需要更大的缓冲区\n");
fprintf(stderr, "尚无可用值\n");
}
return 0;
}
VLINK_C_API_EXPORT int vlink_create_setter(const char *url, const vlink_schema_info_t *schema_info, vlink_setter_handle_t *handle)
Setter.
VLINK_C_API_EXPORT int vlink_destroy_setter(vlink_setter_handle_t *handle)
Destroys a Setter node and releases all associated resources.
VLINK_C_API_EXPORT int vlink_destroy_getter(vlink_getter_handle_t *handle)
Destroys a Getter node and releases all associated resources.
VLINK_C_API_EXPORT int vlink_set(const vlink_setter_handle_t handle, const uint8_t *data, const size_t size)
Publishes the latest field value.
Opaque handle for a Setter node.
Definition c_api.h:256
示例四:Getter 推送模式(回调)
#include <stdio.h>
#include <unistd.h>
static void on_value_changed(const uint8_t* data, const size_t size, void* user_data) {
(void)user_data;
printf("字段值更新: %zu 字节\n", size);
}
int main(void) {
.ser = "bytes",
};
"dds://example/state",
&schema,
&getter,
on_value_changed,
NULL
);
return 1;
}
sleep(10);
return 0;
}
C++ Wrapper 与纯 C 的互操作
C API 内部通过以下方式封装 C++ 对象:
vlink_schema_info_t schema = {"demo.proto.PointCloud", VLINK_SCHEMA_PROTOBUF};
vlink_create_publisher("dds://my/topic", &schema, &handle)
-> new vlink::Publisher<vlink::Bytes>(url, kWithoutInit)
-> ptr->set_ser_type(schema.ser, schema.schema)
-> ptr->init()
-> handle.native_handle = ptr
如果你在 C++ 项目中混合使用 C API 和 C++ API,需注意:
- C API 使用 vlink::Bytes 作为统一类型,C++ API 支持任意模板参数类型
- C API 的 schema_info 参数等价于 C++ API 的 node.set_ser_type(ser, schema)
- C API 不暴露 QoS 配置、安全配置等高级功能,如需这些功能请直接使用 C++ API
Python 通过 C API 使用 VLink(ctypes)
import ctypes
import ctypes.util
lib = ctypes.CDLL("/usr/local/lib/libvlink-c_api.so")
class VlinkPublisherHandle(ctypes.Structure):
_fields_ = [
("native_handle", ctypes.c_void_p),
("reserved", ctypes.c_void_p * 4),
]
class VlinkSubscriberHandle(ctypes.Structure):
_fields_ = [
("native_handle", ctypes.c_void_p),
("reserved", ctypes.c_void_p * 4),
]
class VlinkSchemaInfo(ctypes.Structure):
_fields_ = [
("ser", ctypes.c_char_p),
("schema", ctypes.c_int),
]
MsgCallback = ctypes.CFUNCTYPE(None, ctypes.POINTER(ctypes.c_uint8),
ctypes.c_size_t, ctypes.c_void_p)
lib.vlink_create_publisher.restype = ctypes.c_int
lib.vlink_create_publisher.argtypes = [
ctypes.c_char_p,
ctypes.POINTER(VlinkSchemaInfo),
ctypes.POINTER(VlinkPublisherHandle),
]
lib.vlink_publish.restype = ctypes.c_int
lib.vlink_publish.argtypes = [
VlinkPublisherHandle,
ctypes.POINTER(ctypes.c_uint8),
ctypes.c_size_t,
]
lib.vlink_destroy_publisher.restype = ctypes.c_int
lib.vlink_destroy_publisher.argtypes = [ctypes.POINTER(VlinkPublisherHandle)]
VLINK_SCHEMA_RAW = 1
pub = VlinkPublisherHandle()
schema = VlinkSchemaInfo(b"bytes", VLINK_SCHEMA_RAW)
ret = lib.vlink_create_publisher(b"dds://python/topic", ctypes.byref(schema), ctypes.byref(pub))
assert ret == 0, f"创建失败: {ret}"
msg = b"Hello from Python"
buf = (ctypes.c_uint8 * len(msg)).from_buffer_copy(msg)
ret = lib.vlink_publish(pub, buf, len(msg))
print("发布结果:", ret)
lib.vlink_destroy_publisher(ctypes.byref(pub))
Go 通过 C API 使用 VLink(cgo)
package main
/*
#cgo CFLAGS: -I/usr/local/include
#cgo LDFLAGS: -L/usr/local/lib -lvlink-c_api
#include <vlink/external/c_api.h>
#include <stdlib.h>
// 导出给 Go 使用的 C 回调包装
extern void goMsgCallback(const uint8_t* data, size_t size, void* user_data);
*/
import "C"
import (
"fmt"
"unsafe"
)
//export goMsgCallback
func goMsgCallback(data *C.uint8_t, size C.size_t, userData unsafe.Pointer) {
slice := C.GoBytes(unsafe.Pointer(data), C.int(size))
fmt.Printf("Go 收到消息: %s\n", slice)
}
func main() {
// 创建 Publisher
var pub C.vlink_publisher_handle_t
url := C.CString("dds://go/topic")
ser := C.CString("bytes")
schema := C.vlink_schema_info_t{ser: ser, schema: C.VLINK_SCHEMA_RAW}
defer C.free(unsafe.Pointer(url))
defer C.free(unsafe.Pointer(ser))
ret := C.vlink_create_publisher(url, &schema, &pub)
if ret != C.VLINK_RET_NO_ERROR {
fmt.Printf("创建 Publisher 失败: %d\n", ret)
return
}
defer C.vlink_destroy_publisher(&pub)
// 创建 Subscriber
var sub C.vlink_subscriber_handle_t
subUrl := C.CString("dds://go/topic")
defer C.free(unsafe.Pointer(subUrl))
ret = C.vlink_create_subscriber(subUrl, &schema, &sub,
C.vlink_msg_callback_t(C.goMsgCallback), nil)
if ret != C.VLINK_RET_NO_ERROR {
fmt.Printf("创建 Subscriber 失败: %d\n", ret)
return
}
defer C.vlink_destroy_subscriber(&sub)
// 发布消息
msg := []byte("Hello from Go")
ret = C.vlink_publish(pub,
(*C.uint8_t)(unsafe.Pointer(&msg[0])),
C.size_t(len(msg)))
fmt.Printf("发布结果: %d\n", ret)
}
Rust 通过 C API 使用 VLink(bindgen + FFI)
首先用 bindgen 或手动定义绑定:
// bindings.rs(由 bindgen 生成或手写)
use std::os::raw::{c_char, c_int, c_void};
#[repr(C)]
pub struct VlinkPublisherHandle {
pub native_handle: *mut c_void,
pub reserved: [*mut c_void; 4],
}
pub type VlinkMsgCallback = extern "C" fn(*const u8, usize, *mut c_void);
#[repr(C)]
pub struct VlinkSchemaInfo {
pub ser: *const c_char,
pub schema: c_int,
}
extern "C" {
pub fn vlink_create_publisher(
url: *const c_char,
schema_info: *const VlinkSchemaInfo,
handle: *mut VlinkPublisherHandle,
) -> c_int;
pub fn vlink_publish(
handle: VlinkPublisherHandle,
data: *const u8,
size: usize,
) -> c_int;
pub fn vlink_destroy_publisher(handle: *mut VlinkPublisherHandle) -> c_int;
}
// main.rs
use std::ffi::CString;
mod bindings;
use bindings::*;
fn main() {
let url = CString::new("dds://rust/topic").unwrap();
let ser = CString::new("bytes").unwrap();
let schema = VlinkSchemaInfo {
ser: ser.as_ptr(),
schema: VLINK_SCHEMA_RAW,
};
let mut pub_handle = VlinkPublisherHandle {
native_handle: std::ptr::null_mut(),
reserved: [std::ptr::null_mut(); 4],
};
let ret = unsafe {
vlink_create_publisher(url.as_ptr(), &schema, &mut pub_handle)
};
assert_eq!(ret, 0, "创建 Publisher 失败");
let msg = b"Hello from Rust";
let ret = unsafe {
vlink_publish(pub_handle, msg.as_ptr(), msg.len())
};
println!("发布结果: {}", ret);
unsafe { vlink_destroy_publisher(&mut pub_handle) };
}
注意事项
线程安全
| 操作 | 线程安全性 |
| 同一句柄的 create / destroy | **不安全**,不可并发调用 |
| 同一 Publisher 并发 vlink_publish | **安全**,底层使用内部锁 |
| 同一 Server 的 vlink_reply 在回调外调用 | **返回 VLINK_RET_RUNTIME_ERROR**,严禁在回调返回后调用 |
| 不同句柄之间的并发操作 | **安全**,各句柄独立 |
| 回调内调用其他 VLink API | 视传输层而定,通常**不安全**(可能死锁),建议通过队列转发 |
内存管理
- **create/destroy 必须成对调用**,忘记 destroy 会导致内存和 DDS 资源泄漏
- **vlink_publish / vlink_invoke / vlink_set 的 data 指针**:函数调用期间需有效,返回后可以安全释放
- **vlink_get 的 data 缓冲区**:由调用方分配和管理,*size 入参必须是缓冲区容量
- **消息回调中的 data 指针**:仅在回调执行期间有效,需要在回调内完成拷贝
- **vlink_server_handle_t::reserved[1]**:由 vlink_reply 在堆上 new[] 分配,由 vlink_destroy_server 负责 delete[] 释放,用户代码不应直接操作
常见错误
void on_req(const uint8_t* data, size_t size, void* ud) {
}
static const uint8_t* saved_ptr = NULL;
void on_msg(const uint8_t* data, size_t size, void* ud) {
saved_ptr = data;
}
static uint8_t saved_buf[4096];
static size_t saved_size = 0;
void on_msg(const uint8_t* data, size_t size, void* ud) {
if (size <= sizeof(saved_buf)) {
memcpy(saved_buf, data, size);
saved_size = size;
}
}
传输 URL 格式
C API 的 url 参数与 C++ API 完全一致,格式为 <transport>://<topic_name>(完整的传输后端列表参见 07-transport.md):
C API 封装架构
稳定后端:
| transport | 底层传输 | 示例 | 状态 |
| intra:// | 进程内队列 | "intra://my/topic" | 稳定 |
| shm:// | Iceoryx 共享内存 | "shm://my/topic" | 稳定 |
| dds:// | Fast-DDS | "dds://sensor/lidar" | 稳定 |
| ddsc:// | CycloneDDS | "ddsc://sensor/lidar" | 稳定 |
Beta 后端:
| transport | 底层传输 | 示例 | 状态 |
| shm2:// | Iceoryx2 | "shm2://sensor/lidar" | Beta |
| ddsr:// | RTI DDS | "ddsr://sensor/lidar" | Beta |
| ddst:// | TravoDDS(国产 DDS) | "ddst://sensor/lidar" | Beta |
| zenoh:// | Zenoh | "zenoh://sensor/lidar" | Beta |
| someip:// | SOME/IP | "someip://my/service" | Beta |
| mqtt:// | MQTT | "mqtt://sensor/data" | Beta |
| fdbus:// | FDBus IPC | "fdbus://my/service" | Beta |
| qnx:// | QNX IPC | "qnx://my/topic" | Beta |
切换传输后端只需修改 URL 前缀,其余代码无需改动——这是 VLink C API 与 C++ API 共享的核心设计优势。