使用 JavaScript 创建 WebAssembly 模块实例

这是 3 部分系列中的第 1 篇文章

  1. 使用 JavaScript 创建 WebAssembly 模块实例
  2. WebAssembly 中的内存(以及为什么它比你想象的更安全)
  3. WebAssembly 表导入… 它们是什么?

WebAssembly 是一种 在 Web 上运行代码的新方式。有了它,你可以用 C 或 C++ 等语言编写模块并在浏览器中运行它们。

目前,模块还不能独立运行。预计这种情况会随着 ES 模块支持在浏览器中的出现而改变。一旦到位,WebAssembly 模块将 很可能以与其他 ES 模块相同的方式加载,例如使用 <script type="module">

但目前,你需要使用 JavaScript 启动 WebAssembly 模块。这将创建一个模块实例。然后你的 JavaScript 代码可以调用该 WebAssembly 模块实例上的函数。

例如,让我们看看 React 如何实例化一个 WebAssembly 模块。(你可以在这个视频中了解有关 React 如何使用 WebAssembly 的更多信息。)

当用户加载页面时,它将以相同的方式开始。

浏览器将下载 JS 文件。此外,还会获取一个 .wasm 文件。其中包含 WebAssembly 代码,它是二进制的。

Browser downloading a .js file and a .wasm file

我们需要加载这些文件中的代码才能运行它。首先是 .js 文件,它加载 React 的 JavaScript 部分。然后该 JavaScript 将创建一个 WebAssembly 模块的实例… 协调器。

为此,它将调用 WebAssembly.instantiate

React.js robot calling WebAssembly.instantiate

让我们仔细看看它。

我们传递给 WebAssembly.instantiate 的第一件事将是我们从 .wasm 文件中获取的二进制代码。这就是模块代码。

因此,我们将二进制文件提取到一个缓冲区中,然后将其传递进去。

Binary code being passed in as the source parameter to WebAssembly.instantiate

引擎将开始将模块代码编译成特定于其运行的机器的东西。

但我们不想在主线程上执行此操作。我之前说过主线程就像一个全栈开发人员,因为它处理 JavaScript、DOM 和布局。我们不想在编译模块时阻塞主线程。因此,WebAssembly.instantiate 返回的是一个承诺。

Promise being returned as module compiles

这使得主线程可以回到其他工作。主线程知道一旦编译器完成编译此模块,它将通过承诺收到通知。该承诺将为它提供实例。

但是,编译后的模块并不是创建实例所需的唯一东西。我认为模块就像一本说明书。

实例就像一个试图用说明书制作东西的人。为了制作该东西,他们还需要原材料。他们需要可以操作的东西。

Instruction book next to WebAssembly robot

这就是 WebAssembly.instantiate 的第二个参数的用武之地。这就是导入对象。

Arrow pointing to importObject param of WebAssembly.instantiate
我认为导入对象就像一个装有原材料的盒子,就像你从宜家买到的那样。实例使用这些原材料(这些导入)来构建东西,如指令所指。正如说明手册预期使用一组特定的原材料一样,每个模块都预期使用一组特定的导入。

Imports box next to WebAssembly robot

因此,当你实例化一个模块时,你会传递一个包含这些导入的导入对象。每个导入可以是以下四种导入之一

  • 函数闭包
  • 内存

它可以包含值,这些值基本上是全局变量。WebAssembly 目前支持的唯一类型是整数和浮点数,因此值必须是这两个类型之一。这将随着 WebAssembly 规范中添加更多类型而发生变化。

函数闭包

它还可以包含函数闭包。这意味着你可以传入 JavaScript 函数,WebAssembly 然后可以调用这些函数。

这特别有用,因为在当前版本的 WebAssembly 中,你不能直接调用 DOM 方法。直接 DOM 访问已列入 WebAssembly 路线图,但尚未成为规范的一部分。

你可以做的是传入一个 JavaScript 函数,它可以按照你需要的 방식与 DOM 交互。然后 WebAssembly 只需调用该 JS 函数即可。

内存

另一种导入是内存对象。这个对象使 WebAssembly 代码能够模拟手动内存管理。内存对象的概念让人困惑,所以我在这篇 另一篇文章(本系列的下一篇文章)中更深入地讨论了它。

最后一种类型的导入也与安全性有关。它被称为表。它使你能够使用称为函数指针的东西。同样,这有点复杂,所以我在这篇 本系列的第三部分 中进行了解释。

这些是你可以为实例配备的不同类型的导入。

Different kinds of imports going into the imports box

为了返回实例,从 WebAssembly.instantiate 返回的承诺将被解决。它包含两件事:实例和编译后的模块(分别)。

拥有编译后的模块的好处在于,你可以快速启动同一模块的其他实例。你所要做的就是将模块作为 source 参数传递进去。模块本身没有任何状态(所有状态都附加到实例上)。这意味着实例可以共享编译后的模块代码。

你的实例现在已经完全准备就绪,可以开始工作了。它有它的说明手册,即编译后的代码,以及它所有的导入。现在你可以调用它的方法了。

WebAssembly robot is booted

在接下来的两篇文章中,我们将深入探讨 内存导入表导入

关于 Lin Clark

Lin 在 Mozilla 的高级开发部门工作,专注于 Rust 和 WebAssembly。

更多 Lin Clark 的文章…


3 则评论

  1. Timothy

    是否可以在像 Rust 这样的可证明安全的语言中编写这些 WASM 模块,而不是 JavaScript?

    2017 年 7 月 23 日 下午 10:57

    1. Lin Clark

      WASM 模块本身将使用 C 或 C++(或者,是的,Rust)等语言编写。目前无法用 JavaScript 编写 WASM 模块。我在另一篇文章中介绍了 创建 WebAssembly 模块

      一旦你有了该模块,你需要实例化它。这就是这篇文章要讲的内容。目前,你必须使用命令式 JS API 来实例化它。但正如我在文章中提到的,随着浏览器获得内置的 ES 模块支持,这种情况可能会改变。

      2017 年 7 月 23 日 下午 11:05

  2. Mathieu Débit

    嗨 Lin,感谢你的工作!

    所以如果我理解正确,importObject 完全对应于 WASM 模块导出的内容。

    1. 是否可以导出多个导入(例如一个值和一个函数)?
    2. 是否有一种方法可以从 C++ 到 WASM 的编译中自动获取 Javascript importObject,还是我们必须自己编写它?

    2017 年 7 月 31 日 下午 2:10

本文的评论已关闭。