如何从WebAssembly函数返回JavaScript字符串

32 javascript webassembly

如何从WebAssembly函数返回JavaScript字符串?

以下模块可以用C(++)编写吗?

export function foo() {
  return 'Hello World!';
}
Run Code Online (Sandbox Code Playgroud)

另外:我可以将它传递给JS引擎进行垃圾回收吗?

JF *_*ien 46

WebAssembly本身不支持字符串类型,它支持i32/ i64/ f32/ f64 值类型以及i8/ i16用于存储.

您可以使用以下方法与WebAssembly实例进行交互:

  • exports,从JavaScript调用WebAssembly,WebAssembly返回单个值类型.
  • imports 其中WebAssembly调用JavaScript,使用尽可能多的值类型(注意:必须在模块编译时知道计数,这不是数组而且不是可变参数).
  • Memory.buffer,这是一个ArrayBuffer可以使用(其中)索引Uint8Array.

这取决于你想做什么,但似乎直接访问缓冲区是最简单的:

const bin = ...; // WebAssembly binary, I assume below that it imports a memory from module "imports", field "memory".
const module = new WebAssembly.Module(bin);
const memory = new WebAssembly.Memory({ initial: 2 }); // Size is in pages.
const instance = new WebAssembly.Instance(module, { imports: { memory: memory } });
const arrayBuffer = memory.buffer;
const buffer = new Uint8Array(arrayBuffer);
Run Code Online (Sandbox Code Playgroud)

如果你的模块有一个start函数,那么它会在实例化时执行.否则你可能会有一个你打电话的出口,例如instance.exports.doIt().

一旦完成,您需要在内存中获取字符串大小+索引,您还将通过导出公开:

const size = instance.exports.myStringSize();
const index = instance.exports.myStringIndex();
Run Code Online (Sandbox Code Playgroud)

然后你将它从缓冲区中读出来:

let s = "";
for (let i = index; i < index + size; ++i)
  s += String.fromCharCode(buffer[i]);
Run Code Online (Sandbox Code Playgroud)

请注意,我正在从缓冲区读取8位值,因此我假设字符串是ASCII.这就是std::string给你的东西(内存中的索引会.c_str()返回),但是要暴露其他东西,比如UTF-8,你需要使用支持UTF-8的C++库,然后自己从JavaScript读取UTF-8,获取代码点,并使用String.fromCodePoint.

你也可以依赖于以null结尾的字符串,我在这里没有这样做.

您也可以使用TextDecoderAPI,一旦它获得更广泛的浏览器中创建一个ArrayBufferViewWebAssembly.Memorybuffer(这是ArrayBuffer).


相反,如果您正在执行从WebAssembly到JavaScript的操作,那么您可以公开Memory上面的内容,然后从WebAssembly声明一个调用JavaScript的大小+位置的导入.您可以将模块实例化为:

const memory = new WebAssembly.Memory({ initial: 2 });
const arrayBuffer = memory.buffer;
const buffer = new Uint8Array(arrayBuffer);
const instance = new WebAssembly.Instance(module, {
    imports: {
        memory: memory,
        logString: (size, index) => {
            let s = "";
            for (let i = index; i < index + size; ++i)
                s += String.fromCharCode(buffer[i]);
            console.log(s);
    }
});
Run Code Online (Sandbox Code Playgroud)

这有一个警告,如果你增长了内存(通过JavaScript使用Memory.prototype.grow或使用grow_memory操作码),那么ArrayBuffer获取绝对,你需要重新创建它.


垃圾收集器:WebAssembly.Module/ WebAssembly.Instance/ WebAssembly.Memory是由JavaScript引擎收集到的所有垃圾,但是这是一个相当大的锤子.您可能想要GC字符串,而这对于居住在其中的对象来说是不可能的WebAssembly.Memory.我们已经讨论了将来添加GC支持.

  • 您还可以使用`TextDecoder` api将UTF-8 Uint8Array解码为字符串:https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/decode (2认同)
  • 如何从C/C++实际访问此内存/缓冲区? (2认同)

rsp*_*rsp 16

2020 更新

自从发布其他答案以来,情况发生了变化。

今天我将赌 WebAssembly 接口类型 - 见下文。

由于您专门询问了 C++,请参阅:

nbind - 神奇的头文件,让你的 C++ 库可以从 JavaScript 访问

nbind 是一组头文件,使您的 C++11 库可以从 JavaScript 访问。使用单个 #include 语句,您的 C++ 编译器无需任何其他工具即可生成必要的绑定。然后,您的库可用作 Node.js 插件,或者,如果使用 Emscripten 编译为 asm.js,则无需任何插件即可直接在网页中使用。

Embind 用于将 C++ 函数和类绑定到 JavaScript,以便编译后的代码可以被“正常”的 JavaScript 以自然的方式使用。Embind 还支持从 C++ 调用 JavaScript 类。

请参阅以下 WebAssembly 提案:

该提案向 WebAssembly 添加了一组新的接口类型,用于描述高级值(如字符串、序列、记录和变体),而无需承诺单一的内存表示或共享方案。接口类型只能在模块的接口中使用,并且只能由声明性接口适配器产生或使用。

有关更多信息和很好的解释,请参阅:

您已经可以将它与一些实验性功能一起使用,请参阅:

有关使用另一种方法的良好现实示例,请参阅:

libsodium.js - 使用 Emscripten 编译为 WebAssembly 和纯 JavaScript 的钠加密库,具有自动生成的包装器,使其易于在 Web 应用程序中使用。

也可以看看:

Wasmer 是用于在服务器上执行 WebAssembly 的开源运行时。我们的使命是让所有软件普遍可用。我们支持在我们的运行时中独立运行 Wasm 模块,但也可以使用我们的语言集成嵌入多种语言。

特别是Wasmer-JS

Wasmer-JS 支持在 Node.js 和浏览器中使用服务器端编译的 WebAssembly 模块。该项目被设置为多个 JavaScript 包的单一存储库。

这篇关于 Hacker News 的文章中也有一些很好的信息。


Vla*_*eev 6

鉴于:

  • mem,WebAssembly.Memory对象(来自模块导出)
  • p, 字符串第一个字符的地址
  • len,字符串的长度(以字节为单位),

您可以使用以下方法读取字符串:

let str = (new TextDecoder()).decode(new Uint8Array(mem.buffer, p, len));
Run Code Online (Sandbox Code Playgroud)

这假设字符串是 UTF-8 编码的。