现在您已经设置好了开发环境,并准备开始编写 C、C++ 和 JavaScript,是时候添加拼图的最后一块了。为了从我们的 C/C++ 代码中生成.wasm
文件,我们需要安装和配置Emscripten SDK(EMSDK)。
在本章中,我们将讨论开发工作流,并讨论 EMSDK 如何适应开发过程。将提供如何在每个平台上安装和配置 EMSDK 的详细说明,以及任何先决条件。一旦安装和配置过程完成,您将通过编写和编译一些 C 代码来测试它。
本章的目标是理解以下内容:
- 使用网络组件时的整体开发工作流程
- EMSDK 如何与 Emscripten 和 WebAssembly 相关联,以及为什么需要它
- 如何安装 EMSDK 的先决条件
- 如何安装和配置 EMSDK
- 如何测试 EMSDK 以确保其正常工作
WebAssembly 的开发工作流程与大多数其他需要编译和构建过程的语言类似。在进入工具设置之前,我们将介绍开发周期。在本节中,我们将为本章剩余部分中安装和配置的工具建立一些上下文。
对于这本书,我们将编写 C 和 C++ 代码,并将其编译成一个 Wasm 模块,但是工作流将适用于任何编译成.wasm
文件的编程语言。下图概述了该过程:
Steps in the development workflow
对于我们的例子,这一过程将在整本书中使用,因此您将了解项目结构如何对应于工作流。我们将使用一些可用的工具来加速和简化这个过程,但是步骤仍然是相同的。
有许多编辑器和工具可以用来简化开发过程。幸运的是,C/C++ 和 JavaScript 已经存在了相当长的时间,因此您可以利用最适合您的选项。考虑到该技术存在的时间较短,WebAssembly 工具的列表要短得多,但它们确实存在。
我们将使用的主要工具 VS Code 为简化构建和开发过程提供了一些优秀且有用的特性。除了用它来编写我们的代码,我们还将利用 VS Code 的内置 Tasks 特性,从 C/C++ 中构建.wasm
文件。通过在项目根文件夹中创建一个.vscode/tasks.json
文件,我们能够指定与构建步骤相关的所有参数,并使用键盘快捷键快速运行它。除了执行构建之外,我们还可以启动和停止正在运行的 Node.js 进程(即工作流图中的本地服务器)。我们将在下一章介绍如何添加和配置这些功能。
我们将使用 Emscripten 将我们的 C/C++ 代码编译成.wasm
文件。到目前为止,Emscripten 只是在一般的上下文中被简单地提到过。因为我们将在构建过程中使用这个工具和相应的 Emscripten SDK (EMSDK),所以了解每种技术是什么以及它在开发工作流中扮演的角色非常重要。在本节中,我们将描述 Emscripten 的目的,并讨论它与 EMSDK 的关系。
那么什么是 Emscripten 呢?维基百科提供了以下定义:
"Emscripten is a source-to-source compiler that runs as a back end to the LLVM compiler and produces a subset of JavaScript known as asm.js. It can also produce WebAssembly."
我们在第一章中讨论了源到源编译器(或 transpilers),并以 TypeScript 为例。Transpilers 将一种编程语言的源代码转换为另一种编程语言的等效源代码。为了详细说明 Emscripten 作为 LLVM 编译器的后端运行,我们需要提供一些关于 LLVM 的附加细节。
LLVM 的官方网站(https://llvm.org)将 LLVM 定义为模块化和可重用的编译器和工具链技术的集合。有几个子项目组成了 LLVM,但是我们将重点关注 Emscripten 使用的两个:Clang 和 LLVM 核心库。为了理解这些部分是如何结合在一起的,让我们回顾一下三阶段编译器的设计:
Design of a general three-stage compiler
过程相对简单:三个单独的阶段或结束处理编译过程。这种设计允许不同编程语言和目标架构的不同前端和后端,并通过使用中间表示将机器代码与源代码完全分离。现在,让我们将每个编译阶段与工具链的一个组件相关联,我们将使用它来生成 WebAssembly:
Three-stage compilation using the LLVM, Clang, and Emscripten
Clang 用于将 C/C++ 编译成 LLVM 的中间表示 ( IR ,Emscripten 将其编译成 Wasm 模块(二进制格式)。这两张图也展示了 Wasm 和机器代码之间的关系。你可以把 WebAssembly 想象成浏览器中的一个 CPU,Wasm 是它运行的机器代码。
Emscripten 指的是用于将 C 和 C++ 向下编译到asm.js
或 WebAssembly 的工具链。EMSDK 用于管理工具链中的工具和相应的配置。这消除了对复杂环境设置的需要,并防止了工具版本不兼容的问题。通过安装 EMSDK,我们拥有了使用 Emscripten 编译器所需的所有工具(前提条件除外)。下图是 Emscripten 工具链的可视化表示(EMSDK 以深灰色显示):
Emscripten Toolchain (modified slightly from emscripten.org)
现在,您对 Emscripten 和 EMSDK 有了更好的理解,让我们继续进行先决条件的安装过程。
在安装和配置 EMSDK 之前,我们需要安装一些先决条件。您在第 3 章、设置开发环境中安装了两个先决条件:Node.js 和 Git。每个平台的安装流程和工具要求略有不同。在本节中,我们将介绍每个平台必备工具的安装过程。
可能您已经安装了所有的先决条件。无论平台如何,您都需要以下三种:
- 饭桶
- Node.js
- Python 2.7
注意 Python 版本;这很重要,因为安装错误的版本可能会导致安装过程失败。如果你在第 2 章、中学习了 WebAssembly 的元素——Wat、Wasm 和 JavaScript API ,并安装了 Node.js 和 Git,剩下的就是安装 Python 2.7 和为你的平台指定的任何附加先决条件。每个平台的 Python 安装过程将在以下小节中详细说明。
Python is a high-level programming language used for general-purpose programming. If you'd like to learn more, check out the official website at https://www.python.org/.
在安装 EMSDK 之前,您还需要安装另外三个工具:
- x mode(x mode)-x mode(x mode)-x mode(x mode)(x mode)(x mode)(x mode)(x mode)(x mode)(x mode)(x mode)
- Xcode 命令行工具
- CMake
您可以从 macOS 应用商店安装 Xcode。如果您已经安装了 Xcode,您可以通过转到 Xcode |首选项|位置并检查命令行工具选项是否有值来检查命令行工具是否已安装。如果您安装了自制软件包管理器,命令行工具应该已经安装:
Checking the current version of the Xcode Command Line Tools
如果您没有看到,请打开终端并运行以下命令:
xcode-select --install
完成后,您可以通过运行以下命令来安装 CMake:
brew install cmake
安装 Python 之前,请运行以下命令:
python --version
如果看到Python 2.7.xx
(其中xx
是补丁版本,可以是任意数字),就可以安装 EMSDK 了。如果你得到一个说找不到 Python 命令的错误或者你看到Python 3.x.xx
,我建议你安装pyenv
,一个 Python 版本管理器。要安装pyenv
,运行以下命令:
brew install pyenv
您需要采取一些额外的配置步骤来完成安装。遵循https://github.com/pyenv/pyenv#homebrew-on-mac-os-x的家酿安装说明。安装配置pyenv
后,运行此命令安装 Python 2.7:
pyenv install 2.7.15
安装完成后,运行以下命令:
pyenv global 2.7.15
要确保您使用的是正确版本的 Python,请运行以下命令:
python --version
你应该看到 Python 2.7.xx
,其中xx
是补丁版本(我看到的是2.7.10
,会很好用)。
Ubuntu 应该已经安装了 Python 2.7。您可以通过运行以下命令来确认:
python --version
如果看到 Python 2.7.xx
(其中xx
是补丁版本,可以是任意数字),就可以安装 EMSDK 了。如果你得到一个说找不到 python 命令的错误或者你看到Python 3.x.xx
,我建议你安装pyenv
,一个 Python 版本管理器。在安装pyenv
之前,请检查您是否安装了curl
。您可以通过运行以下命令来实现这一点:
curl --version
如果看到版本号等信息,则安装curl
。如果没有,可以通过运行以下命令来安装curl
:
sudo apt-get install curl
一旦curl
安装完成,运行该命令安装pyenv
:
curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
安装并配置 pyenv 后,运行以下命令安装 Python 2.7:
pyenv install 2.7.15
如果您遇到构建问题,请导航至位于https://github.com/pyenv/pyenv/wiki/common-build-problems的常见构建问题页面。安装完成后,运行以下命令:
pyenv global 2.7.15
要确保您使用的是正确版本的 Python,请运行以下命令:
python --version
你应该看到Python 2.7.xx
,其中xx
是补丁版本(我看到的是2.7.10
,会很好用)。
Windows 的唯一附加先决条件是 Python 2.7。在尝试安装之前,请运行以下命令:
python --version
如果看到Python 2.7.xx
(其中xx
是补丁版本,可以是任意数字),就可以安装 EMSDK 了。如果您收到一个错误,说找不到 Python 命令,或者您看到Python 3.x.xx
并且您的系统上没有安装 Python 2.7,请运行以下命令来安装 Python 2.7:
choco install python2 -y
如果在安装 Python 2.7 之前看到Python 3.x.xx
,应该可以通过更新路径来改变当前的 Python 版本。在尝试安装 EMSDK 之前,运行以下命令将 Python 设置为 2.7:
SET PATH=C:\Python27\python.exe
如果您已经安装了所有的先决条件,就可以安装 EMSDK 了。启动和运行 EMSDK 的过程相对简单。在本节中,我们将介绍 EMSDK 的安装过程,并演示如何更新 VS Code C/C++ 配置以适应 Emscripten。
首先,选择一个文件夹来安装 EMSDK。我在~/Tooling
(或 Windows 上的C:\Users\Mike\Tooling
)创建了一个文件夹。在终端中,cd
进入您刚刚创建的文件夹并运行以下命令:
git clone https://github.com/juj/emsdk.git
克隆过程完成后,请按照下面对应于您的平台的部分中的说明完成安装。
克隆过程完成后,运行以下代码片段中的每个命令。如果您看到建议您运行git pull
而不是./emsdk update
的消息,请在运行./emsdk install latest
命令之前使用git pull
命令:
# Change directory into the EMSDK installation folder
cd emsdk
# Fetch the latest registry of available tools
./emsdk update
# Download and install the latest SDK tools
./emsdk install latest
# Make the latest SDK active for the current user (writes ~/.emscripten file)
./emsdk activate latest
# Activate PATH and other environment variables in the current Terminal
source ./emsdk_env.sh
source ./emsdk_env.sh
命令将激活当前终端中的环境变量,这意味着每次创建新的终端实例时,都必须重新运行它。为了避免必须采取这一步骤,您可以在 Bash 或 Zsh 配置文件(即~/.bash_profile
或~/.zshrc
)中添加以下行:
source ~/Tooling/emsdk/emsdk_env.sh > /dev/null
如果您在不同的位置安装了 EMSDK,请确保更新路径以反映这一点。将这一行添加到您的配置文件中会自动运行该环境更新命令,因此您可以立即开始使用 EMSDK。要确保可以使用 Emscripten 编译器,请运行以下命令:
emcc --version
如果看到包含版本信息的消息,说明安装成功。如果您看到一条错误消息,指出找不到该命令,请仔细检查您的配置。您可能在 Bash 或 Zsh 配置文件中为emsdk_env.sh
指定了无效路径。
在完成安装之前,我建议您继续使用 PowerShell 。本书中的例子将在cmder
中使用 PowerShell。克隆过程完成后,运行下面代码片段中给出的每个命令。如果您看到建议您运行git pull
而不是./emsdk update
的消息,请在运行./emsdk install latest
命令之前使用git pull
命令:
# Change directory into the EMSDK installation folder
cd emsdk
# Fetch the latest registry of available tools
.\emsdk update
# Download and install the latest SDK tools
.\emsdk install latest
# Make the latest SDK active for the current user (writes ~/.emscripten file)
.\emsdk activate --global latest
.\emsdk activate
命令中的--global
标志允许您运行emcc
,而无需运行脚本来设置每个会话的环境变量。要确保您可以使用 Emscripten 编译器,请重新启动命令行界面并运行以下命令:
emcc --version
如果看到包含版本信息的消息,说明安装成功。
如果您还没有这样做,请创建一个包含我们将要处理的代码示例的文件夹(示例使用名称book-examples
)。在 VS Code 中打开这个文件夹,按下 F1 键,选择 C/Cpp:编辑配置…在你的项目根目录下创建一个.vscode/c_cpp_properties.json
文件。它应该会自动打开文件。在browse.path
阵中加入以下一行:"${env:EMSCRIPTEN}/system/include"
。这将防止错误被抛出,如果你包括emscripten.h
标题。如果没有自动生成path
条目,您可能需要手动创建带有该条目的browse
对象。以下代码片段表示 Ubuntu 上更新的配置文件:
{
"name": "Linux",
"includePath": [
"/usr/include",
"/usr/local/include",
"${workspaceFolder}",
"${env:EMSCRIPTEN}/system/include"
],
"defines": [],
"intelliSenseMode": "clang-x64",
"browse": {
"path": [
"/usr/include",
"/usr/local/include",
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
}
}
安装和配置 EMSDK 后,您需要测试它,以确保您能够从 C/C++ 代码生成 Wasm 模块。测试它最简单的方法是使用emcc
命令编译一些代码,并尝试在浏览器中运行。在本节中,我们将通过编写和编译一些简单的 C 代码以及评估与.wasm
输出相关联的 Wat 来验证 EMSDK 安装。
我们将使用一些非常简单的 C 代码来测试我们的编译器安装。我们不需要导入任何头文件或外部库。我们不会使用 C++ 进行这个测试,因为我们需要用 C++ 执行一个额外的步骤来防止名称篡改,我们将在第 6 章、与 JavaScript 交互和调试中更详细地描述。该部分的代码位于learn-webassembly
存储库的/chapter-04-installing-deps
文件夹中。按照此处列出的说明测试 EMSDK。
在/book-examples
文件夹中创建一个名为/chapter-04-installing-deps
的子文件夹。接下来,在这个名为main.c
的文件夹中创建一个新文件,并用以下内容填充它:
int addTwoNumbers(int leftValue, int rightValue) {
return leftValue + rightValue;
}
为了用 Emscripten 编译一个 C/C++ 文件,我们将使用emcc
命令。我们需要向编译器传递一些参数,以确保我们得到一个可以在浏览器中使用的有效输出。要从 C/C++ 文件生成 Wasm 文件,命令遵循以下格式:
emcc <file.c> -Os -s WASM=1 -s SIDE_MODULE=1 -s BINARYEN_ASYNC_COMPILATION=0 -o <file.wasm>
以下是emcc
命令的每个参数的分解:
| 参数 | 描述 |
| <file.c>
| 将被编译成 Wasm 模块的 C 或 C++ 输入文件的路径;我们将在运行命令时用实际的文件路径替换它。 |
| -Os
| 编译器优化级别。这个优化标志允许模块实例化,而不需要 Emscripten 的粘合代码。 |
| -s WASM=1
| 告诉编译器将代码编译到网络程序集。 |
| -s SIDE_MODULE=1
| 确保仅输出一个WebAssembly
模块(无粘合代码)。 |
| -s BINARYEN_ASYNC_COMPILATION=0
| 来自官方文件:Whether to compile the wasm asynchronously, which is more efficient and does not block the main thread. This is currently required for all but the smallest modules to run in V8*.* |
| -o <file.wasm>
| 输出文件.wasm
文件路径。当我们运行该命令时,我们将用所需的输出路径替换它。 |
要测试 Emscripten 是否正常工作,请在 VS Code 中打开集成终端并运行以下命令:
# Ensure you're in the /chapter-04-installing-deps folder:
cd chapter-04-installing-deps
# Compile the main.c file to main.wasm:
emcc main.c -Os -s WASM=1 -s SIDE_MODULE=1 -s BINARYEN_ASYNC_COMPILATION=0 -o main.wasm
第一次编译文件可能需要一分钟,但是后续的构建会快得多。如果编译成功,您应该会在/chapter-04-installing-deps
文件夹中看到一个main.wasm
文件。如果您遇到错误,Emscripten 的错误消息应该具有足够的描述性,以帮助您纠正问题。
如果一切都成功完成,您可以通过在 VS Code 的文件浏览器中右键单击main.wasm
并从上下文菜单中选择“显示 WebAssembly”来查看与main.wasm
文件相关联的 Wat。输出应该如下所示:
(module
(type $t0 (func (param i32)))
(type $t1 (func (param i32 i32) (result i32)))
(type $t2 (func))
(type $t3 (func (result f64)))
(import "env" "table" (table $env.table 2 anyfunc))
(import "env" "memoryBase" (global $env.memoryBase i32))
(import "env" "tableBase" (global $env.tableBase i32))
(import "env" "abort" (func $env.abort (type $t0)))
(func $_addTwoNumbers (type $t1) (param $p0 i32) (param $p1 i32) (result i32)
get_local $p1
get_local $p0
i32.add)
(func $runPostSets (type $t2)
nop)
(func $__post_instantiate (type $t2)
get_global $env.memoryBase
set_global $g2
get_global $g2
i32.const 5242880
i32.add
set_global $g3)
(func $f4 (type $t3) (result f64)
i32.const 0
call $env.abort
f64.const 0x0p+0 (;=0;))
(global $g2 (mut i32) (i32.const 0))
(global $g3 (mut i32) (i32.const 0))
(global $fp$_addTwoNumbers i32 (i32.const 1))
(export "__post_instantiate" (func $__post_instantiate))
(export "_addTwoNumbers" (func $_addTwoNumbers))
(export "runPostSets" (func $runPostSets))
(export "fp$_addTwoNumbers" (global 4))
(elem (get_global $env.tableBase) $f4 $_addTwoNumbers))
如果编译器成功运行,您就可以进入下一步,编写 JavaScript 代码与模块进行交互,我们将在下一章介绍这一点。
在本章中,我们介绍了使用 WebAssembly 时的整体开发工作流程。为了生成我们的.wasm
文件,我们使用了 Emscripten,这需要安装 EMSDK。在回顾任何安装细节之前,我们讨论了引擎盖下的技术,并描述了它们之间以及与 WebAssembly 之间的关系。我们介绍了让 EMDSK 在您的计算机上本地工作所需的每个步骤。介绍了 EMSDK 在每个平台上的安装过程,以及 EMSDK 的安装和配置说明。安装 EMSDK 后,我们测试了编译器(否到)。这是我们在前一节中运行的emcc
命令。在一个简单的 C 代码文件上使用emcc
命令来确保 Emscripten 工作正常。在下一章中,我们将介绍创建和加载第一个模块的过程!
- 开发工作流程的五个步骤是什么?
- Emscripten 在编译过程中代表哪个阶段或结束?
- IR 代表什么(LLVM 的输出)?
- EMSDK 在 Emscripten 方面扮演什么角色?
- 所有三个平台(macOS、Windows 和 Linux)都需要哪些 EMSDK 先决条件?
- 为什么要先运行
emsdk_env
脚本才能使用 Emscripten 编译器? - 为什么需要在 C/Cpp 配置文件中添加
"${env:EMSCRIPTEN}/system/include"
路径? - 将 C/C++ 向下编译成 Wasm 模块的命令是什么?
-Os
编译器标志代表什么?
- 描述: http://emscripten.org
- LLVM 编译器基础设施项目:https://llvm.org
- 用 Visual Studio 代码进行 C++ 编程:https://code.visualstudio.com/docs/languages/cpp