从 JavaScript 调用本地函数(javascript的函数调用有哪几种方式)

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 告诉我们你的需求)。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注