WebAssembly 入门:在浏览器中运行 C/Rust

FreeGuideOnline 最新 2026-06-15

WebAssembly 入门:在浏览器中运行 C/Rust

什么是 WebAssembly

WebAssembly(缩写为 Wasm)是一种紧凑的二进制指令格式,为浏览器设计,却不仅限于浏览器。它允许你用 C、C++、Rust 等语言编写高性能代码,并直接在 Web 环境中以接近原生的速度运行。Wasm 与 JavaScript 协同工作,不是要取代它,而是弥补 JavaScript 在计算密集型场景下的不足。

为什么选择 WebAssembly

  • 近乎原生的性能:二进制格式解析快,编译快,执行效率高。
  • 安全沙箱:与 JavaScript 共享同样的同源策略和权限模型。
  • 语言多样性:复用现有 C/C++/Rust 生态,将游戏引擎、图像处理、加密算法等带入 Web。
  • 可移植性:一次编译,所有现代浏览器均可运行(Chrome、Firefox、Safari、Edge 均已支持)。
  • 更小的体积:紧凑的二进制格式,下载解析速度比等效 JavaScript 更快。

注意:本教程全部工具和示例均为免费开源方案,你无需支付任何费用即可开始学习。


环境准备

根据你选择的语言,安装对应的工具链。以下分别介绍 C(使用 Emscripten)和 Rust(使用 wasm-pack)。

方案一:使用 C 语言 + Emscripten

Emscripten 是一个完整的 C/C++ 到 Wasm 的编译工具链,帮你将 C 代码编译为 .wasm 文件,并生成必要的 JavaScript 胶水代码。

  1. 安装 Emscripten

    # 克隆 emsdk 仓库
    git clone https://github.com/emscripten-core/emsdk.git
    cd emsdk
    # 安装最新版本
    ./emsdk install latest
    ./emsdk activate latest
    # 设置环境变量(每次新终端都需执行,建议写入 ~/.bashrc)
    source ./emsdk_env.sh
    

    验证安装:

    emcc --version
    
  2. (可选)配置编辑器
    VS Code 用户可安装 C/C++ 扩展,无需额外配置。

方案二:使用 Rust 语言 + wasm-pack

wasm-pack 专为构建 Rust 生成的 Wasm 包而设计,可以和 npm 生态无缝集成。

  1. 安装 Rust
    通过 rustup 安装(若已安装请跳过):

    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    

    安装后确保 WASM 编译目标可用:

    rustup target add wasm32-unknown-unknown
    
  2. 安装 wasm-pack

    curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
    

    或通过 npm:npm install -g wasm-pack。验证:

    wasm-pack --version
    
  3. 安装 Node.js 和 npm(用于本地测试服务器)
    nodejs.org 下载 LTS 版本。


用 C 编写并运行第一个 Wasm 模块

我们将编写一个简单的 C 函数,计算两个数的和,然后在网页上调用它。

编写 C 代码

创建 add.c

#include <emscripten.h>

// 使用 EMSCRIPTEN_KEEPALIVE 确保函数不会被编译器优化掉
EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
    return a + b;
}

编译为 WebAssembly

emcc add.c -o add.js \
    -s EXPORTED_FUNCTIONS='["_add"]' \
    -s EXPORTED_RUNTIME_METHODS='["cwrap"]'
  • -o add.js:生成 JavaScript 胶水文件 add.jsadd.wasm
  • -s EXPORTED_FUNCTIONS:指明需要导出哪些 C 函数(名称前加下划线)。
  • -s EXPORTED_RUNTIME_METHODS:暴露 cwrap 方法,便于在 JS 中包装 C 函数。

在网页中加载并使用

创建 index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>C Wasm Demo</title>
</head>
<body>
    <h2>C WebAssembly 计算器</h2>
    <p>结果:<span id="result"></span></p>

    <script src="add.js"></script>
    <script>
        Module.onRuntimeInitialized = () => {
            // 用 cwrap 包装 add 函数
            const add = Module.cwrap('add', 'number', ['number', 'number']);
            const sum = add(10, 25);
            document.getElementById('result').textContent = sum;
        };
    </script>
</body>
</html>

