从 JavaScript 调用本地函数

serverless 技术社区 2021-10-19 16:06:39
加精

 

WasmEdge 让 JavaScript 可以在共享库调用本地函数。

在前两篇文章中,我解释了为什么以及如何在 WebAssembly 沙箱中运行 JavaScript 程序。同时,还讨论了如何使用 Rust 为 WasmEdge 创建自定义 JavaScript AP。

但是,为了完全访问底层系统的操作系统和硬件功能,我们有时需要为基于 C 的本机函数创建 JavaScript API。 也就是说,当 JavaScript 程序调用预定义的函数时,WasmEdge 会将其传递给 OS 上的原生共享库执行。

本文中,我们将向你展示如何做到这一点。我们将创建以下两个组件。

  • 一个定制的 WasmEdge runtime,允许 WebAssembly 函数调用外部原生函数。

  • 一个定制的 QuickJS 解释器,用于解析 JavaScript 中的函数调用,并将外部函数调用传递给 WebAssembly,后者又将它们传递给原生函数调用。

为了能够 follow 这个例子,你需要 fork 或克隆 wasmedge-quickjs repo。示例在该Repo的 examples/host_function 文件夹。

$ git clone https://github.com/second-state/wasmedge-quickjs/

将一个基于 C 的函数嵌入 WasmEdge

首先,我们将向 WasmEdge runtime 添加一个基于 C 的函数,以便我们的 JavaScript 程序可以稍后调用它。我们使用 WasmEdge C API 创建一个 HostInc 函数,然后将其注册为 host_inc

wasmedge_c/demo_wasmedge.c 文件包含 host 函数的完整源代码和其在 WasmEdge 的注册。

#include <stdio.h>
#include "wasmedge.h"

WasmEdge_Result HostInc(void *Data, WasmEdge_MemoryInstanceContext *MemCxt, const WasmEdge_Value *In, WasmEdge_Value *Out) {
  int32_t Val1 = WasmEdge_ValueGetI32(In[0]);
  printf("Runtime(c)=> host_inc call : %d\n",Val1 + 1);
  Out[0] = WasmEdge_ValueGenI32(Val1 + 1);
  return WasmEdge_Result_Success;
}

// mapping dirs
char* dirs = ".:..\0";
  
int main(int Argc, const char* Argv[]) {
  /* Create the configure context and add the WASI support. */
  /* This step is not necessary unless you need WASI support. */
  WasmEdge_ConfigureContext *ConfCxt = WasmEdge_ConfigureCreate();
  WasmEdge_ConfigureAddHostRegistration(ConfCxt, WasmEdge_HostRegistration_Wasi);
  /* The configure and store context to the VM creation can be NULL. */
  WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(ConfCxt, NULL);
  WasmEdge_ImportObjectContext *WasiObject = WasmEdge_VMGetImportModuleContext(VMCxt, WasmEdge_HostRegistration_Wasi);
  WasmEdge_ImportObjectInitWASI(WasiObject,Argv+1,Argc-1,NULL,0,&dirs,1,NULL,0);
  
  /* Create the import object. */
  WasmEdge_String ExportName = WasmEdge_StringCreateByCString("extern");
  WasmEdge_ImportObjectContext *ImpObj = WasmEdge_ImportObjectCreate(ExportName, NULL);
  enum WasmEdge_ValType ParamList[1] = { WasmEdge_ValType_I32 };
  enum WasmEdge_ValType ReturnList[1] = { WasmEdge_ValType_I32 };
  WasmEdge_FunctionTypeContext *HostFType = WasmEdge_FunctionTypeCreate(ParamList, 1, ReturnList, 1);
  WasmEdge_HostFunctionContext *HostFunc = WasmEdge_HostFunctionCreate(HostFType, HostInc, 0);
  WasmEdge_FunctionTypeDelete(HostFType);
  WasmEdge_String HostFuncName = WasmEdge_StringCreateByCString("host_inc");
  WasmEdge_ImportObjectAddHostFunction(ImpObj, HostFuncName, HostFunc);
  WasmEdge_StringDelete(HostFuncName);
  
  WasmEdge_VMRegisterModuleFromImport(VMCxt, ImpObj);
  
  /* The parameters and returns arrays. */
  WasmEdge_Value Params[0];
  WasmEdge_Value Returns[0];
  /* Function name. */
  WasmEdge_String FuncName = WasmEdge_StringCreateByCString("_start");
  /* Run the WASM function from file. */
  WasmEdge_Result Res = WasmEdge_VMRunWasmFromFile(VMCxt, Argv[1], FuncName, Params, 0, Returns, 0);
  
  if (WasmEdge_ResultOK(Res)) {
    printf("\nRuntime(c)=> OK\n");
  } else {
    printf("\nRuntime(c)=> Error message: %s\n", WasmEdge_ResultGetMessage(Res));
  }
  
  /* Resources deallocations. */
  WasmEdge_VMDelete(VMCxt);
  WasmEdge_ConfigureDelete(ConfCxt);
  WasmEdge_StringDelete(FuncName);
  return 0;
}

