您的位置:  首页 > 技术 > 前端 > 正文

从 JavaScript 调用本地函数 | 用 WasmEdge 运行JavaScript 程序

2021-10-19 18:00 OSCHINA WasmEdge 次阅读 条评论

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

  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接