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

IT技术 javascript webassembly
2021-01-17 14:36:35

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

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

export function foo() {
  return 'Hello World!';
}

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

5个回答

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

您可以使用以下方式与 WebAssembly 实例交互:

  • exports,从 JavaScript 调用 WebAssembly,WebAssembly 返回单个值类型。
  • imports WebAssembly 调用 JavaScript 的地方,可以使用任意数量的值类型(注意:必须在module编译时知道计数,这不是数组,也不是可变参数)。
  • 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);

如果您的module有一个start函数,那么它会在实例化时执行。否则,您可能会有一个您调用的导出,例如instance.exports.doIt().

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

const size = instance.exports.myStringSize();
const index = instance.exports.myStringIndex();

然后你会从缓冲区中读取它:

let s = "";
for (let i = index; i < index + size; ++i)
  s += String.fromCharCode(buffer[i]);

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

您还可以依赖以空字符结尾的字符串,我在这里没有这样做。

一旦TextDecoderAPI在浏览器中更广泛地可用,您也可以使用该API,方法是ArrayBufferViewWebAssembly.Memory's buffer(即ArrayBuffer)中创建 。


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

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);
        }
    }
});

这有一个警告,如果你曾经增加内存(通过 JavaScript 使用Memory.prototype.grow,或使用grow_memory操作码),那么它ArrayBuffer会被阉割,你需要重新创建它。


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

这是令人难以置信。WebAssembly 被誉为互联网的未来,它甚至无法处理字符串。Javascript 如此流行的一个重要原因是它的字符串处理能力。我现在明白为什么 WebAssembly 如此受欢迎,没有人在现实世界的场景中使用它。这只是开发人员在等待分配实际工作时玩的东西。
2021-03-17 14:36:35
您还可以使用TextDecoderapi 将 UTF-8 Uint8Array 解码为字符串:developer.mozilla.org/en-US/docs/Web/API/TextDecoder/decode
2021-03-21 14:36:35
如何从 C/C++ 实际访问这个内存/缓冲区?
2021-03-28 14:36:35
在邻居问题中: Module.AsciiToString(ptr)
2021-04-03 14:36:35
@DanielFeketeWebAssembly.Memory是常规的 C++ 堆。i32你绕过是一个普通的C ++指针到该堆。
2021-04-07 14:36:35

2020 更新

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

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

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

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

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

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

请参阅以下 WebAssembly 提案:

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

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

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

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

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

也可以看看:

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

特别是Wasmer-JS

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

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

鉴于:

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

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

let str = (new TextDecoder()).decode(new Uint8Array(mem.buffer, p, len));

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

好的!Emscripten 在github.com/emscripten-core/emscripten/blob/cbc9742/src/... 中的作用完全相同字符串的长度是通过扫描空字符来确定的。
2021-03-29 14:36:35

我找到了一种 hack 方式,就像我们在 hybird appication 方式中所做的一样,而且非常容易。

只需注入window.alert函数,然后将其放回:

let originAlert = window.alert;
window.alert = function(message) {
    renderChart(JSON.parse(message))
};
get_data_from_alert();
window.alert = originAlert;

和本机方面,只是:

// Import the `window.alert` function from the Web.
#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

...

pub fn get_data_from_alert() {
    alert(CHART_DATA);
}

你可以在我的 GitHub 示例中看到:https : //github.com/phodal/rust-wasm-d3js-sample

请随意参考本指南如果您在最小的可重现示例中包含至少一个简短的解释,通常会更有帮助
2021-04-12 14:36:35

有一种更简单的方法可以做到这一点。首先,您需要二进制文件的实例:

const module = new WebAssembly.Module(bin);
const memory = new WebAssembly.Memory({ initial: 2 });
const instance = new WebAssembly.Instance(module, { imports: { memory: memory } });

然后,如果你运行console.log(instance),几乎在这个对象的顶部你会看到 function AsciiToString从 C++ 传递返回字符串的函数,您将看到输出。对于这种情况,请查看此库

AsciiToString 似乎是 Emscripten 的事情。
2021-03-30 14:36:35