uni-app 中跨端跑 C 库的 WebAssembly 实战:uasm 插件与 uni.loadUasm 完整教程
uni-app 中跨端跑 C 库的 WebAssembly 实战:uasm 插件与 uni.loadUasm 完整教程
【免费下载链接】uni-appA cross-platform framework using Vue.js项目地址: https://gitcode.com/gh_mirrors/un/uni-app
在 uni-app 里做 zstd 压缩、crypto 加密、FFmpeg 转码这类 CPU 重活,纯 JS 写不动;可一旦换成原生库,Android 要 so、iOS 要 framework、鸿蒙又要 so,小程序和 Web 端还得再准备一套 WebAssembly。uni-app 给出的答案是 uasm(UniAssembly)插件:一份 C/C++ 库,全平台统一用uni.loadUasm()加载调用。
三个平台的原生库,为什么没法直接复用
先说清痛点。每个 App 平台其实都支持二进制库,但形态各不相同:
- Android / 鸿蒙:
.so动态库 - iOS:framework
- Web / 小程序:WebAssembly 编译产物
同一份 C 代码,要在五六个运行时里分别打包、分别适配调用方式。而 JS 引擎侧也有差异:uni-app x 蒸汽模式在 Android 用 V8、iOS 用 JSCore、鸿蒙用 ArkTS/JSVM,三套引擎的 C API 互不相同。
所以问题不是"能不能用二进制库",而是怎么只用一份胶水代码对接所有运行时。
原理一句话:wasm 走 Web 生态,Node-API 走 Node 生态
uasm 插件把这件事拆成了两条路(详见 uasm 插件文档):
- Web / 小程序端:就是 WebAssembly 方案的封装,
.wasm加一层胶水 JS,直接复用 Web 生态; - App 端:基于一套统一的 Node-API,屏蔽 V8、JSCore、JSVM 的引擎差异,把 Node 的部分能力引入了手机端。
这意味着:一个 C 库只要已经有 wasm 版本或 Node 版本封装,就能直接变成 uasm 插件;两者都没有的话,用 AI 补一层胶水也不难。
前端调用只有两个 API:uni.loadUasm()异步加载全平台可用,uni.loadUasmSync()同步加载仅限 App 平台,接口定义见 uni.loadUasm API 文档。
三步把一个 C 库变成全平台插件 🛠️
第一步:准备 wasm 与 Node-API 胶水
Web 和小程序端用 emscripten 编译,注意小程序端要加-s DYNAMIC_EXECUTION=0(微信不支持new Function这类动态行为);支付宝端还受限于 worker 中才能使用,且不支持 BigInt,遇到0n语法要用-s WASM_BIGINT=0去掉。
App 端则是写一个binding.cc,按 Node-API(napi)规范导出函数,例如add和concat两个方法。
第二步:用 uni-gyp 编译 App 端产物
App 三端不手写 CMake,而是用官方工具 uni-gyp(基于 gyp-next,扩展了 Android/iOS/HarmonyOS 的 CMake 生成能力):
npm install --save-dev @dcloudio/uni-gyp写一个binding.gyp声明源文件和各平台条件,然后在 package.json 里配好编译命令,一条命令即可同时产出三端库并打包成 uni_module:
npm run build:test-uasm:all注意 iOS 的 xcframework 只能在 macOS 上编译。
第三步:放入 uni_modules 并在页面加载
最终插件目录长这样(以 zstd 为例):
uni_modules/uni-zstd/uasm/ ├── web/ # 胶水 js + .wasm ├── mp-weixin/ # 胶水 js + .wasm.br ├── mp-alipay/ ├── app-android/libs/ ├── app-ios/frameworks/ └── app-harmony/libs/页面里加载后直接调方法,返回的就是插件作者导出的方法集:
const lib = await uni.loadUasm("uni_modules/hello-uasm") lib.add(1, 2) // 3容易踩的 3 个坑 ⚠️
- 小程序优先用
.wasm.br:Brotli 压缩版体积更小;web 端.wasm和.wasm.br二选一,后者需要服务器支持对应的响应头。 - 模块要缓存,别反复加载:
uni.loadUasm()的结果存起来复用即可,每次调用都重新加载会白白增加开销。 - 支付宝小程序的 wasm 只能在 worker 里跑,需要在
workers/mp-alipay目录单独放入口文件做异步桥接,且必须真机验证。
另外,uasm 和 uts 插件可以共存于同一个 uni_modules,但定位不同:uts 面向原生能力封装(各平台代码不同),uasm 面向本来就跨平台的 C/Rust/Zig 库,JS 直通二进制、少一层桥接,性能也更高,而且真机调试不需要打自定义基座。
加载失败时先看错误码
App 端加载失败别急着重编,先按错误码定位,完整速查表在 uasm 插件文档的排查指南:
| 错误码 | 含义 | 优先检查 |
|---|---|---|
ERR_NATIVE_MODULE_RESOLVE_FAILED | 找不到模块位置 | 插件名、打包路径 |
ERR_NATIVE_MODULE_LOAD_FAILED | 动态库加载失败 | so 文件、依赖库、ABI 架构 |
ERR_NATIVE_MODULE_ENTRY_MISSING | 缺少模块入口 | 是否导出了 napi 入口函数 |
ERR_NATIVE_MODULE_VERSION_MISMATCH | Node-API 版本不兼容 | 模块与运行时版本 |
特别注意一条:把两个 so 放在同一目录不代表依赖能被找到。Failed to open native module后面跟的系统原因才是关键,not found+needed by说明是依赖库缺失,要检查依赖的 ABI 是否与应用进程一致,不能只看目录名。
适合封装成 uasm 的库都在这了
常见 C 生态里大量库已有 wasm 或 Node 版本,可以直接对接:sqlite、crypto、cmark、FFmpeg、mqtt、lame、Tesseract、Tree-sitter 等。选型时的判断标准很简单——
如果你的计算逻辑是纯 C/C++/Rust 写的、且各平台算法一致,就用 uasm;如果依赖平台原生 API(相机、地图、系统 UI),才去写 uts 插件。
下一步建议:拿你项目里最耗时的那段 JS 计算,先用 emscripten 编出 web 端 wasm 跑通uni.loadUasm()全链路,再补 App 端 uni-gyp 编译。全平台里 Web 端改动最小,是验证胶水代码正确性的最快路径。
【免费下载链接】uni-appA cross-platform framework using Vue.js项目地址: https://gitcode.com/gh_mirrors/un/uni-app
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考