你可以使用一个标准的 C 编译器,如 GCC,来编译 C 源代码。

#build custom webassembly Runtime
$ cd wasmedge_c

#build a custom Runtime
wasmedge_c/$ gcc demo_wasmedge.c -lwasmedge_c -o demo_wasmedge

编译器生成一个二进制码可执行文件 demo_wasmedge ,用于定制化的含有 host 函数的 WasmEdge runtime 版本。

创建一个 Rust 模块来将函数 bind 到 JavaScript

接下来,我们需要在 Rust 中创建一个定制的 JavaScript 解释器。它解释对 host_inc 的 JavaScript 调用,并通过自定义的 WasmEdge runtime (demo_wasmedge) 将调用定向到本地 C 函数。src/main.rs 文件有完整的 Rust 源代码,用于注册外部函数。

mod host_extern {
    use quickjs_rs_wasi::{Context, JsValue};

    #[link(wasm_import_module = "extern")]
    extern "C" {
        pub fn host_inc(v: i32) -> i32;
    }

    pub struct HostIncFn;
    impl quickjs_rs_wasi::JsFn for HostIncFn {
        fn call(ctx: &mut Context, _this_val: JsValue, argv: &[JsValue]) -> JsValue {
            if let Some(JsValue::Int(i)) = argv.get(0) {
                unsafe {
                    let r = host_inc(*i);
                    r.into()
                }
            } else {
                ctx.throw_type_error("'v' is not a int").into()
            }
        }
    }
}

use quickjs_rs_wasi::*;

fn main() {
    let mut ctx = Context::new();
    let f = ctx.new_function::<host_extern::HostIncFn>("host_inc");
    ctx.get_global().set("host_inc", f.into());
    
    // Run the embedded JavaScript
    ctx.eval_global_str("print('js=> host_inc(2)=',host_inc(2))");
}

Rust 程序创建一个定制的 QuickJS 解释器,然后执行一个 JavaScript 程序,该程序依次调用在 WasmEdge runtime 中注册的基于 C 的本地函数。

$ cargo build --target wasm32-wasi --release

带有嵌入的 JavaScript 程序的定制 QuickJS 解释器可以在 target/wasm32-wasi/release/quickjs-rs-wasi.wasm 查看。

调用 JavaScript 函数

嵌入的 JavaScript 程序调用 host_inc() 函数。 JavaScript 解释器(host_function.wasm 的 Rust 程序)将此调用路由到 WebAssembly host_inc() 调用。定制的 WasmEdge 运行时(demo_wasmedge 的 C 程序)将 WebAssembly 调用路由到本机 C 函数。

print('js=> host_inc(2)=',host_inc(2))

当然,你也可以编写一个从文件中读取 JavaScript 的通用 Rust 程序。

要运行此 JavaScript,你需要在我们定制的 WasmEdge Runtime 中使用我们定制的 QuickJS 解释器。解释器和运行时都经过检测以支持 host_inc 本机函数调用。

$ cd wasmedge_c
$ export LD_LIBRARY_PATH=.
$ ./demo_wasmedge --dir .:. ../target/wasm32-wasi/release/host_function.wasm
js=> host_inc(2)= 3

接下来

上面这个简单的例子展示了如何将一个基于 C 的原生函数变为一个 JavaScript API。你可以使用同样的方式添加很多本地 API 到 JavaScript。非常期待你的绝妙点子!

云原生 WebAssembly 中的 JavaScript 是下一代云和边缘计算基础设施中的新兴领域。 我们也是刚刚起步!如果你也感兴趣,请加入我们的 WasmEdge 项目(或通过提出 feature request issue 告诉我们你的需求)。

...全文
1750 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

217

社区成员

发帖
与我相关
我的任务
社区描述
Serverless 技术爱好者的聚集地
其他 企业社区
社区管理员
  • serverless 技术社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