WebAssembly (WASM) 是一种基于栈的虚拟机的二进制指令格式,旨在作为高性能应用的便携执行目标。在这篇文章中,我们将探讨如何将一个简单的 C 程序编译到 WebAssembly,将其加载到 web 浏览器中,并使用 JavaScript 来与其交互。我们还将探讨一些在非开发环境中使用 WASM 的有用工具和命令。
此处无内容 (chǔcǐ wú nèiróng)
搭建开发环境为 WebAssembly 项目创建所需的文件夹结构和文件。
创建项目文件夹:
首先,创建一个新的文件夹用于你的项目。在这个文件夹中,你将添加必要的文件和设置。
在命令行中输入:mkdir wasm-web-example
然后输入:cd wasm-web-example
在命令行中创建一个名为wasm-web-example的目录,并切换到该目录下。
全屏模式,退出全屏
设置开发容器环境
在 wasm-web-example
目录下,创建 .devcontainer
文件夹来存储开发环境容器配置文件。这些文件将设置一个已安装 Emscripten 的容器,用于将 C 代码编译成 WebAssembly。
在.devcontainer
文件夹里,你需要创建如下文件。
- devcontainer.json :
devcontainer.json
文件让 VSCode 使用带有必要扩展和环境设置的 Docker 容器。
{
"名称": "Emscripten 开发容器",
"构建": {
"Dockerfile": "Dockerfile"
},
"自定义": {
"VSCode": {
"设置": {
"terminal.integrated.shell.linux": "/bin/bash",
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
"C_Cpp.default.intelliSenseMode": "gcc-x64"
},
"扩展": [
"ms-vscode.cpptools",
"ms-vscode.cmake-tools"
]
}
},
"后创建命令": "emcc --version"
}
进入全屏 退出全屏
- Dockerfile : Dockerfile 将用来设置 Emscripten 环境,内容如下:
# 使用官方的 Emscripten 镜像环境
FROM emscripten/emsdk:3.1.74
# 设置工作目录为
WORKDIR /workspace
# 复制源代码到容器内部
COPY . .
# 如有需要,安装任何额外的包(可选)
# 确保清理缓存来减少 Docker 镜像的大小
RUN apt-get update && \
apt-get install -y build-essential && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
全屏进入 退出
设置 VSCode
在你的项目主目录下,创建一个名为 .vscode
的文件夹,并在该文件夹内放入以下文件:
- c_cpp_properties.json : 该文件用于设置项目的 C++ IntelliSense 和包含路径。
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/emsdk/upstream/emscripten/system/include"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c17", // C标准为C17
"cppStandard": "gnu++17", // C++标准为gnu++17
"configurationProvider": "ms-vscode.cmake-tools"
}
],
"version": 4
}
进入全屏,退出全屏
- settings.json:此文件包含VSCode中特定的语言关联设置。
{
"files.associations": {
"emscripten.h": "c"
},
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[html]": {
"editor.defaultFormatter": "vscode.html-language-features"
}
}
切换到全屏 退出全屏
创建 C、JavaScript 和 HTML 文件这几步
现在,请为你的项目创建如下文件:
- test.c : 这个 C 文件包含一个将被编译成 WebAssembly 的简单函数。
// test.c
// 这是一个测试代码,定义了一个加法函数
int add(int lhs, int rhs) {
return lhs + rhs;
}
进入全屏,退出全屏
- test.html :此 HTML 文件会用 JavaScript 加载 WebAssembly 模块
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebAssembly 例子</title>
</head>
<body>
<h1>WebAssembly 例子</h1>
<div id="output"></div>
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="test.js"></script>
</body>
</html>
进入全屏,退出全屏
- test.js :此 JavaScript 文件将加载 WebAssembly 模块(Wasm 模块)并调用导出的功能函数。
// 指向 .wasm 文件的路径
const wasmFile = 'test.wasm';
// 加载 WebAssembly 模块
fetch(wasmFile)
.then(response => {
if (!response.ok) {
throw new Error(`加载 ${wasmFile} 失败: ${response.statusText}`);
}
return response.arrayBuffer();
})
.then(bytes => WebAssembly.instantiate(bytes))
.then(({ instance }) => {
// 访问导出的函数
const wasmExports = instance.exports;
console.log({ wasmExports })
// 示例:调用 WebAssembly 模块导出的函数
if (wasmExports.add) {
const result = wasmExports.add(5, 3); // 示例函数调用
document.getElementById('output').textContent = `来自 WebAssembly 的结果: ${result}`;
} else {
document.getElementById('output').textContent = '未在 WebAssembly 模块中找到 "add" 函数。';
}
})
.catch(error => {
console.error('加载或运行 WebAssembly 模块时出错:', error);
document.getElementById('output').textContent = '加载 WebAssembly 模块出错。';
});
进入全屏 退出全屏
现在你已经准备好所有必要的文件和配置,可以开始编译并且与WebAssembly进行交互了。
项目结构目前如下:
➜ wasm-web-example: tree . -a
.
├── .devcontainer
│ ├── Dockerfile
│ └── devcontainer.json
├── .vscode
│ ├── c_cpp_properties.json
│ └── settings.json
├── test.c
├── test.html
├── test.js
这里显示的是wasm-web-example目录下的文件结构。
全屏模式,退出全屏
……
使用Emscripten编译C代码成WebAssembly简单的 C 程序
文件 test.c
包含一个简单的加法函数 add
,该函数用于加两个整数。我们将使用 Emscripten 将这个 C 函数编译成 WebAssembly。
// 测试文件 test.c
int add(int lhs, int rhs) {
// 这是一个简单的加法函数
return lhs + rhs;
}
进入全屏 退出全屏
Emscripten命令:
在开发容器内,打开终端(在 VSCode 中,可以使用 cmd+j
),运行以下 Emscripten 命令,将 C 代码编译为 WebAssembly:
emcc test.c -O3 -s STANDALONE_WASM -s EXPORTED_FUNCTIONS='["_add"]' --no-entry -o test.wasm # 使用emcc编译test.c,生成一个独立的WASM文件test.wasm
切换到全屏模式 退出全屏模式
命令解析
-
emcc
:emcc
是 Emscripten 的 C/C++ 编译器命令。它会将 C/C++ 源文件编译成 WebAssembly 或 asm.js。 -
test.c
:这是你想要编译的C源代码文件。 -
-O3
:此标志使编译过程采用激进优化。-O3
优化级别通常用于性能关键的应用程序,因为它采用了多种可以显著提升运行时性能的优化方法。 -
-s STANDALONE_WASM
: 此选项让 Emscripten 生成一个不依赖任何 JavaScript 胶水代码的独立 WebAssembly 模块。这样的 WASM 模块可以在支持 WebAssembly 的环境中独立运行。 -
-s EXPORTED_FUNCTIONS='["_add"]'
:此标志指定了从 C 代码中应导出并可供 JavaScript 调用的函数。例如,-s EXPORTED_FUNCTIONS='["_add"]'
表示导出_add
函数。在这种情况下,名为_add
的函数将在生成的 WASM 模块中可见。 -
--no-entry
: 通知编译器程序中没有入口点(例如main()
函数)。这对于那些设计用于其他代码调用而不是不直接运行的库或模块很有用。 -o test.wasm
: 这指定了编译后的 WebAssembly 模块的输出文件名。在这种情况下,将会生成一个名为test.wasm
的文件。
此命令会生成 test.wasm
,这样一个 WebAssembly 二进制文件(.wasm 文件),并确保 add
函数被导出,以便在 JavaScript 中使用。
……
在浏览器里加载和运行WebAssembly以及与其交互HTML 设置篇
文件 test.html
包含一个简单的 HTML 页面,该页面使用 JavaScript 加载 WebAssembly 二进制文件。在 JavaScript 文件 test.js
中,JavaScript 代码加载 .wasm
二进制文件并实例化该二进制文件。
<!-- test.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebAssembly 示例页面</title>
</head>
<body>
<h1>WebAssembly 示例页面</h1>
<div id="output"></div>
<script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="test.js"></script>
</body>
</html>
进入全屏 关闭全屏
JavaScript 设置指南
test.js
文件加载 test.wasm
文件,并调用其中导出的 add
函数。
// test.js
// .wasm 文件路径
const wasmFile = 'test.wasm';
// 加载 WebAssembly 模块
fetch(wasmFile)
.then(response => {
if (!response.ok) {
throw new Error(`加载 ${wasmFile} 失败,原因: ${response.statusText}`);
}
return response.arrayBuffer();
})
.then(bytes => WebAssembly.instantiate(bytes))
.then(({ instance }) => {
// 访问导出的 WebAssembly 函数
const wasmExports = instance.exports;
console.log({ wasmExports })
// 示例:调用 WebAssembly 模块导出的函数
if (wasmExports.add) {
const result = wasmExports.add(5, 3); // 示例函数调用
document.getElementById('output').textContent = `WebAssembly 返回的结果是: ${result}`;
} else {
document.getElementById('output').textContent = '在 WebAssembly 模块中未找到 "add" 函数。';
}
})
.catch(error => {
console.error('在加载或运行 WebAssembly 模块时出错: ${error}');
document.getElementById('output').textContent = '加载 WebAssembly 模块时失败。';
});
全屏开启 退出全屏
当模块成功加载后,HTML页面上会展示add
函数的结果。
此处省略内容
在 macOS 上使用外挂工具在 Mac 上,你可以运行一些有用的命令来与 WebAssembly 一起工作,这些命令在开发容器外部运行。
安装 wabt
:
wabt
(WebAssembly二进制工具套件)提供了操作WebAssembly的实用工具,包括将.wasm
文件转换成人类可读的WAT (WebAssembly文本) 格式。可以通过Homebrew进行安装,
使用 brew 安装 wabt 工具
brew install wabt
点击此处全屏模式 点击此处退出全屏
### 把WASM转成WAT
一旦安装了 `wabt`,你可以使用 `wasm2wat` 工具将你的 WebAssembly 二进制文件(如 `test.wasm`)转换成 WAT 格式:
运行以下命令来将test.wasm转换为文本格式:`wasm2wat test.wasm`
全屏模式,退出全屏
将输出 WebAssembly 模块的文本形式,你可以阅读和检查。
(模块
(类型 (;0;) (功能))
(类型 (;1;) (功能 (参数 i32 i32) (结果 i32)))
(类型 (;2;) (功能 (参数 i32)))
(类型 (;3;) (功能 (结果 i32)))
(功能 (;0;) (类型 0)
不操作)
(功能 (;1;) (类型 1) (参数 i32 i32) (结果 i32)
(局部取 0)
(局部取 1)
(i32.add))
(功能 (;2;) (类型 2) (参数 i32)
(局部取 0)
(全局设 0))
(功能 (;3;) (类型 3) (结果 i32)
(全局取 0))
(表 (;0;) 2 2 funcref)
(内存 (;0;) 258 258)
(全局 (;0;) (mut i32) (i32.const 66560))
(导出 "memory" (内存 (;0;)))
(导出 "add" (功能 (;1;)))
(导出 "_initialize" (功能 (;0;)))
(导出 "__indirect_function_table" (表 (;0;)))
(导出 "_emscripten_stack_restore" (功能 (;2;)))
(导出 "emscripten_stack_get_current" (功能 (;3;)))
(元素 (;0;) (i32.const 1) (功能 (;0;)))
进入全屏模式,退出全屏
### **展示 HTML 页面**
要查看与 WebAssembly 模块交互的 HTML 页面,你可以用 Python 的简单 HTTP 服务器来查看这个页面。
python -m http.server
运行这个命令会启动一个简单的HTTP服务器。
切换到全屏模式 取消全屏
该命令将启动一个本地的Web服务器,,您可以通过浏览器打开 `index.html` 文件来查看正在运行的WebAssembly模块的效果。
![WebAssembly 示例输出](https://imgapi.imooc.com/67886d92090aa06b08000179.jpg "点击可查看大图")
* * *
### 结尾
通过遵循本文中概述的步骤,您可以搭建一个开发环境来将C代码编译成WebAssembly,并通过JavaScript与之交互,还可以将生成的WebAssembly二进制文件转换为WAT格式以供检查。借助外部工具如`wabt`和Python的HTTP服务器,可以更轻松地在您的macOS系统上管理和查看WebAssembly模块。
共同学习,写下你的评论
评论加载中...
作者其他优质文章