我如何 return 来自 WebAssembly 函数的 JavaScript 字符串

How can I return a JavaScript string from a WebAssembly function

如何从 WebAssembly 函数中 return 一个 JavaScript 字符串?

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

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

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

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

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

  • exports,你从 JavaScript 调用 WebAssembly,WebAssembly returns 一个单一的值类型。
  • imports,其中 WebAssembly 调用 JavaScript,具有您想要的任意多的值类型(注意:计数必须在模块编译时已知,这不是数组并且不是' t可变参数)。
  • 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);

如果你的模块有一个 start function 那么它会在实例化时执行。否则你可能会有一个你称之为的出口,例如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() returns),但是要公开其他内容,例如 UTF-8,你需要使用支持 UTF 的 C++ 库-8,然后自己从 JavaScript 读取 UTF-8,获取代码点,然后使用 String.fromCodePoint.

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

您还可以将 TextDecoder API once it's available more widely in browsers by creating an ArrayBufferView 用于 WebAssembly.Memorybuffer(这是一个 ArrayBuffer)。


相反,如果你正在做一些事情,比如从 WebAssembly 登录到 JavaScript,那么你可以像上面那样公开 Memory,然后从 WebAssembly 声明一个调用 [=84] 的导入=] 大小 + 位置。您可以将模块实例化为:

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 中的对象来说这是不可能的。我们讨论了 adding GC support in the future.

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

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),几乎在该对象的顶部您将看到函数 AsciiToString。从 C++ 传递你的函数 returns 字符串,你将看到输出。对于这种情况,check out this library.

2020 更新

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

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

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

nbind - Magical headers that make your C++ library accessible from JavaScript

nbind is a set of headers that make your C++11 library accessible from JavaScript. With a single #include statement, your C++ compiler generates the necessary bindings without any additional tools. Your library is then usable as a Node.js addon or, if compiled to asm.js with Emscripten, directly in web pages without any plugins.

Embind is used to bind C++ functions and classes to JavaScript, so that the compiled code can be used in a natural way by “normal” JavaScript. Embind also supports calling JavaScript classes from C++.

查看以下 WebAssembly 提案:

The proposal adds a new set of interface types to WebAssembly that describe high-level values (like strings, sequences, records and variants) without committing to a single memory representation or sharing scheme. Interface types can only be used in the interfaces of modules and can only be produced or consumed by declarative interface adapters.

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

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

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

libsodium.js - The sodium crypto library compiled to WebAssembly and pure JavaScript using Emscripten, with automatically generated wrappers to make it easy to use in web applications.

另请参阅:

Wasmer is an open-source runtime for executing WebAssembly on the Server. Our mission is make all software universally available. We support running Wasm modules standalone in our runtime, but also can be embedded in multiple languages using our language integrations.

特别是 Wasmer-JS:

Wasmer-JS enables the use of server-side compiled WebAssembly Modules in Node.js and the Browser. The project is set up as mono-repo of multiple JavaScript packages.

Hacker News this article 中也有一些不错的信息。

鉴于:

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

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

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

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

我找到了一种 hack 方法,就像我们在混合应用程序中所做的那样,而且非常容易。

直接注入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