纯 C 桥接 API
本页介绍 cbridge.h 和 dcb_codec.h 提供的纯 C API,以及配合 C++ 协程使用的 cbridge_wait.hpp。
你可以用它们做三件事:
- 用
dcb_codec.h编解码 wire 数据,与 C++ 侧ByteWriter/ByteReader二进制兼容 - 用
dcb_invoke_dart_fn从任意线程调用已注册的 Dart 回调 - 用
dcb_async_*配合cbridge_wait.hpp让 C++ 协程非阻塞等待外部 C 异步操作完成
如果你的代码是纯 C 项目、只能导出 C 符号,或者不想把 async-simple / asio / C++20 协程引入调用方,就应该用这章的 C 入口。
其他语言 runtime(如 Python
ctypes、Rust FFI、Gocgo)也可以把这套 C API 当作最小接入点。
什么时候用纯 C API
Section titled “什么时候用纯 C API”以下情况选择这套入口:
- 你的代码是纯 C 项目,或只能导出 C 符号:C 编译单元没有
co_await、模板和Executor,无法直接使用 bridge 的 C++ 协程入口。 - 你从其他语言 runtime 调用 bridge:Python
ctypes、Rust FFI、Gocgo等只能绑定到 C ABI。 - 你希望调用方保持零 C++ 依赖:C 端只编译 C 头文件,bridge 内部的 async-simple + asio 对调用者不可见。
纯 C API 是零 C++ 依赖的最低公分母入口:
任何 C/C++ 运行时(不管用什么协程/事件循环) │ │ dcb_codec.h — 编解码参数/返回值 │ dcb_async_* — 异步操作原语 │ dcb_invoke_dart_fn — 调用 Dart 回调 ▼bridge 内部(C++ 协程管道,调用者无需知道) │ ▼Dart Isolate 执行回调 → 结果通过 callback 返回#include "dart_cpp_bridge/dcb_codec.h" // wire payload 编解码#include "dart_cpp_bridge/cbridge.h" // 异步操作原语 + 调用 Dart 回调// 纯 C99,不引入任何 C++ 头文件API 一:纯 C 编解码
Section titled “API 一:纯 C 编解码”dcb_codec.h 提供与 C++ 侧 ByteWriter / ByteReader(codec.hpp)二进制兼容的纯 C99 编解码器。
| C 类型 | wire 编码 | 说明 |
|---|---|---|
int32_t |
4 bytes LE | bool 也用这个(0/1) |
uint32_t |
4 bytes LE | |
int64_t |
8 bytes LE | |
uint64_t |
8 bytes LE | |
double |
8 bytes LE (IEEE 754) | |
const char* |
u32 len + UTF-8 bytes | 唯一变长类型 |
| 数组 | u32 count + N 个元素 | 元素可以是上表任意基本类型 |
数组元素支持:i32、u32、i64、u64、f64、str。同一个数组内元素类型必须一致(由调用者保证)。
不支持 struct / 指针 / 嵌套容器——复杂数据序列化为 string(JSON、protobuf 等)即可。
Writer
Section titled “Writer”动态增长缓冲区,初始 64 字节,按需倍增。内部 malloc/realloc,用完后 dcb_writer_free 释放。
dcb_writer w;dcb_writer_init(&w);
dcb_write_str(&w, "hello"); // u32 strlen + UTF-8 bytesdcb_write_i32(&w, 42); // 4 bytes LEdcb_write_f64(&w, 3.14); // 8 bytes LEdcb_write_u64(&w, 123456789ULL); // 8 bytes LE
// 数组:先写 count,再循环写元素dcb_write_arr_begin(&w, 3);dcb_write_i32(&w, 10);dcb_write_i32(&w, 20);dcb_write_i32(&w, 30);
// 使用 w.data / w.len 作为 payloaddcb_invoke_dart_fn(session_id, fn_id, w.data, w.len, callback, userdata);
dcb_writer_free(&w); // 释放内部缓冲区完整 Writer API:
void dcb_writer_init(dcb_writer* w);void dcb_writer_free(dcb_writer* w);void dcb_write_u32(dcb_writer* w, uint32_t v);void dcb_write_u64(dcb_writer* w, uint64_t v);void dcb_write_i32(dcb_writer* w, int32_t v);void dcb_write_i64(dcb_writer* w, int64_t v);void dcb_write_f64(dcb_writer* w, double v);void dcb_write_len_bytes(dcb_writer* w, const void* p, uint32_t n); // u32 len + datavoid dcb_write_str(dcb_writer* w, const char* s); // inline: strlen + len_bytesvoid dcb_write_arr_begin(dcb_writer* w, uint32_t count); // inline: u32 countReader
Section titled “Reader”零拷贝读取器——不 malloc,不持有数据所有权。越界读取时进入错误状态(后续返回 0),通过 dcb_reader_valid 检查。
// data / data_len 来自 dcb_invoke_dart_fn 的 callback 参数dcb_reader r;dcb_reader_init(&r, data, data_len);
uint32_t slen;const char* s = dcb_read_str(&r, &slen); // 零拷贝,指向 data 内部int32_t v = dcb_read_i32(&r);double d = dcb_read_f64(&r);
// 读数组uint32_t n = dcb_read_arr_begin(&r);for (uint32_t i = 0; i < n; i++) { int32_t elem = dcb_read_i32(&r); // ...}
if (!dcb_reader_valid(&r)) { // 数据格式不匹配,读取越界}完整 Reader API:
void dcb_reader_init(dcb_reader* r, const uint8_t* data, uint32_t len);int dcb_reader_valid(const dcb_reader* r); // 1=正常, 0=曾越界uint32_t dcb_read_u32(dcb_reader* r);uint64_t dcb_read_u64(dcb_reader* r);int32_t dcb_read_i32(dcb_reader* r);int64_t dcb_read_i64(dcb_reader* r);double dcb_read_f64(dcb_reader* r);const uint8_t* dcb_read_len_bytes(dcb_reader* r, uint32_t* out_len); // 零拷贝const char* dcb_read_str(dcb_reader* r, uint32_t* out_len); // inline castuint32_t dcb_read_arr_begin(dcb_reader* r); // inline: u32 count与 C++ codec 的对应关系
Section titled “与 C++ codec 的对应关系”纯 C (dcb_codec.h) |
C++ (codec.hpp) |
wire 格式 |
|---|---|---|
dcb_write_i32 |
ByteWriter::i32() |
4 bytes LE |
dcb_write_u32 |
ByteWriter::u32() |
4 bytes LE |
dcb_write_i64 |
ByteWriter::i64() |
8 bytes LE |
dcb_write_u64 |
ByteWriter::u64() |
8 bytes LE |
dcb_write_f64 |
ByteWriter::f64() |
8 bytes LE |
dcb_write_str |
ByteWriter::str() |
u32 len + bytes |
dcb_write_arr_begin + 循环 |
ByteWriter::vec() 头 |
u32 count + N elements |
dcb_read_str |
ByteReader::str() |
零拷贝 |
两侧编码完全一致,纯 C 侧写的数据 C++ 侧能直接读,反之亦然。
API 二:异步操作原语
Section titled “API 二:异步操作原语”这组函数用于在 C 端创建、完成或取消一个异步操作。C++ 协程侧通过 cbridge_wait.hpp 中的 dcb::async_wait() 非阻塞等待,期间不占用 bridge 线程。
uint64_t dcb_async_create(void); // 创建操作void dcb_async_complete(uint64_t op_id, const uint8_t* data, uint32_t len); // 成功完成void dcb_async_fail(uint64_t op_id, const char* error); // 失败void dcb_async_cancel(uint64_t op_id); // 取消C++ 协程侧等待
Section titled “C++ 协程侧等待”#include "dart_cpp_bridge/cbridge_wait.hpp"
// 在协程中(必须绑定 Executor):auto data = co_await dcb::async_wait(op_id); // 挂起,不占线程// data 为 std::vector<uint8_t>;失败时抛 std::runtime_error典型场景:C++ 协程调用外部 C 库的异步 API
Section titled “典型场景:C++ 协程调用外部 C 库的异步 API”假设 Dart 侧调用 fetchUrl(url) → C++ 协程收到 url 参数 → 调用外部 C 库的异步 HTTP API → 库完成后返回响应体 → 协程将结果返回 Dart。
#include "dart_cpp_bridge/cbridge.h"#include "dart_cpp_bridge/cbridge_wait.hpp"#include "dart_cpp_bridge/dcb_codec.h" // 纯 C codec(回调用)#include "dart_cpp_bridge/codec.hpp" // C++ ByteReader(协程用)#include <string>#include <thread>#include <chrono>#include <cstring>
// ─── 模拟外部 C 库(代表任何第三方异步库,如 libcurl、libuv 等) ───// 回调类型:status=0 成功,body/body_len 为响应数据typedef void (*http_callback)(void* ctx, int status, const uint8_t* body, uint32_t body_len);
// 外部库的异步接口:在自己的线程上完成工作后调用 callbackstatic void http_client_get_async(const char* url, http_callback cb, void* ctx) { // 模拟:库内部开一个线程处理请求(实际库可能是线程池/事件循环) std::thread([url = std::string(url), cb, ctx] { // 模拟网络延迟... std::this_thread::sleep_for(std::chrono::milliseconds(50));
// 模拟响应体 std::string response = "response from " + url; cb(ctx, 0, (const uint8_t*)response.data(), (uint32_t)response.size()); }).detach();}
// ─── 我们的回调(在库的线程上触发) ───// ctx 是我们传进去的 op_idstatic void http_done(void* ctx, int status, const uint8_t* body, uint32_t body_len) { uint64_t op_id = (uint64_t)(uintptr_t)ctx; if (status == 0) { // 用 dcb_codec 编码为 wire 格式(string: u32 len + bytes) dcb_writer w; dcb_writer_init(&w); dcb_write_len_bytes(&w, body, body_len); // 把原始字节包装为 string dcb_async_complete(op_id, w.data, w.len); // 传给协程 dcb_writer_free(&w); } else { dcb_async_fail(op_id, "http request failed"); }}
// ─── bridge 协程(由 wire dispatch 调用,参数已从 Dart 解码好) ───// url 是 wire dispatch 从 Dart 请求中解码出的 std::stringasync_simple::coro::Lazy<std::string> fetch_url(std::string url) { // 1. 创建异步操作 uint64_t op_id = dcb_async_create();
// 2. 启动外部 C 库的异步操作(非阻塞) // 传入 url 和回调,库完成后会调用 http_done http_client_get_async(url.c_str(), http_done, (void*)(uintptr_t)op_id);
// 3. 协程挂起,不占 io 线程。库完成后自动恢复。 auto payload = co_await dcb::async_wait(op_id); // payload 是 http_done 中 dcb_async_complete 传入的字节
// 4. 解码结果(C++ 协程内直接用 ByteReader) dcb::ByteReader r(payload.data(), payload.size()); co_return r.str(); // 对应 http_done 中的 dcb_write_len_bytes}数据流转:
Dart C++ 协程 外部 C 库──────────────────────────────────────────────────────────────fetchUrl("https://...") │ wire payload ▼wire dispatch 解码 → url = "https://..." │ dcb_async_create() → op_id http_client_get_async(url, cb, op_id) │ │ co_await async_wait(op_id) │ (库在自己的线程上工作) │ 协程挂起 ───────────── │ │ http_done(op_id, body) │ dcb_write_len_bytes(body) │ dcb_async_complete(op_id, ...) │ ◀─────────────────── 协程恢复 ByteReader(payload).str() → body co_return body ◀─── wire responseDart 收到响应体对比 spawn_blocking
Section titled “对比 spawn_blocking”dcb_async_* |
spawn_blocking |
|
|---|---|---|
| 是否占线程 | 否(协程挂起) | 是(占一个 pool 线程) |
| 并发上限 | 无(只消耗一个 op_id) | thread_pool 大小(默认 4) |
| 适用场景 | 外部 C 库有异步 API | 外部 C 库只有同步 API |
| C++ 依赖 | 协程侧需要 cbridge_wait.hpp |
需要 runtime.hpp |
API 三:调用 Dart 回调
Section titled “API 三:调用 Dart 回调”typedef void (*dcb_dart_fn_callback)( void* userdata, int ok, // 1=成功, 0=失败 const uint8_t* data, // 成功时的编码返回值(wire payload) uint32_t data_len, const char* error // 失败时的错误信息(NUL 结尾));
int dcb_invoke_dart_fn( uint64_t session_id, // 目标 session uint64_t fn_id, // Dart 闭包 ID const uint8_t* args, // 编码后的参数(wire payload 格式) uint32_t args_len, dcb_dart_fn_callback callback, void* userdata);- 非阻塞:立即返回 0(成功发起)或 -1(session 无效)
- 回调保证被调用恰好一次(成功或失败)
- 回调在 bridge 的 io 线程上触发——不要在回调中阻塞
- 可从任意线程调用(包括外部运行时的 loop 线程)
示例:C 调用 Dart 函数全流程
Section titled “示例:C 调用 Dart 函数全流程”完整链路:Dart 调 C++ → C++ 把 ID 传给 C 层 → C 层调 Dart 回调 → Dart 返回数据 → C 层处理并唤醒 C++ → C++ 可选返回结果。
① Dart 侧——用户写一个普通异步函数 + 调用桥接 API:
// 用户写的回调:接收一个名字,返回问候语Future<String> greet(String name) async { if (name.isEmpty) throw Exception('name cannot be empty'); return 'Hello, $name!';}
// 调用桥接函数(codegen 生成的顶层函数)// 把回调 + 参数一起传给 C++final result = await startGreetTask(callback: greet, name: 'World');print(result); // "Dart said: Hello, World!"② C++ 侧——接收 DartFn + 参数,创建异步操作,把一切传给 C 层:
#include "dart_cpp_bridge/dart_fn.hpp"#include "dart_cpp_bridge/session.hpp"#include "dart_cpp_bridge/cbridge_wait.hpp"
// 纯 C 层的前向声明void c_start_greet(uint64_t session_id, uint64_t fn_id, uint64_t op_id, const char* name);
// wire dispatch 解码后调用此协程async_simple::coro::Lazy<std::string> start_greet_task( dcb::DartFn<std::string(std::string)> callback, std::string name) { // 1. 提取纯 C API 需要的 ID uint64_t session_id = dcb::SessionRegistry::instance().find_id(callback.session()); uint64_t fn_id = callback.fn_id();
// 2. 创建异步操作(C 层完成后用来唤醒本协程) uint64_t op_id = dcb_async_create();
// 3. 把 ID + 参数传给 C 层,C 层在某个时刻会回调 Dart c_start_greet(session_id, fn_id, op_id, name.c_str());
// 4. 挂起,等 C 层完成(不占 io 线程) auto payload = co_await dcb::async_wait(op_id);
// 5. 解码 C 层返回的最终结果(可选) dcb::ByteReader r(payload.data(), payload.size()); co_return r.str();}③ 纯 C 侧——在某个时刻调 Dart 回调,拿到结果后唤醒 C++:
#include "dart_cpp_bridge/cbridge.h"#include "dart_cpp_bridge/dcb_codec.h"#include <stdio.h>#include <string.h>#include <stdlib.h>
// 上下文:C 层需要记住 op_id(用来唤醒 C++)typedef struct { uint64_t op_id;} greet_ctx;
// Dart 执行完回调后,bridge 在 io 线程触发此函数// 参数说明:// userdata — 调用 dcb_invoke_dart_fn 时传入的用户指针(原样传回)// ok — 1=Dart 正常返回, 0=Dart 抛了异常// data/len — 成功时:Dart 返回值的编码字节;失败时:NULL/0// error — 失败时:Dart 闭包抛出的异常 message(NUL 结尾);成功时:NULL// 例如 Dart 侧 throw Exception('name cannot be empty')// → error = "Exception: name cannot be empty"static void on_dart_reply(void* userdata, int ok, const uint8_t* data, uint32_t len, const char* error) { greet_ctx* ctx = (greet_ctx*)userdata;
if (ok) { // 解码 Dart 返回的字符串 dcb_reader r; dcb_reader_init(&r, data, len); uint32_t slen; const char* greeting = dcb_read_str(&r, &slen);
// C 层拿到数据后做自己的操作(拼接最终结果) char result[256]; snprintf(result, sizeof(result), "Dart said: %.*s", (int)slen, greeting);
// 编码结果,唤醒 C++ 协程 dcb_writer w; dcb_writer_init(&w); dcb_write_str(&w, result); dcb_async_complete(ctx->op_id, w.data, w.len); dcb_writer_free(&w); } else { // Dart 闭包抛了异常(如 throw Exception('...')) // bridge 捕获后把 exception.toString() 作为 error 传到这里 // → 转发给 C++ 协程,co_await 处会抛出 std::runtime_error(error) dcb_async_fail(ctx->op_id, error); }
free(ctx);}
// C++ 调用此函数启动整个流程void c_start_greet(uint64_t session_id, uint64_t fn_id, uint64_t op_id, const char* name) { // 编码要传给 Dart 回调的参数 dcb_writer w; dcb_writer_init(&w); dcb_write_str(&w, name); // Dart 侧收到 name 参数
// 保存上下文(回调时需要 op_id) greet_ctx* ctx = (greet_ctx*)malloc(sizeof(greet_ctx)); ctx->op_id = op_id;
// 发起调用(可以在任意线程、任意时刻) int rc = dcb_invoke_dart_fn(session_id, fn_id, w.data, w.len, on_dart_reply, ctx); dcb_writer_free(&w);
if (rc != 0) { dcb_async_fail(op_id, "invoke failed: invalid session"); free(ctx); }}数据流转:
Dart C++ 协程 纯 C 层──────────────────────────────────────────────────────────────startGreetTask( callback: greet, name: "World") │ wire 传递 ▼ start_greet_task(callback, "World") find_id() → session_id fn_id() dcb_async_create() → op_id │ c_start_greet(sid, fn_id, op_id, "World") │ dcb_write_str("World") dcb_invoke_dart_fn(...) ◀──────────────────────────────────── 调用 greet("World") greet 执行 → "Hello, World!" ────────────────────────────────────▶ on_dart_reply 收到结果 │ 拼接 "Dart said: Hello, World!" dcb_async_complete(op_id, ...) │ co_await 恢复 ◀───────────┘ ByteReader.str() co_return "Dart said: Hello, World!" ◀─── 最终结果返回 Dartprint(result)内部实现原理
Section titled “内部实现原理”dcb_async_create() → 创建 oneshot channel,Sender + Receiver 存入全局 registry → 返回 op_id
C++ 协程: co_await dcb::async_wait(op_id) → 从 registry 取出 Receiver → co_await rx.recv() 挂起协程
dcb_async_complete(op_id, data, len) [任意线程] → 从 registry 取出 Sender → tx.send(OpResult{ok=true, data}) → wake_waiter → 协程在其 Executor 上恢复
dcb_async_fail / dcb_async_cancel 类似。dcb_invoke_dart_fn 内部:
dcb_invoke_dart_fn(session_id, fn_id, args, len, callback, userdata) → 查找 Session → spawn_on_asio: 在 io 线程启动协程 → co_await session->invoke_dart_fn_async(gen, fn_id, args) → 发送 DartFnCall 帧到 Dart → 挂起等待 Dart 回复 → Dart 回复 → 协程恢复 → callback(userdata, ok, data, len, error)| 函数 | 线程安全 | 说明 |
|---|---|---|
dcb_invoke_dart_fn |
是 | 可从任意线程调用 |
dcb_async_create |
是 | 全局 mutex 保护 |
dcb_async_complete/fail/cancel |
是 | 可从任意线程调用 |
dcb::async_wait |
— | 仅在协程内使用 |
- 测试用例:
examples/foreign_runtime_demo中的test_cbridge_async/test_cbridge_invoke - 编解码:
dart/native/include/dart_cpp_bridge/dcb_codec.h - 头文件:
dart/native/include/dart_cpp_bridge/cbridge.h - C++ helper:
dart/native/include/dart_cpp_bridge/cbridge_wait.hpp