WasmEdge 让 JavaScript 可以在共享库调用本地函数。
在前三篇文章中,我解释了为什么以及如何在 WebAssembly 沙箱中运行 JavaScript 程序。同时,还讨论了如何使用 Rust 为 WasmEdge 创建自定义 JavaScript AP。
但是,为了完全访问底层系统的操作系统和硬件功能,我们有时需要为基于 C 的本机函数创建 JavaScript API。 也就是说,当 JavaScript 程序调用预定义的函数时,WasmEdge 会将其传递给 OS 上的原生共享库执行。
本文中,我们将向你展示如何做到这一点。我们将创建以下两个组件。
examples/host_function
文件夹。$ git clone https://github.com/second-state/wasmedge-quickjs/
首先,我们将向 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 中创建一个定制的 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 程序调用 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 告诉我们你的需求)。
|