在项目目录下启动一个本地服务器(因为浏览器不允许通过 file:// 加载 Wasm):

# 用 Python 快速启动
python3 -m http.server 8000
# 或者用 Node.js 的 http-server(npm install -g http-server)
http-server -p 8000

打开 http://localhost:8000,你会看到页面显示“结果:35”。


用 Rust 编写并运行第一个 Wasm 模块

Rust 生态通常使用 wasm-bindgen 在 Rust 和 JavaScript 之间自动生成绑定,wasm-pack 能一键构建。

创建 Rust 项目

cargo new --lib wasm-add
cd wasm-add

编辑 Cargo.toml,添加依赖:

[package]
name = "wasm-add"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

编写 Rust 函数

打开 src/lib.rs

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[wasm_bindgen] 告诉编译器生成 JavaScript 可调用的绑定。

构建 Wasm 包

wasm-pack build --target web

构建完成后会在 pkg/ 目录下生成:

  • wasm_add_bg.wasm:实际的 Wasm 模块
  • wasm_add.js:JavaScript 绑定
  • package.json 和 TypeScript 声明等(适合 npm 生态)

在网页中使用

创建 index.html(放在项目根目录,与 pkg 同级):

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Rust Wasm Demo</title>
</head>
<body>
    <h2>Rust WebAssembly 计算器</h2>
    <p>结果:<span id="result"></span></p>

    <script type="module">
        import init, { add } from './pkg/wasm_add.js';

        async function run() {
            await init();
            const sum = add(10, 25);
            document.getElementById('result').textContent = sum;
        }

        run();
    </script>
</body>
</html>

同样需要本地服务器访问。启动服务器后打开页面,你会看到“结果:35”。


进一步:从 JavaScript 传递复杂数据

C 与字符串

修改 add.c,增加一个字符串处理函数:

#include <emscripten.h>
#include <string.h>
#include <stdlib.h>

EMSCRIPTEN_KEEPALIVE
char* greet(const char* name) {
    const char* prefix = "Hello, ";
    char* result = malloc(strlen(prefix) + strlen(name) + 1);
    strcpy(result, prefix);
    strcat(result, name);
    return result;
}

// 释放内存(必须由 JS 手动调用)
EMSCRIPTEN_KEEPALIVE
void free_mem(void* ptr) {
    free(ptr);
}

编译时导出这两个函数,并在 HTML 中使用 UTF8ToString_free

Module.onRuntimeInitialized = () => {
    const greet = Module.cwrap('greet', 'number', ['string']);
    const free_mem = Module.cwrap('free_mem', null, ['number']);
    const ptr = greet("World");
    const greeting = Module.UTF8ToString(ptr);
    free_mem(ptr);
    console.log(greeting); // 输出 "Hello, World"
};

Rust 与字符串

在 Rust 中,字符串转换由 wasm-bindgen 自动处理:

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

JS 调用时直接传入字符串,返回也是字符串,完全透明。


构建一个简单的 DOM 应用

用 Rust 演示:添加一个按钮,点击后用 Wasm 函数更新页面文本。

Rust 代码 (src/lib.rs):

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn double(n: i32) -> i32 {
    n * 2
}

构建 wasm-pack build --target web

HTML

<body>
    <input type="number" id="input" value="2" />
    <button id="btn">Double It</button>
    <p>Result: <span id="output"></span></p>

    <script type="module">
        import init, { double } from './pkg/wasm_add.js';
        await init();

        document.getElementById('btn').addEventListener('click', () => {
            const val = parseInt(document.getElementById('input').value) || 0;
            const result = double(val);
            document.getElementById('output').textContent = result;
        });
    </script>
</body>

这是最经典的“从 Web 调用 Wasm”模式:用户交互 → JS 调用 Wasm 函数 → 更新 UI。


调试与性能分析

  • 浏览器开发者工具:Chrome/Edge DevTools 可以查看 Wasm 的文本格式(类似汇编)并支持断点调试(需源码映射)。在 Sources 面板中可以看到 .wasm 文件,点击可查看反汇编。
  • 启用 DWARF 调试信息(C 使用 -g 参数,Rust 用 wasm-pack build --dev)可生成源映射,让调试器显示原始 C/Rust 代码。
  • 分析性能:使用 console.time 或 Performance 面板测量 Wasm 函数执行时间,对比 JavaScript 版本。

常见问题

  • 错误 TypeError: Failed to fetch:多因本地文件直接打开,请务必使用 HTTP 服务器。
  • 函数未找到:检查导出名称(C 默认加下划线),Rust 中确保使用了 #[wasm_bindgen]
  • 内存泄漏:C 中手动分配的堆内存务必从 JS 调用 free;Rust 的内存由 Wasm 线性内存管理,但复杂对象传递仍需注意所有权。

总结与下一步

通过本教程,你已经掌握了使用 C 和 Rust 两种语言创建 WebAssembly 模块并在浏览器中调用的基础流程。现在你可以:

  • 将现有的 C/Rust 库迁移到 Web,比如图像处理、物理模拟或加密算法。
  • 使用 WebAssembly System Interface (WASI) 将 Wasm 带到服务器端,用 Wasmtime 或 Wasmer 等运行时执行。
  • 学习更高级的绑定方法,比如在 Rust 中访问 Web API(web-sys)或使用 wasm-bindgen 的直接 DOM 操作。
  • 探索其他语言支持:Go、AssemblyScript 等。

WebAssembly 正在打开“任何语言都可以在 Web 上安全、高效运行”的大门。坚持动手练习,你很快就能构建令人惊叹的跨平台应用。

延伸阅读