Fluent Bit 内置 WAMR 的 wasm-c-api 嵌入指南:引擎初始化、线程模型与支持范围详解
2026/9/18 9:17:35 网站建设 项目流程

Fluent Bit 内置 WAMR 的 wasm-c-api 嵌入指南:引擎初始化、线程模型与支持范围详解

【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit

导读

本文以 wasm-micro-runtime(WAMR,本仓库以lib/wasm-micro-runtime-WAMR-2.4.1/目录形式随 Fluent Bit 一起发布)中的 wasm-c-api 为主线,系统讲解这套引擎无关的 C 语言 WebAssembly 嵌入 API:它是什么、与 WAMR 原生 APIwasm_export.h如何二选一、线程模型有哪些硬性约束、核心对象如何串联(Engine → Store → Module → Instance → Func/Memory/Table/Global),并给出可直接编译运行的完整示例(hello、callback)以及当前版本的支持/不支持清单。读完本文,你将能够自行编写嵌入 WAMR 的 C 宿主程序,并规避线程与内存管理上的常见坑。

一、wasm-c-api 是什么

wasm-c-api 是一套与具体引擎无关的 WebAssembly 嵌入 API。它由 WebAssembly 社区维护的 wasm-c-api(约 910 行)。

这套 API 的定位与 WAMR 原生 APIwasm_export.h功能重叠:

  • wasm-c-api(wasm_c_api.h:引擎无关、面向可移植嵌入场景,类型与函数命名遵循标准(wasm_engine_*wasm_store_*wasm_module_*等);
  • WAMR 原生 API(wasm_export.h,见 core/iwasm/include/wasm_export.h):功能更强、更贴近 WAMR 特性(AOT 加载、实例化参数、内存管理选项等)。

文档明确要求:嵌入方(embedder)应从中二选一,不要混用两套 API。从源码结构看,二者在 WAMR 内部共享同一套运行时对象(例如wasm_module_tWASMModuleCommon直接互相关联,见 wasm_c_api.h),因此混用虽然"能编译",但会破坏所有权与生命周期约定,属于未定义行为。

二、所有权(own)与向量(vec)约定

wasm-c-api 中大量使用own限定符与wasm_xxx_vec_t向量类型,这是理解整套 API 内存语义的钥匙。

2.1own所有权语义

在 wasm_c_api.h 中,own是一个被定义为空的宏,纯粹用于文档标注

  • own wasm_xxx_t*:拥有指针指向的数据;
  • own wasm_xxx_t:所有权分布到结构体/联合体的所有字段;
  • own wasm_xxx_vec_t:同时拥有向量本身及其元素;
  • own函数参数:所有权从调用方移交给被调方;
  • own函数返回值:所有权从被调方移交给调用方;
  • 例外:名为outown指针参数是"拷贝回写"输出参数,所有权从被调方交回调用方。

所有own数据都由wasm_xxx_new系列函数创建,并必须用对应的wasm_xxx_delete释放。注意:删除一个引用并不一定删除底层对象,它只是表示"该持有者不再使用它"。

2.2 向量类型

向量通过宏WASM_DECLARE_VEC(name, ptr_or_none)声明(wasm_c_api.h),结构体包含size(容量)、data(数据指针)、num_elems(当前元素数)、size_of_elem(元素字节大小)和lock字段。配套提供:

  • wasm_xxx_vec_new_empty:创建空向量;
  • wasm_xxx_vec_new_uninitialized:按给定长度分配未初始化内存(读取二进制文件时常用);
  • wasm_xxx_vec_new:从现有数组拷贝构造;
  • wasm_xxx_vec_copy:深拷贝;
  • wasm_xxx_vec_delete:释放。

wasm_byte_vec_t是字节向量(typedef byte_t wasm_byte_t),用于承载 wasm 二进制内容,同时被别名为wasm_name_t(模块名、导入/导出名等字符串均以字节向量表达,并提供wasm_name_new_from_string/wasm_name_new_from_string_nt便捷函数,见 wasm_c_api.h)。

三、运行时环境:Config、Engine、Store 与线程模型

3.1 Config:运行时配置

wasm_config_t允许在创建引擎前定制运行时行为(wasm_c_api.h):

字段含义默认值
mem_alloc_type内存分配器类型:Alloc_With_Pool(用户堆缓冲池)、Alloc_With_Allocator(用户自定义 malloc/realloc/free)、Alloc_With_System_Allocator(系统分配器)Alloc_With_System_Allocator
mem_alloc_option分配器选项联合体:pool分支含heap_buf/heap_sizeallocator分支含malloc_func/realloc_func/free_func/user_data全 0
segue_flagsLinux x86_64 下是否用 GS 寄存器作为线性内存基址以加速 LLVM AOT/JIT 访问:bit0-bit4 控制 i32/i64/f32/f64/v128 load,bit8-bit12 控制 store;如0x01启用 i32.load,0x1F1F启用全部 load/store0
enable_linux_perf是否启用 Linux perf 支持false

创建与定制入口:wasm_config_new()wasm_config_set_mem_alloc_opt(config, type, option)wasm_config_set_linux_perf_opt(config, bool)wasm_config_set_segue_flags(config, flags)

3.2 Engine 与 Store

wasm_engine_t* engine = wasm_engine_new(); // 默认配置 wasm_engine_t* engine = wasm_engine_new_with_config(cfg); // 定制配置 wasm_store_t* store = wasm_store_new(engine); // 由 engine 创建 store

注意wasm_engine_new_with_args(mem_alloc_type_t, const MemAllocOption*)在 WAMR 中已被标记为WASM_API_DEPRECATED(见 wasm_c_api.h),新代码应使用wasm_config_t路线。

3.3 线程模型(原文档核心内容,务必遵守)

原文档在 FYI 中给出两条铁律:

  1. 一个wasm_engine_t实例在单个进程内只能创建一次
  2. 每一个wasm_store_t及其对象(module、instance、func 等)只能在单个线程中被访问

同时,wasm_engine_newwasm_engine_new_with_configwasm_engine_new_with_argswasm_engine_delete这些引擎级操作应当在线程安全环境中调用。源码进一步说明(wasm_c_api.h):如果平台具备 mutex 初始化能力,WAMR 会用全局锁保证这些操作线程安全;否则多线程同时执行引擎的 new/delete 时,开发者必须自行加锁。

原文档明确列出的不推荐行为(如确需使用,请保证合理的调用顺序):

  • 在不同线程分别调用wasm_engine_newwasm_engine_delete
  • 在不同线程多次调用wasm_engine_newwasm_engine_delete

实践建议:在进程启动阶段单线程创建 engine 与 store,随后把不同的 store 分发给不同线程各自使用,这是 WAMR 多线程嵌入的典型架构。

四、核心对象与类型体系

4.1 值类型(Value Types)

wasm_valkind_t枚举(wasm_c_api.h):

常量说明
WASM_I32/WASM_I640 / 132/64 位整数
WASM_F32/WASM_F642 / 332/64 位浮点
WASM_V1284128 位 SIMD 向量
WASM_EXTERNREF128外部引用
WASM_FUNCREF129函数引用

辅助判定:wasm_valkind_is_num(小于WASM_EXTERNREF为数值类型)、wasm_valkind_is_ref(大于等于WASM_EXTERNREF为引用类型)。

wasm_val_t是运行时值结构(wasm_c_api.h),包含kind字段与联合体ofi32/i64/f32/f64/ref)。

4.2 类型表示(Type Representations)

  • wasm_functype_twasm_functype_new(params, results)构造,wasm_functype_params/wasm_functype_results读取;
  • wasm_globaltype_twasm_globaltype_new(valtype, mutability),可变性WASM_CONST/WASM_VAR
  • wasm_tabletype_twasm_tabletype_new(element, limits)
  • wasm_memorytype_twasm_memorytype_new(limits)wasm_limits_tmin/max,上限默认wasm_limits_max_default = 0xffffffff
  • wasm_externtype_t:四种外部类型的统一抽象,wasm_externkind_t取值WASM_EXTERN_FUNC/WASM_EXTERN_GLOBAL/WASM_EXTERN_TABLE/WASM_EXTERN_MEMORY,并提供wasm_functype_as_externtypewasm_externtype_as_functype等类型双向转换(含_const版本);
  • wasm_importtype_t/wasm_exporttype_t:描述模块的导入/导出签名,分别携带 module 名、name 与类型。

4.3 Module 与 Instance

// 校验 + 编译 bool wasm_module_validate(store, &binary); own wasm_module_t* module = wasm_module_new(store, &binary); // 可选的模块命名(供调试) wasm_module_set_name(module, "hello"); const char* name = wasm_module_get_name(module); // 实例化(imports 为外部导入向量,第三个参数为 trap 输出) own wasm_instance_t* instance = wasm_instance_new(store, module, &imports, NULL); // 查询导入/导出 wasm_module_imports(module, &importtype_vec); wasm_module_exports(module, &exporttype_vec); wasm_instance_exports(instance, &exports);

