WAMR 多模块(Multi-Module)依赖加载机制完全指南:从 import/export 到动态链接实战

发布时间:2026/9/17 12:04:31
WAMR 多模块(Multi-Module)依赖加载机制完全指南:从 import/export 到动态链接实战 WAMR 多模块Multi-Module依赖加载机制完全指南从 import/export 到动态链接实战【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit导读WASM 模块可以通过 import 机制依赖其他模块WAMRWebAssembly Micro Runtime提供了一整套多模块注册、按需加载与跨模块函数调用的 API。本文以 WAMR 官方文档 multi_module.md 为骨架结合仓库内 multi-module 示例工程 的完整源码系统讲解多模块的依赖声明、Command/Reactor 编程模型、构建开关、宿主侧回调实现以及跨模块查找函数的方法让你能够从零搭建一个主模块 多个子模块的 WASM 应用。一、WASM 多模块模型import 与 export 的依赖关系WASM 模块可以像传统动态库一样相互依赖一个模块通过import引入其他模块的函数functions、全局变量globals、内存memories和表tables同时通过export将这些实体暴露给其他模块使用。WAMR 会在加载主模块时根据其 import section递归加载所有依赖的子模块。;; 伪代码示意主模块从 mB 导入函数 B1 (import mB B1 (func $B1 (result i32)))关键前提文档明确说明WAMR 目前仅实现 load-time加载期动态链接即所有模块在实例化前一次性解析完依赖关系不支持运行期的动态加载/dlopen 语义多模块的加载顺序完全由主模块 import section 中声明的依赖关系驱动WAMR 内部按依赖树递归完成。依赖的递归加载是在 wasm_loader.c 与 wasm_runtime.c 等核心文件中实现的二者均受WASM_ENABLE_MULTI_MODULE宏控制。WASI Command/Reactor 模型WAMR 的多模块设计遵循WASI Command/Reactor 模型它将模块划分为两类角色角色说明必备导出Command命令主模块程序入口依赖 reactor 提供的导出必须导出_start()Reactor反应器子模块/库模块被 Command 或其他 reactor 调用必须导出_initialize()当编译时开启WASM_ENABLE_LIBC_WASI且任意模块导入了 WASI API形如(import wasi_snapshot_preview1 XXX)时必须遵守上述 WASI Application ABI 限制主模块command应包含_start()子模块reactor应包含_initialize()command 与 reactor 都必须导出memory——因为跨模块函数调用共享同一线性内存空间被调用方必须能访问调用方的内存导出。二、多模块相关 API 全景1. 注册模块wasm_runtime_register_modulebool wasm_runtime_register_module(const char *module_name, wasm_module_t module, char *error_buf, uint32_t error_buf_size);用于以指定的module_name将一个模块注册到运行时。它的典型场景是主模块主模块由wasm_runtime_load()直接加载没有机会向运行时告知自己的模块名而它又可能被其他模块 import例如示例中的 mC 注册后即可被查找因此需要显式注册。而子模块的名字来自主模块 import section 中的声明WAMR 会据此从文件系统或流中读取对应的.wasm文件并在内部自动注册无需宿主手动注册。在 samples/multi-module/src/main.c 中可以看到mC 作为主模块通过wasm_runtime_register_module(mC, module, ...)注册随后wasm_runtime_find_module_registered(mC)能返回与module相同的指针。2. 查找已注册模块wasm_runtime_find_module_registeredwasm_module_t wasm_runtime_find_module_registered( const char *module_name);用于校验某个module_name是否已被注册若已注册则返回对应模块句柄否则返回 NULL。示例 main.c 用它验证 mA、mB 被自动注册、mC 注册前后行为差异见 main.c#L133-L157。3. 模块读取器与销毁器回调typedef bool (*module_reader)(const char *module_name, uint8_t **p_buffer, uint32_t *p_size); typedef void (*module_destroyer)(uint8_t *buffer, uint32_t size); void wasm_runtime_set_module_reader(const module_reader reader, const module_destroyer destroyer);WAMR 的设计哲学是文件系统与存储完全交给宿主embedding environment负责。宿主只需把子模块的二进制内容交给运行时运行时不关心这些字节来自文件、网络流还是内存。因此module_reader在动态加载子模块时被调用宿主需要根据module_name读出二进制内容到*p_buffer并返回实际大小*p_sizemodule_destroyer在卸载子模块时被调用宿主负责释放reader分配的内存两个回调都必须由开发者自行实现wasm_runtime_set_module_reader在运行时初始化后、加载主模块前调用。4. 调用子模块导出函数wasm_runtime_lookup_functionwasm_function_inst_t wasm_runtime_lookup_function(wasm_module_inst_t const module_inst, const char *name);多模块模式下该 API 支持两种函数名格式名称格式查找范围function_name仅函数名默认在父模块主模块中查找$submodule_name$function_name在指定子模块中查找导出函数例如$mA$A1其中signature参数可传 NULL由运行时自行解析函数签名。三、完整实战mA / mB / mC 三模块依赖链仓库中的 samples/multi-module 提供了完整可运行的示例其依赖关系如下图示例中三个模块的 import/export 依赖关系原图见 doc/pics/multi_module_pic1.png。依赖拓扑为mAreactor只导出 A1不依赖任何模块mBreactor从 mA 导入 A1导出 B1、B2mCcommand主模块从 mA 导入 A1、从 mB 导入 B1、B2导出 C1C4并拥有main()。1. 用attribute声明 import/exportC/C 侧通过两种__attribute__声明跨模块依赖与导出// 声明依赖当前模块从 MODULE_NAME 导入 FUNCTION_NAME __attribute__((import_module(MODULE_NAME))) __attribute__((import_name(FUNCTION_NAME))) extern int FUNCTION_NAME(); // 声明导出将函数暴露给其他模块 __attribute__((export_name(FUNCTION_NAME))) int FUNCTION_NAME() { ... }以仓库源码 mA.c 为例/* mA.c —— 纯 reactor不需要 main() */ __attribute__((export_name(A1))) int A1() { return 11; } int A2() { return 12; /* 未加 export_name不对外导出 */ }mB.c 演示了导入 再导出的链条/* mB.c —— 从 mA 导入 A1导出 B1、B2 */ __attribute__((import_module(mA))) __attribute__((import_name(A1))) extern int A1(); __attribute__((export_name(B1))) int B1() { return 21; } __attribute__((export_name(B2))) int B2() { return A1(); /* 内部调用导入函数 */ }mC.c 则是主模块同时从 mA、mB 导入多个函数/* mC.c —— command拥有 main() */ #include stdio.h __attribute__((import_module(mA))) __attribute__((import_name(A1))) extern int A1(); __attribute__((import_module(mB))) __attribute__((import_name(B1))) extern int B1(); __attribute__((import_module(mB))) __attribute__((import_name(B2))) extern int B2(); __attribute__((export_name(C1))) int C1() { return 31; } __attribute__((export_name(C2))) int C2() { return B1(); } __attribute__((export_name(C3))) int C3() { return A1(); } __attribute__((export_name(C4))) int C4() { return B2(); } int C5() { return C1() C2() C3() 35; } /* 未导出 */ int main() { printf(%u\n, C5()); return EXIT_SUCCESS; }注意 C5 没有加export_name属性。示例 main.c 中wasm_application_execute_func(module_inst, C5, ...)因此调用失败这正是对未导出函数不可跨模块访问的刻意验证。2. 编译选项command vs reactor使用 WASI SDK 的 clang 分别编译# 生成 command主模块默认 exec-model $ /path/to/wasi-sdk/bin/clang -o command.wasm main_module.c # 生成 reactor子模块 $ /path/to/wasi-sdk/bin/clang -mexec-modelreactor -o reactor.wasm submodule.c仓库的 wasm-apps/CMakeLists.txt 通过封装函数精确控制每种角色的编译方式compile_with_clang(mA.c OFF) # reactor compile_with_clang(mB.c OFF) # reactor compile_with_clang(mC.c ON) # command compile_with_clang(mD.cpp ON) # commandC 示例 compile_with_clang(mE.cpp OFF) # reactorC 示例其中compile_with_clang内部通过target_link_options(... PRIVATE -mexec-modelreactor)见 CMakeLists.txt#L62-L67为 reactor 追加链接选项。因此在本例中mA、mB 是 reactor子模块mC 是 command主模块。C 示例 mD/mE 还额外演示了__attribute__((constructor))/__attribute__((destructor))构造析构函数在模块生命周期中的执行顺序以及import_module与import_name的合并写法见 mD.cpp 与 mE.cpp。3. 构建 vmlib开启 WAMR_BUILD_MULTI_MODULE要启用多模块支持构建 WAMR vmlib核心库时必须开启WAMR_BUILD_MULTI_MODULE选项。根据 build_wamr.md 中的说明该选项默认关闭WAMR_BUILD_MULTI_MODULE1/0, default to disable if not set该开关在 config_common.cmake 中转换为WASM_ENABLE_MULTI_MODULE宏最终写入 core/config.h并贯穿解释器interpreter、AOT 与 fast-jit 各执行模式。示例 README.md 还指出多模块在解释器与 AOT 模式下均受支持运行模式由主模块的类型.wasm或.aot决定。4. 宿主侧代码回调 内存池 加载调用参照 main.c宿主侧共分三步。第一步实现两个回调负责按模块名加载/释放 WASM 二进制。static char *module_search_path .; static bool module_reader_callback(package_type_t module_type, const char *module_name, uint8 **p_buffer, uint32 *p_size) { char *file_format NULL; #if WASM_ENABLE_INTERP ! 0 if (module_type Wasm_Module_Bytecode) file_format .wasm; #endif #if WASM_ENABLE_AOT ! 0 if (module_type Wasm_Module_AoT) file_format .aot; #endif bh_assert(file_format ! NULL); const char *format %s/%s%s; int sz strlen(module_search_path) strlen(/) strlen(module_name) strlen(file_format) 1; char *wasm_file_name wasm_runtime_malloc(sz); if (!wasm_file_name) { return false; } snprintf(wasm_file_name, sz, format, module_search_path, module_name, file_format); *p_buffer (uint8_t *)bh_read_file_to_buffer(wasm_file_name, p_size); wasm_runtime_free(wasm_file_name); return *p_buffer ! NULL; } static void module_destroyer_callback(uint8 *buffer, uint32 size) { if (!buffer) { return; } wasm_runtime_free(buffer); buffer NULL; }注意示例中的 reader 会根据运行模式解释器Wasm_Module_Bytecode/ AOTWasm_Module_AoT自动追加.wasm或.aot后缀这正是 README 中如果构建了 wamrc还会生成 aot 文件的配套逻辑。第二步创建大缓冲区让 WAMR 的所有分配都来自该内存池。static char sandbox_memory_space[10 * 1024 * 1024] { 0 };在初始化参数中指定Alloc_With_Pool分配模式init_args.mem_alloc_type Alloc_With_Pool; init_args.mem_alloc_option.pool.heap_buf sandbox_memory_space; init_args.mem_alloc_option.pool.heap_size sizeof(sandbox_memory_space);第三步组装完整流程——初始化、注册 reader、加载主模块、递归加载子模块、实例化、跨模块调用。/* 1. 初始化运行时 */ if (!wasm_runtime_full_init(init_args)) { printf(Init runtime environment failed.\n); goto EXIT; } /* 2. 注册 reader/destroyer必须启用 MULTI_MODULE */ wasm_runtime_set_module_reader(module_reader_callback, module_destroyer_callback); /* 3. 读取主模块 mC 的二进制 */ file_buf (uint8 *)bh_read_file_to_buffer(wasm_file, file_buf_size); /* 4. 加载 mCWAMR 依据 import section 自动加载 mA、mB */ module wasm_runtime_load(file_buf, file_buf_size, error_buf, sizeof(error_buf)); /* 5. 实例化 */ module_inst wasm_runtime_instantiate(module, stack_size, heap_size, error_buf, sizeof(error_buf)); /* 6. 跨模块调用C2 - mB.B1()C3 - mA.A1()C4 - mB.B2() - mA.A1() */ wasm_application_execute_func(module_inst, C1, 0, args); /* 返回 0x1f */ wasm_application_execute_func(module_inst, C2, 0, args); /* 返回 0x15 */ wasm_application_execute_func(module_inst, C3, 0, args); /* 返回 0xb */ wasm_application_execute_func(module_inst, C4, 0, args); /* 返回 0xb */运行示例见 README.md$ mkdir build cd build $ cmake .. make $ ./multi_module mC.wasm # 解释器模式 $ ./multi_module mC.aot # AOT 模式需预先用 wamrc 编译运行时输出验证了依赖链的正确性C2 经 mB.B1 返回 210x15C3 直接调用 mA.A1 返回 110xbC4 通过 mB.B2 间接调用 mA.A1 同样返回 110xb而 C5 因未导出调用以失败告终与代码注释预期完全一致。四、小结与适用边界要点结论加载机制load-time 动态链接WAMR 按主模块 import section 递归加载依赖编程模型WASI Command主_start()/ Reactor子_initialize()二者均须导出 memory构建开关WAMR_BUILD_MULTI_MODULE1默认关闭运行时宏为WASM_ENABLE_MULTI_MODULE宿主职责必须实现 module_reader / module_destroyer 回调并负责文件系统访问跨模块调用$submodule_name$function_name格式定位子模块导出函数模式支持解释器与 AOT 均支持模式由主模块类型决定多模块能力让 WAMR 在资源受限的嵌入式场景下也能实现库式代码复用与模块化拆分——宿主只需关注文件的读写运行时的依赖解析、注册与跨模块寻址全部由 WAMR 内部完成。若需深入验证源码行为可继续阅读 wasm_loader.c 中load_from_sections对 import 段与子模块的解析逻辑以及 wasm_runtime_common.c 中 reader/destroyer 与模块注册表的实现。【免费下载链接】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),仅供参考