【问题标题】:How can I return a JavaScript string from a WebAssembly function如何从 WebAssembly 函数返回 JavaScript 字符串
【发布时间】:2017-05-12 05:24:52
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: javascript webassembly


    【解决方案1】:

    WebAssembly 本身并不支持字符串类型,而是支持 i32 / i64 / f32 / f64 value types 以及 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);
    

    如果您的模块有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() 返回的),但是要公开其他东西,比如 UTF-8,你需要使用支持 UTF-8 的 C++ 库,然后读取 UTF- 8 自己从 JavaScript 获取代码点,然后使用 String.fromCodePoint

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

    一旦TextDecoder API 在浏览器中更广泛地可用,您也可以使用它,方法是在WebAssembly.Memorybuffer(即ArrayBuffer)中创建一个ArrayBufferView


    相反,如果您正在执行类似从 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);
            }
        }
    });
    

    需要注意的是,如果您曾经增加内存(通过 JavaScript 使用 Memory.prototype.grow,或使用 grow_memory 操作码),那么 ArrayBuffer 会被绝育,您需要重新创建它。


    关于垃圾收集:WebAssembly.Module / WebAssembly.Instance / WebAssembly.Memory 都是 JavaScript 引擎收集的垃圾,但这是一个相当大的锤子。您可能想要 GC 字符串,而目前对于位于 WebAssembly.Memory 中的对象来说这是不可能的。我们已经讨论过adding GC support in the future

    【讨论】:

    • 也可以使用TextDecoder api将UTF-8 Uint8Array解码成字符串:developer.mozilla.org/en-US/docs/Web/API/TextDecoder/decode
    • 在邻居问题中:Module.AsciiToString(ptr)
    • 如何从 C/C++ 实际访问这个内存/缓冲区?
    • @DanielFekete WebAssembly.Memory 是常规的 C++ 堆。您传递的 i32 是指向该堆的常规 C++ 指针。
    • 这太不可思议了。 WebAssembly 被誉为互联网的未来,它甚至不能处理字符串。 Javascript 如此受欢迎的一大原因是它的字符串处理能力。我现在明白为什么 WebAssembly 如此受欢迎,没有人在现实世界的场景中使用它。这只是开发人员在等待分配实际工作时玩的东西。
    【解决方案2】:

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

    有关详细信息和详细说明,请参阅:

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

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

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

    另见:

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

    特别是Wasmer-JS:

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

    Hacker News 上的this article 也有一些很好的信息。

    【讨论】:

      【解决方案3】:

      给定:

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

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

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

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

      【讨论】:

      【解决方案4】:

      我找到了一种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

      【讨论】:

      【解决方案5】:

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

      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++ 传递您的函数,您将看到输出。对于这种情况,check out this library

      【讨论】:

      • AsciiToString 似乎是 Emscripten 的东西。
      猜你喜欢
      • 1970-01-01
      • 2018-05-11
      • 2022-10-14
      • 2011-04-14
      • 1970-01-01
      • 2010-12-23
      • 2016-11-17
      相关资源
      最近更新 更多