wasm_module_new_ex(store, binary, LoadArgs*)提供了更细粒度的加载控制(wasm_c_api.h),其语义对应wasm_export.h中的wasm_runtime_load_ex

  • name:模块名;
  • clone_wasm_binary:默认 true;若为 false,模块直接引用输入缓冲区而非克隆,加载完成后可释放原缓冲区;
  • wasm_binary_freeable:仅 AOT/wasm loader 使用;
  • no_resolve:默认 false;为 true 时不立即解析符号,需后续调用wasm_runtime_resolve_symbols

4.4 运行对象

  • Funcwasm_func_new(store, functype, callback)创建宿主函数;wasm_func_new_with_env(store, functype, callback_with_env, env, finalizer)创建带环境闭包的宿主函数;wasm_func_call(func, args, results)同步调用并返回wasm_trap_t*(NULL 表示成功)。回调签名:own wasm_trap_t* (*wasm_func_callback_t)(const wasm_val_vec_t* args, own wasm_val_vec_t* results)
  • Globalwasm_global_new/wasm_global_get/wasm_global_set
  • Tablewasm_table_new/wasm_table_get/wasm_table_set/wasm_table_size/wasm_table_grow
  • Memorywasm_memory_new/wasm_memory_data(返回线性内存首字节指针)/wasm_memory_data_size/wasm_memory_size(页数,一页MEMORY_PAGE_SIZE = 0x10000即 64 KiB)/wasm_memory_grow
  • Externwasm_func_as_extern/wasm_extern_as_func等"对象 ↔ 外部抽象"转换,是组装 imports 向量与解析 exports 的必经之路;
  • Trapwasm_trap_new/wasm_trap_message/wasm_trap_origin/wasm_trap_trace(获取调用栈帧wasm_frame_t序列);
  • Foreignwasm_foreign_new创建宿主侧任意对象句柄,可随引用类型传入 wasm。

宿主信息挂钩:所有引用对象都支持wasm_xxx_get_host_info/wasm_xxx_set_host_info/wasm_xxx_set_host_info_with_finalizer(wasm_c_api.h),方便把自定义数据结构挂到运行时对象上。

五、完整可运行示例:hello 与 callback

WAMR 在 samples/wasm-c-api/ 下提供了完整示例(每个示例同时附.c宿主源码与.wat手写 wasm 源文件)。

5.1 hello:最小嵌入流程

