尧图精选

Dora C API 完全指南:用 C 语言开发 Node 与 Operator

🕒 发布时间:2026/9/18 10:21:56 📁 来源:尧图网络
Dora C API 完全指南用 C 语言开发 Node 与 Operator【免费下载链接】doraDORA (Dataflow-Oriented Robotic Architecture) is middleware designed to streamline and simplify the creation of AI-based robotic applications. It offers low latency, composable, and distributed dataflow capabilities. Applications are modeled as directed graphs, also referred to as pipelines.项目地址: https://gitcode.com/GitHub_Trending/do/doraDORADataflow-Oriented Robotic Architecture以数据流图pipeline为核心组织机器人应用而 C 语言是其多语言支持中与 Rust 运行时衔接最直接的一层。本文以仓库文档 guide/src/languages/c.md 为主体系统讲解 Dora 提供的两套 C 接口供独立进程使用的Node APIdora-node-api-c与供共享库 Operator 使用的Operator APIdora-operator-api-c。读完本文你将掌握 C 节点的初始化、事件循环与输出发送C Operator 的生命周期三函数与事件处理并能在examples/c-dataflow的真实工程中完成编译、链接与数据流编排。一、两种 C API 的定位与区别Dora 的 C 生态包含两个层级不同的 API理解它们的差异是正确选型的前提维度Node APIOperator API产物形态独立可执行文件C 进程共享库.so/.dylib/.dll头文件apis/c/node/node_api.hapis/c/operator/operator_api.h、apis/c/operator/operator_types.hRust cratedora-node-api-c构建为staticlibdora-operator-api-c运行载体daemon 以外部进程方式 spawn并通过环境变量传入初始化信息由 Dora 运行时进程在启动时dlopen加载入口形态拥有自己的main函数没有main导出三个生命周期函数供运行时回调Node 是数据流中的外部参与者daemon 启动该进程并设置环境变量节点在init_dora_context_from_env中读取这些变量完成初始化。Operator 则寄生在运行时进程内与运行时共享内存空间因此调用开销更低、状态传递更直接但必须严格遵守运行时约定的生命周期与内存所有权规则。二、Node API 详解dora-node-api-c头文件 apis/c/node/node_api.h 定义了全部 Node API。从源码可以看到该头文件同时是 Rust crate 通过include_str!嵌入、再经no_mangle导出符号的实现源见 apis/c/node/src/lib.rs因此头文件与二进制始终同步。2.1 初始化与销毁init_dora_context_from_envvoid *init_dora_context_from_env();从 daemon 设置的环境变量初始化 Dora 节点上下文成功返回不透明指针失败返回NULL。该指针必须传给所有后续 Node API 调用使用完毕后用free_dora_context释放。从实现看apis/c/node/src/lib.rs它内部调用DoraNode::init_from_env()并通过Box::into_raw将 Rust 对象泄漏为裸指针返回因此NULL之外的所有指针都必须走 API 释放不能free()。free_dora_contextvoid free_dora_context(void *dora_context);释放init_dora_context_from_env创建的上下文。每个上下文恰好释放一次释放后指针不得再使用。2.2 事件循环dora_next_eventvoid *dora_next_event(void *dora_context);阻塞等待下一个事件。返回不透明事件指针或当所有事件流关闭时返回NULL节点通常应据此退出。返回的指针不能直接解引用须用read_dora_*系列函数读取类型与载荷用完调用free_dora_event释放。源码中它对应context.events.recv()的Option映射Some(event)装箱返回裸指针None返回空指针apis/c/node/src/lib.rs。free_dora_eventvoid free_dora_event(void *dora_event);释放dora_next_event返回的事件每个事件恰好释放一次。释放后事件指针以及由read_dora_input_id、read_dora_input_data派生的所有指针全部失效——因为那些指针直接指向事件内部内存。2.3 事件检查read_dora_event_typeenum DoraEventType read_dora_event_type(void *dora_event);返回事件类型取值见下文 DoraEventType。实现上它把 Rust 的Event枚举映射为 C 枚举Stop、Input、InputClosed、Error之外的任意变体统一归为Unknownapis/c/node/src/lib.rs。read_dora_input_idvoid read_dora_input_id(void *dora_event, char **out_ptr, size_t *out_len);从DoraEventType_Input事件中读取输入 ID起始指针写入*out_ptr字节长度写入*out_len。该字符串是合法 UTF-8 但非 NUL 结尾必须用out_len界定边界。若事件不是输入事件则写入*out_ptr NULL、*out_len 0。read_dora_input_datavoid read_dora_input_data(void *dora_event, char **out_ptr, size_t *out_len);读取输入事件的原始数据字节。非输入事件或输入无数据时写入NULL/0。关于数据类型支持原文档提到目前仅支持UInt8Arrow 数组其他 Arrow 类型会导致运行时 panic而当前源码已经演进了这一行为从 apis/c/node/src/lib.rs 的实现看非UInt8/Null的 Arrow 类型不再中止进程而是记录tracing::error!日志并返回out_ptr NULL、out_len 0表示无数据。这一点有单元测试read_dora_input_data_non_uint8_returns_null直接佐证如Int32载荷返回空指针而非崩溃。因此实际编码时应把空指针理解为无可用原始字节视图它既可能是真无数据也可能是载荷类型不受支持——跨语言场景例如其他节点发来Int32/Float尤其要留意。零长度UInt8载荷同样返回NULL/0测试read_dora_input_data_empty_uint8_returns_null验证了该契约。read_dora_input_timestampunsigned long long read_dora_input_timestamp(void *dora_event);返回输入事件元数据中的混合逻辑时钟hybrid logical clock, HLC时间戳以uint64返回非输入事件返回0。2.4 发送输出dora_send_outputint dora_send_output( void *dora_context, const char *id_ptr, size_t id_len, const char *data_ptr, size_t data_len );向所有下游订阅者发送输出数据。id_ptr/id_len必须是合法 UTF-8 字符串且须与 dataflow YAML 中该节点声明的某个输出 ID 一致data_ptr/data_len作为原始字节UInt8 Arrow 数组发送。成功返回0失败返回-1错误经tracing记录任一指针参数为NULL时立即返回-1。从实现看apis/c/node/src/lib.rs这里有两个值得注意的细节输出 ID 会经DataId::from_str解析解析失败例如 ID 中含空格会返回-1而不是跨 FFI 边界 panic发送路径允许(NULL, 0)表示空消息data_slice对data_len 0且指针为空的组合显式放行这使 C 节点可以用惯用的dora_send_output(ctx, id, id_len, NULL, 0)发送空载荷但data_len 0时data_ptr为NULL会被拒绝有测试data_slice_rejects_null_with_nonzero_len验证。2.5 结构化日志dora_logint dora_log( void *dora_context, const char *level_ptr, size_t level_len, const char *msg_ptr, size_t msg_len );通过 Dora 日志管线发送结构化日志。level与msg均须为合法 UTF-8 字符串。合法日志级别error、warn、info、debug、trace。成功返回0失败返回-1任一指针为NULL立即返回-1。2.6 DoraEventType 枚举enum DoraEventType { DoraEventType_Stop, // Graceful shutdown requested DoraEventType_Input, // New input data available DoraEventType_InputClosed, // An input stream was closed DoraEventType_Error, // An error occurred DoraEventType_Unknown, // Unrecognized event type };2.7 Node API 的线程安全契约这是头文件 apis/c/node/node_api.h 中一段容易被忽略、但对多线程 C 节点至关重要的注释值得单独说明上下文不线程安全dora_next_event、dora_send_output、dora_log都会修改上下文内部状态事件流游标、发送器状态、日志状态同一上下文被多线程并发调用属于未定义行为。如需把事件扇出到工作线程应在单线程内 drain 事件再按事件类型分发。事件可并发只读事件指针创建后只读多个线程可并发读取同一事件的不同字段各自提供独立的out_ptr/out_len存储。释放须独占free_dora_context与free_dora_event转移所有权调用时须保证没有其他线程正在使用该上下文/事件。这些契约是 dora-rs 对 C API 审计见头文件注释引用的 issue后固化下来的跨 FFI 边界写并发代码前务必通读。三、Operator API 详解dora-operator-api-cOperator API 面向加载进 Dora 运行时进程的共享库。Operator没有main函数而是导出三个生命周期函数供运行时在合适的时机回调。头文件 apis/c/operator/operator_api.h 用EXPORT宏Windows 为__declspec(dllexport)其余平台为visibility(default)标记导出符号并包裹extern Capis/c/operator/operator_types.h 由safer-ffi自动生成文件头注明File auto-generated by::safer_ffi定义全部 C 兼容的结构体与枚举。3.1 生命周期函数dora_init_operator——运行时加载 Operator 时调用一次DoraInitResult_t dora_init_operator(void);在此分配并初始化 Operator 状态通过operator_context字段返回。运行时会在后续每次调用中把该指针回传。成功时返回result.error NULL的DoraInitResult_t。dora_drop_operator——Operator 被卸载时调用一次DoraResult_t dora_drop_operator(void *operator_context);释放与operator_context关联的全部资源。成功时返回.error NULL的DoraResult_t。3.2 事件处理dora_on_eventOnEventResult_t dora_on_event( RawEvent_t *event, const SendOutput_t *send_output, void *operator_context );每次有事件到达该 Operator 时由运行时调用。通过检查event各字段判断事件类型字段条件含义event-input ! NULL有新输入可用event-stop true请求优雅停机event-error.ptr ! NULL发生错误UTF-8 字符串在error.ptr/error.lenevent-input_closed.ptr ! NULL某个输入流关闭输入 ID 在input_closed.ptr/input_closed.len使用send_output向下游发送数据见dora_send_operator_output并通过返回OnEventResult_t中适当的DoraStatus_t控制 Operator 生命周期。注意RawEvent_t中多个字段可能同时置位应按优先级顺序检查。3.3 读取输入dora_read_input_idchar *dora_read_input_id(const Input_t *input);返回新建分配、以 NUL 结尾的输入 ID 字符串调用者必须用dora_free_input_id释放。dora_read_dataVec_uint8_t dora_read_data(Input_t *input);将输入数据读为字节数组。该操作会消费底层 Arrow 数组——每个事件的数据只能读取一次重复读取返回.ptr NULL并记录double read提示输入无数据或载荷类型不受原始字节 API 支持API 只读UInt8载荷时同样返回.ptr NULL。返回的数据须用dora_free_data释放。3.4 发送输出dora_send_operator_outputDoraResult_t dora_send_operator_output( const SendOutput_t *send_output, const char *id, const uint8_t *data_ptr, size_t data_len );向下游订阅者发送输出。id须为 NUL 结尾字符串且与 Operator 声明的某个输出匹配data_ptr/data_len在内部转换为 UInt8 Arrow 数组。成功返回.error NULL的DoraResult_t。与 Node API 对应地(NULL, 0)空载荷是合法惯用法类型头文件中的文档注释明确说明了这一契约并指出它补齐了 2026-04-08 unsafe 审计发现的空指针检查缺口。3.5 内存管理规则Operator API 分配的内存必须用对应的函数释放这是本 API 最容易踩坑的地方分配来源释放函数dora_read_input_iddora_free_input_iddora_read_datadora_free_datavoid dora_free_input_id(char *input_id); void dora_free_data(Vec_uint8_t data);严禁对上述分配调用free()——它们由 Rust 运行时分配必须通过 API 释放否则会泄漏或破坏运行时内存布局。3.6 核心结构体Vec_uint8_t——Rust 分配的字节向量typedef struct Vec_uint8 { uint8_t *ptr; size_t len; size_t cap; } Vec_uint8_t;访问从ptr开始的len个字节即可不要修改cap用dora_free_data释放。DoraResult_t——通用结果类型typedef struct DoraResult { Vec_uint8_t *error; // NULL on success, points to error string on failure } DoraResult_t;error为NULL表示成功非NULL时指向含 UTF-8 错误消息的向量。DoraInitResult_t——dora_init_operator的返回值typedef struct DoraInitResult { DoraResult_t result; void *operator_context; // opaque pointer to operator state } DoraInitResult_t;成功时result.error NULLoperator_context保存 Operator 状态指针。OnEventResult_t——dora_on_event的返回值typedef struct OnEventResult { DoraResult_t result; DoraStatus_t status; } OnEventResult_t;同时包含成败结果与控制生命周期的状态码。RawEvent_t——送达 Operator 的事件typedef struct RawEvent { Input_t *input; // non-NULL when this is an input event Vec_uint8_t input_closed; // non-empty when an input stream closed bool stop; // true when shutdown is requested Vec_uint8_t error; // non-empty on error } RawEvent_t;多个字段可能同时置位需按优先级顺序检查。Input_t/Output_t——两个不透明类型Input_t表示输入事件的数据用dora_read_input_id/dora_read_data提取内容Output_t仅供dora_send_operator_output内部使用用户代码不直接创建。SendOutput_t——传给dora_on_event的回调句柄typedef struct SendOutput { ArcDynFn1_DoraResult_Output_t send_output; } SendOutput_t;把它传给dora_send_operator_output即可发送数据。不要将其保存超过当前dora_on_event调用的作用域。Metadata_t——事件元数据typedef struct Metadata { Vec_uint8_t open_telemetry_context; } Metadata_t;包含 OpenTelemetry 追踪上下文字符串。3.7 DoraStatus_t 枚举enum DoraStatus { DORA_STATUS_CONTINUE 0, // Keep running DORA_STATUS_STOP 1, // Stop this operator DORA_STATUS_STOP_ALL 2, // Stop the entire dataflow }; typedef uint8_t DoraStatus_t;在OnEventResult_t中返回用于处理完事件后控制 Operator 生命周期继续运行、停止本 Operator或停止整个数据流。四、完整实战C 节点 C Operator 数据流仓库中的 examples/c-dataflow 是上述两套 API 的完整可运行工程包含节点、Operator、Sink 三个 C 文件与一份编排三者的 dataflow 配置下面逐段拆解。4.1 C 节点node.c参考 examples/c-dataflow/node.c 的骨架#include stdio.h #include string.h #include node_api.h int main() { void *dora_context init_dora_context_from_env(); if (dora_context NULL) { fprintf(stderr, failed to init dora context\n); return 1; } for (int i 0; i 100; i) { void *event dora_next_event(dora_context); if (event NULL) break; // all streams closed enum DoraEventType ty read_dora_event_type(event); if (ty DoraEventType_Input) { char *id; size_t id_len; read_dora_input_id(event, id, id_len); // Send a response char out_id[] message; char out_data[64]; int out_len snprintf(out_data, sizeof(out_data), iteration %d, i); dora_send_output(dora_context, out_id, strlen(out_id), out_data, out_len); } else if (ty DoraEventType_Stop) { free_dora_event(event); break; } free_dora_event(event); } free_dora_context(dora_context); return 0; }核心模式初始化上下文 → 事件循环dora_next_event→ 按类型分发 → 发送输出 → 释放事件 → 最终释放上下文。仓库工程中的sink.cexamples/c-dataflow/sink.c还演示了InputClosed分支与用fwrite(id, id_len, 1, stdout)按长度打印非 NUL 结尾 ID 的正确姿势。4.2 C Operatoroperator.c参考 examples/c-dataflow/operator.c 的完整三函数实现#include operator_api.h #include stdio.h #include stdlib.h #include string.h DoraInitResult_t dora_init_operator(void) { // Allocate operator state (a simple counter) int *counter (int *)calloc(1, sizeof(int)); DoraInitResult_t result {.operator_context counter}; return result; } DoraResult_t dora_drop_operator(void *operator_context) { free(operator_context); DoraResult_t result {.error NULL}; return result; } OnEventResult_t dora_on_event( RawEvent_t *event, const SendOutput_t *send_output, void *operator_context) { OnEventResult_t result {.status DORA_STATUS_CONTINUE}; int *counter (int *)operator_context; if (event-input ! NULL) { char *id dora_read_input_id(event-input); Vec_uint8_t data dora_read_data(event-input); if (data.ptr ! NULL) { *counter 1; printf(received input %s, counter: %d\n, id, *counter); // Send counter value as string char buf[64]; int len snprintf(buf, sizeof(buf), count%d, *counter); result.result dora_send_operator_output( send_output, counter, (uint8_t *)buf, len); dora_free_data(data); } dora_free_input_id(id); } if (event-stop) { result.status DORA_STATUS_STOP; } return result; }注意示例中体现的四条铁律状态经operator_context跨事件保持dora_read_data返回的Vec_uint8_t用完立即dora_free_datadora_read_input_id返回的字符串用完dora_free_input_id即使data.ptr NULL空载荷也要释放id。仓库工程中还用strcmp(id, message) 0按输入 ID 分流展示了多输入 Operator 的常见写法。4.3 数据流编排dataflow.yml示例的完整编排 examples/c-dataflow/dataflow.yml 将三者串联成流水线nodes: - id: c_node path: build/c_node inputs: timer: dora/timer/millis/50 outputs: - message - id: runtime-node operators: - id: c_operator shared-library: build/operator inputs: message: c_node/message outputs: - counter - id: c_sink path: build/c_sink inputs: counter: runtime-node/c_operator/counter要点解读c_node是外部 C 进程path指向编译出的可执行文件它的输入是 Dora 内置的周期定时器dora/timer/millis/50每 50 ms 触发一次输出为message。runtime-node内嵌 C Operatorshared-library: build/operator指向共享库Operator 的输入message引用c_node/message输出counter被c_sink订阅。c_sink是终端 C 进程消费runtime-node/c_operator/counter。输出引用采用node_id/output_id、算子输出引用采用node_id/operator_id/output_id的层级路径。4.4 一键构建与运行examples/c-dataflow提供的 run.rs 自动化了完整流程先cargo build --package dora-node-api-c与dora-operator-api-c再用 clang 编译node.c、sink.c为可执行文件、编译链接operator.c为共享库自动适配 Linux/macOS/Windows 的链接参数与 DLL 前后缀最后通过RunCommand执行dataflow.yml。手动跑通该示例可参考其脚本逻辑cargo build -p dora-node-api-c --release cargo build -p dora-operator-api-c --release随后按下一节的方式编译 C 文件。五、构建与链接指南5.1 节点静态库链接C 节点链接dora-node-api-c该 crate 构建为静态库。第 1 步构建静态库cargo build -p dora-node-api-c --release产物为target/release/libdora_node_api_c.aWindows 上为.lib。第 2 步编译并链接clang node.c -ldora_node_api_c -L ../../target/release -o build/c_node FLAGS平台相关链接参数平台FlagsLinux-lm -lrt -ldl -pthreadmacOS-framework CoreServices -framework Security -lSystem -lresolv -lpthread -lc -lmWindows-ladvapi32 -luserenv -lkernel32 -lws2_32 -lbcrypt -lncrypt -lschannel -lntdll -liphlpapi -lcfgmgr32 -lcredui -lcrypt32 -lcryptnet -lfwpuclnt -lgdi32 -lmsimg32 -lmswsock -lole32 -lopengl32 -lsecur32 -lshell32 -lsynchronization -luser32 -lwinspool -Wl,-nodefaultlib:libcmt -D_DLL -lmsvcrtWindows 上输出文件需加.exe扩展名。仓库 examples/c-dataflow/run.rs 的构建逻辑还额外验证了 Linux 下追加-lz、Windows 下追加oleaut32/winhttp/rpcrt4等依赖当链接期报符号缺失时可对照参考。5.2 Operator共享库C Operator 编译为共享库由 Dora 运行时在启动时加载。第 1 步编译为目标文件clang -c operator.c -o build/operator.o -fdeclspec -fPICWindows 上省略-fPIC。第 2 步链接为共享库# Linux clang -shared build/operator.o -o build/liboperator.so # macOS clang -shared build/operator.o -o build/liboperator.dylib # Windows clang -shared build/operator.o -o build/operator.dll注意 Windows 上还需链接dora_operator_api_c及平台系统库可参照 examples/c-dataflow/run.rs 中的 Windows 分支。第 3 步在 dataflow YAML 中引用operators: - id: c_operator shared-library: build/operator # without lib prefix or extension inputs: data: source/output outputs: - resultshared-library路径省略平台前缀lib与扩展名.so/.dylib/.dll运行时按当前平台解析实际文件。5.3 Include 路径Node API 头文件apis/c/node/node_api.hOperator API 头文件apis/c/operator/operator_api.h 与 apis/c/operator/operator_types.h# Node clang -I path/to/dora/apis/c/node node.c ... # Operator clang -I path/to/dora/apis/c/operator operator.c ...5.4 C 兼容性两套头文件均可在 C 源码中直接#includeOperator 头文件使用extern C防护见 apis/c/operator/operator_api.h 的__cplusplus分支Node 头文件则采用纯 C 兼容声明。仓库中的 examples/c-dataflow 与 examples/cmake-dataflow 展示了在 C 工程中同时使用 Node APInode-rust-api目录与 Operator APIoperator-rust-api目录的完整形态CMake 集成可参考其中的DoraTargets.cmake。六、从源码理解 API 设计要点结合 apis/c/node/src/lib.rs、apis/c/operator/operator_types.h 等实现可以总结出四条贯穿两套 C API 的设计主线所有权显式化Node API 采用借用模型读函数返回的指针属于事件随free_dora_event失效Operator API 采用转移模型读函数返回全新分配须配对释放。这是两套 API 最本质的差异混用两套心智模型是常见的 bug 来源。错误不跨 FFI 边界 panicNode 侧dora_send_output对非法输出 ID、data_slice对空指针非零长度等场景都显式返回-1而非 unwindOperator 侧同样以DoraResult_t承载错误。C 调用者应始终检查返回值/error字段。类型能力边界清晰原始字节 API 目前只暴露UInt8与空Null载荷其他 Arrow 类型在 Node 侧返回无数据在 Operator 侧返回dora_read_data的None.ptr NULL。跨语言传Int32/Float等类型时需要在上游先序列化为字节或等待未来版本引入 Arrow C Data Interface 的完整类型支持。safer-ffi驱动 ABIOperator 的头文件与结构体布局由safer-ffi自动生成并保证与 Rust 侧#[repr(C)]一致手动修改生成文件会导致 ABI 错位这也是operator_types.h顶部注明Do not manually edit this file的原因。七、延伸阅读完整的 C API 参考文档副本docs/api-c.md可运行示例工程examples/c-dataflow含 node.c、operator.c、sink.c、dataflow.yml 与 run.rsC 场景扩展examples/c-dataflow、examples/cmake-dataflow跨语言数据流示例examples/cross-languageRust 与 Python 节点互发消息其他语言 APIRust 见 apis/rust/node 与 apis/rust/operatorPython 见 apis/python/nodeDora 整体架构与数据流概念docs/architecture.md、docs/extensions.md【免费下载链接】doraDORA (Dataflow-Oriented Robotic Architecture) is middleware designed to streamline and simplify the creation of AI-based robotic applications. It offers low latency, composable, and distributed dataflow capabilities. Applications are modeled as directed graphs, also referred to as pipelines.项目地址: https://gitcode.com/GitHub_Trending/do/dora创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →