微信小程序调用 WebAssembly 烹饪指南

我们都是在夜里崩溃过的俗人,所幸终会天亮。明天就是新的开始,我们会变得与昨天不同。

一、Rust 导出 wasm

参考 wasm-bindgen 官方指南 https://wasm.rust-lang.net.cn/wasm-bindgen/introduction.html

wasm-bindgen,这是一个 Rust 库和 CLI 工具,它可以促进 wasm 模块和 JavaScript 之间的高级交互。

1、创建一个 wasm 项目

bash 复制代码
# 使用模板生成
# cargo generate --git https://gitee.com/tgodfather/wasm-pack-template
cargo generate --git https://github.com/rustwasm/wasm-pack-template

2、添加 js-sys 依赖

bash 复制代码
cargo add js-sys 

3、修改 lib.rs

定义三个外部函数,它们分别对应于 JavaScript 中的 console.logconsole.errorwx.showModal。通过使用 wasm_bindgen,这些函数可以在 Rust 代码中被调用,从而实现与 JavaScript 的交互。

rust 复制代码
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);

    #[wasm_bindgen(js_namespace = console)]
    fn error(s: &str);

    #[wasm_bindgen(js_namespace = wx)]
    fn showModal(param: &Object);
}

使用 rust 分别调用这三个函数,导出为 wasm 函数,

bash 复制代码
#[wasm_bindgen]
pub fn rs_log() {
    log("log");
}

#[wasm_bindgen]
pub fn rs_error() {
    log("error");
}

#[wasm_bindgen]
pub fn rs_show_modal() {
    // 创建一个 JavaScript 对象
    let options = Object::new();

    // 设置对象的属性
    Reflect::set(
        &options,
        &JsValue::from_str("title"),
        &JsValue::from_str("提示"),
    )
    .unwrap();
    Reflect::set(
        &options,
        &JsValue::from_str("content"),
        &JsValue::from_str("这是一个模态弹窗"),
    )
    .unwrap();

    // 创建回调函数
    let success_callback = Closure::wrap(Box::new(|res: JsValue| {
        let confirm = Reflect::get(&res, &JsValue::from_str("confirm"))
            .unwrap()
            .as_bool()
            .unwrap_or(false);
        let cancel = Reflect::get(&res, &JsValue::from_str("cancel"))
            .unwrap()
            .as_bool()
            .unwrap_or(false);

        if confirm {
            log("用户点击确定");
        } else if cancel {
            log("用户点击取消");
        }
    }) as Box<dyn FnMut(_)>);

    // 将回调函数添加到对象中
    Reflect::set(
        &options,
        &JsValue::from_str("success"),
        success_callback.as_ref().unchecked_ref(),
    )
    .unwrap();

    // 为了避免回调被回收,必须调用 `forget`
    success_callback.forget();

    // 调用 JavaScript 的 `showModal` 函数
    showModal(&options);
}

补充:小程序官方弹窗示例代码,

javascript 复制代码
wx.showModal({
  title: '提示',
  content: '这是一个模态弹窗',
  success (res) {
    if (res.confirm) {
      console.log('用户点击确定')
    } else if (res.cancel) {
      console.log('用户点击取消')
    }
  }
})

4、编译打包 wasm

bash 复制代码
wasm-pack build --target web

可以看到在 pkg 目录下生成了我们需要用到的 mywasm_bg.wasm、mywasm.js 。

二、对应小程序相关改动(Rust)

对于微信小程序,直接编译打包后的包无法直接调用,所以还需要进行一些代码修改。

WXWebAssembly | 微信开放文档

1、新增目录

新增 workers 目录:与 pages 同级,创建 workers 目录,用于存放 .wasm 文件

workers 目录只存放 mywasm_bg.wasm,便于把.wasm打包进去,以及分包打包

新增 pages/worker 目录:用于进行打包文件的调用

pages/worker目录只存放 .js ,这个文件包含了一些调用 .wasm 文件的方法

2、修改 mywasm.js 胶水代码

rust 复制代码
#改动点1、注释原 __wbg_load 方法逻辑,替换使用以下代码

async function __wbg_load(module, imports) {
  if (typeof Response === 'function' && module instanceof Response) {
    const bytes = await module.arrayBuffer();
    return await instantiateArrayBuffer(bytes, imports);
  } else {
    return await instantiateArrayBuffer(module, imports);
  }
}
rust 复制代码
# 改动点2、手动指定 WebAssembly 模块的路径

// 手动指定 WebAssembly 模块的路径
const wasmModulePath = '/workers/mywasm_bg.wasm';

async function instantiateArrayBuffer(binaryFile, imports) {
  return WXWebAssembly.instantiate(wasmModulePath, imports)
    .then(function(instance) {
      return instance;
    })
    .catch(function(reason) {
      console.error('Failed to asynchronously prepare wasm: ' + reason);
      throw reason;
    });
}
javascript 复制代码
# 改动点3、手动指定 WebAssembly 模块的路径

if (typeof module_or_path === 'undefined') {
    // module_or_path = new URL('mywasm_bg.wasm',
    //   import.meta.url);
    module_or_path =  wasmModulePath;
  }


if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
    // 需要使用 wx.request 替代 fetch      
    // module_or_path = fetch(module_or_path);
    module_or_path =  wasmModulePath;
  }

3、调用 wasm

javascript 复制代码
import init,{ rs_log ,rs_error,rs_show_modal}  from '../worker/mywasm.js';
javascript 复制代码
  onLoad: async function () {
    try {
      await init();
      // 使用 wasmModule 中的导出函数
      rs_log();
      rs_error();
      rs_show_modal();
    } catch (e) {
      console.error('Failed to load WASM module:', e);
    }
  }

4、运行效果

从控制台日志输出可以看到,wasm 导出的函数运行成功。

三、C/C++ 导出 wasm

1、创建一个 hellojs.cpp

cpp 复制代码
#include <stdio.h>
#include <emscripten/emscripten.h>
 
int main(int argc, char ** argv) {
    printf("Hello World\n");
}
 
#ifdef __cplusplus
extern "C" {
#endif
 
int EMSCRIPTEN_KEEPALIVE myFunction(int argc, char ** argv) {
  printf("我的函数已被调用\n");
}
 
#ifdef __cplusplus
}
#endif

2、使用 emcc 编译打包

bash 复制代码
 # emcc -o hellojs.html hellojs.cpp -O3 -s WASM=1 -s NO_EXIT_RUNTIME=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']" --shell-file html_template/shell_minimal.html
 
emcc -o hellojs.html hellojs.cpp -O3 -s WASM=1 -s NO_EXIT_RUNTIME=1 -s "EXPORTED_RUNTIME_METHODS=['ccall']" --shell-file html_template/shell_minimal.html

成功生成我们所需要的 hellojs.js、hellojs.wasm 文件。

四、对应小程序相关改动(C++)

1、新增目录

新增 workers 目录:与 pages 同级,创建 workers 目录,用于存放 .wasm 文件

workers 目录只存放 mywasm_bg.wasm,便于把.wasm打包进去,以及分包打包

新增 pages/worker 目录:用于进行打包文件的调用

pages/worker目录只存放 .js ,这个文件包含了一些调用 .wasm 文件的方法

2、修改 hellojs.js 胶水代码

javascript 复制代码
# 改动1、在文件的最底部添加
module.exports = {
    Module: Module
}
javascript 复制代码
# 改动2、注释代码
if(ENVIRONMENT_IS_WORKER) {
        // scriptDirectory=self.location.href
    }
javascript 复制代码
# 改动3、修改instantiateArrayBuffer函数

// function instantiateArrayBuffer(binaryFile, imports, receiver) {
//     return getBinaryPromise(binaryFile).then(binary=>WebAssembly.instantiate(binary, imports)).then(receiver, reason=> {
//             err(`failed to asynchronously prepare wasm: ${reason}`); abort(reason)
//         })
// }
function instantiateArrayBuffer(binaryFile, imports, receiver) {
  return WXWebAssembly.instantiate('/workers/hellojs.wasm', imports)
      .then(function(instance) {
          return instance;
      })
      .then(receiver, function(reason) {
          err('failed to asynchronously prepare wasm: ' + reason);

          // Warn on some common problems.
          if (isFileURI(wasmBinaryFile)) {
              err('warning: Loading from a file URI (' + wasmBinaryFile + ') is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing');
          }
          abort(reason);
      })
}
javascript 复制代码
# 改动4、将js文件中的所有WebAssembly修改为WXWebAssembly

function abort(what) {
    Module["onAbort"]?.(what);
    what="Aborted("+what+")";
    err(what);
    ABORT=true;
    EXITSTATUS=1;
    what+=". Build with -sASSERTIONS for more info.";
    //var e=new WebAssembly.RuntimeError(what);
    var e=new WXWebAssembly.RuntimeError(what);
    throw e
}

function instantiateAsync(binary, binaryFile, imports, callback) {
  if( !binary&&typeof WXWebAssembly.instantiateStreaming=="function" && !isDataURI(binaryFile)&& !isFileURI(binaryFile)&& !ENVIRONMENT_IS_NODE&&typeof fetch=="function") {
      return fetch(binaryFile, {
          credentials:"same-origin"

      }).then(response=> {
          var result=WXWebAssembly.instantiateStreaming(response, imports); return result.then(callback, function(reason) {
                  err(`wasm streaming compile failed: ${reason}`); err("falling back to ArrayBuffer instantiation"); return instantiateArrayBuffer(binaryFile, imports, callback)
              })
      })
}

return instantiateArrayBuffer(binaryFile, imports, callback)
}

3、调用 wasm

javascript 复制代码
# 引入文件
const hellojs_wasm = require('../worker/hellojs');
javascript 复制代码
  onReady() {
    const moudule = hellojs_wasm.Module
    // moudule._myFunction(1,"");
    moudule.ccall(
      "myFunction", // name of C function
      null, // return type
      null, // argument types
      null,
    ); // arguments

  },

4、运行效果

从控制台日志输出可以看到,wasm 导出的函数运行成功。

五、参考资料

基于 Rust 的 Wasm 开发探索与实践_rust wasm-CSDN博客

基于 Rust 的 Wasm/Wasi 开发探索与实践(Linux开发环境)_wasi安装-CSDN博客

基于 Emscripten + OpenXLSX 实现浏览器操作 Excel_使用webassembly在浏览器端操作excel-CSDN博客

相关推荐
七夜zippoe2 天前
WebAssembly与Python:在浏览器中运行Python
开发语言·python·wasm·webassembly·pyscript
爱学习的程序媛2 天前
【Web前端】WebAssembly详解
前端·web·wasm
爱学习的程序媛5 天前
【Web前端】WebAssembly实战项目
前端·web·wasm
REDcker5 天前
Wasm 软解 H.265 方案与原理
wasm·h.265
步步为营DotNet12 天前
ASP.NET Core 10中的Blazor WebAssembly性能优化实践
性能优化·asp.net·wasm
前端之虎陈随易12 天前
Vite 8正式发布,内置devtool,Wasm SSR 支持
前端·人工智能·typescript·npm·node.js·wasm
古城小栈15 天前
Rust 开发 WebAssembly 一眼案例
开发语言·rust·wasm
csdn_aspnet15 天前
.NET 10 中的 Blazor:新增功能及常见问题
wasm·blazor·.net10
zhojiew1 个月前
使用envoy配置jwt校验和ratelimit限流以及通过wasm扩展统计llm消耗token
wasm·envoy