src/hello.c 完整演示了 wasm-c-api 的标准生命周期,值得逐段研读:

  1. 初始化wasm_engine_new()wasm_store_new(engine)
  2. 读取二进制:以fopen打开hello.wasm(AOT-only 构建时打开hello.aot),fseek/ftell获取长度,wasm_byte_vec_new_uninitialized分配并fread读入;
  3. 校验与编译wasm_module_validate(store, &binary)校验,wasm_module_new(store, &binary)编译,随后wasm_byte_vec_delete(&binary)释放二进制;
  4. 创建宿主回调wasm_functype_new_0_0()声明"无参无返回值"函数类型,wasm_func_new绑定hello_callback(其内部打印Calling back...> Hello World!);
  5. 实例化wasm_func_as_extern(hello_func)包装为 extern 数组,WASM_ARRAY_VEC(externs)组装导入向量,wasm_instance_new(store, module, &imports, NULL)
  6. 提取导出wasm_instance_exports(instance, &exports)wasm_extern_as_func(exports.data[0])拿到导出函数;
  7. 调用wasm_func_call(run_func, &args, &results)(空参数、空结果),trap 非空则报错;
  8. 清理:依次wasm_extern_vec_deletewasm_store_deletewasm_engine_delete

5.2 callback:带参回调与环境闭包

src/callback.c 展示了两种宿主函数形态:

  • 普通回调print_callback读取args->data[0]打印 wasm 传来的值,并通过wasm_val_copy(&results->data[0], &args->data[0])将入参原样返回;
  • 带环境闭包closure_callback(void* env, ...)env中取出宿主侧整数i(示例中为 42),把i写入结果results->data[0].of.i32;创建时用wasm_func_new_with_env(store, closure_type, closure_callback, &i, NULL)&i作为环境传入。

调用侧使用wasm_val_t as[2] = { WASM_I32_VAL(3), WASM_I32_VAL(4) }构造参数向量,调用后从rs[0].of.i32读取返回值。回调函数的wasm_val_print帮助函数还演示了如何按kind分派打印 i32/i64/f32/f64/引用类型。

其余示例还覆盖了 memory(线性内存读写)、table(表操作与函数指针)、global(全局变量导入导出)、multi(多模块)、trap(异常与调用栈)、hostref(宿主引用)、reflect(反射)、serialize(序列化)、threads(线程)等场景,均在 samples/wasm-c-api/src/ 下。

六、编译与运行示例

示例工程的构建说明见 samples/wasm-c-api/README.md。前提是下载并安装 WABT(WebAssembly Binary Toolkit)工具链,用于把.wat文本编译为.wasm二进制。

解释器模式(默认)

$ mkdir build && cd build $ cmake .. $ make # 构建出带 c-api 支持的运行时库 # 同时把 ../src/ 下的 *.wasm 拷贝到当前目录,并生成可执行文件 $ ./hello $ ./callback $ ./global

AOT 模式:通过编译开关切换(见 samples/wasm-c-api/CMakeLists.txt,默认WAMR_BUILD_INTERP=1WAMR_BUILD_AOT=0):

$ mkdir build && cd build $ cmake -DWAMR_BUILD_INTERP=0 -DWAMR_BUILD_AOT=1 .. $ make # 同样构建带 c-api 支持的库,并把 *.wasm 转为 *.aot 后运行 $ ./hello

也就是说,同一套 wasm-c-api 宿主代码在解释器与 AOT 两种模式下均可工作;示例宿主源码通过#if WASM_ENABLE_AOT != 0 && WASM_ENABLE_INTERP == 0判断加载*.aot还是*.wasm文件。AOT 模式可获得更快的启动与执行性能,但需要先用 wamrc 等工具把 wasm 编译为 AOT 镜像,这一取舍与 doc/build_wamr.md、doc/embed_wamr.md 中描述的构建策略一致。

七、当前版本的不支持清单(unupported list)

原文档明确指出:WAMR 支持 wasm-c-api 的绝大多数 API,以下是当前版本(WAMR 2.4.1)明确不支持的部分。

7.1 References 共享/获取

WASM_API_EXTERN own wasm_shared_##name##_t* wasm_##name##_share(const wasm_##name##_t*); WASM_API_EXTERN own wasm_##name##_t* wasm_##name##_obtain(wasm_store_t*, const wasm_shared_##name##_t*);

wasm_module_share/wasm_module_obtain等跨 store 共享引用对象的 API 不可用。从源码看,声明中module被注释为WASM_DECLARE_SHARABLE_REF(module)(见 wasm_c_api.h),且typedef wasm_module_t wasm_shared_module_t仅是别名,实际并未提供跨线程/跨 store 的共享语义。这与线程模型(每个 store 单线程访问)是自洽的。

7.2 Module 序列化/反序列化

WASM_API_EXTERN void wasm_module_serialize(const wasm_module_t*, own wasm_byte_vec_t* out); WASM_API_EXTERN own wasm_module_t* wasm_module_deserialize(wasm_store_t*, const wasm_byte_vec_t*);

wasm_module_serialize/wasm_module_deserialize未实现。需要模块级缓存/预编译的场景,应改用 WAMR 原生 API(wasm_export.h)中的 AOT 编译与wasm_runtime_load系列接口,或用wasm_module_new_exLoadArgs精细控制加载行为。

7.3 Table 与 Memory 的宿主侧增长

原文档特别强调:通过 wasm 操作码增长 table 或 memory 是受支持的,但通过宿主侧函数调用增长则不支持

WASM_API_EXTERN bool wasm_table_grow(wasm_table_t*, wasm_table_size_t delta, wasm_ref_t* init); WASM_API_EXTERN bool wasm_memory_grow(wasm_memory_t*, wasm_memory_pages_t delta);

wasm_table_growwasm_memory_grow在 wasm-c-api 层不可用。这意味着:

  • 若 wasm 模块内部通过memory.grow/table.grow指令扩容,正常运行;
  • 若宿主代码期望主动扩容 wasm 线性内存或表,需要通过其他机制(例如在 wasm 侧导出增长函数再由宿主调用),或直接改用wasm_export.h原生 API。

八、与 wasm_export.h 原生 API 的选型建议

结合 doc/wasm_c_api.md 与头文件内容,两者差异可以归纳为:

维度wasm-c-api(wasm_c_api.h原生 API(wasm_export.h
定位引擎无关、跨运行时可移植WAMR 专属、特性最全
类型系统统一wasm_xxx_t抽象(valtype/functype/externtype…)直接面向wasm_module_twasm_function_inst_t等具体对象
所有权约定own标注明确依赖函数约定
AOT/加载细节wasm_module_new_ex+LoadArgswasm_runtime_load_ex、实例化参数完整
序列化/共享不支持(见上文)支持 AOT 镜像等
增长内存/表宿主侧不支持支持

选型建议:追求"一份宿主代码可换引擎"的嵌入式/跨平台场景优先选 wasm-c-api;需要 WAMR 特有优化(AOT 镜像、快速解释器、SGX、自定义内存池细分控制)或需要用到上节不支持清单中能力的场景,选择wasm_export.h。在 Fluent Bit 生态中,WAMR 以 lib/wasm-micro-runtime-WAMR-2.4.1/ 形式内嵌于仓库,其 wasm 相关能力即通过该运行时对外提供,宿主插件在集成时同样面临上述两套 API 的选择。

九、小结

wasm-c-api 为 WAMR 提供了一条标准化的嵌入路径。使用时的关键要点可归纳为:

  1. 二选一:不要混用wasm_c_api.hwasm_export.h
  2. 线程纪律:engine 每进程只建一次;每个 store 及其对象单线程访问;engine 的 new/delete 需要线程安全环境;
  3. 所有权纪律own标注的对象必须配对 delete,向量用wasm_xxx_vec_delete释放;
  4. 标准流程:engine → store → 读二进制 → validate → module_new →(组装 imports)→ instance_new → 取 exports → func_call → 逆序清理;
  5. 能力边界:References 共享、Module 序列化、宿主侧 table/memory grow 当前不支持,遇到相关需求请切换原生 API 或调整设计。

对于需要进一步深挖的读者,仓库内 samples/wasm-c-api/src/ 下的 14 组示例、core/iwasm/common/wasm_c_api_internal.h 中的内部实现,以及 doc/embed_wamr.md、doc/embed_wamr_spawn_api.md 都是继续深入的良好起点。

【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询