WebAssembly(WASM)让C++/Rust等原生代码以接近本地的速度在浏览器中运行,但编译链配置复杂、调试门槛高。本教程教你利用Claude Code的专用Skill和自动化能力,从零搭建C++/Rust到WASM的完整开发流水线——包括Emscripten工具链配置、wasm-bindgen绑定生成、WebGL渲染集成,以及性能优化策略。学完后,你将能独立将一个C++物理引擎编译为浏览器可执行的高性能WASM模块。
适用人群:有C++或Rust基础、希望将原生代码移植到Web平台的后端/系统开发者;对高性能Web应用感兴趣的前端工程师。
| 条件 | 说明 |
|---|---|
| Claude Code | 已安装并完成API配置(claude 命令可用) |
| C++编译器 | GCC 12+ 或 Clang 15+(g++ --version 验证) |
| Rust工具链 | rustup 已安装,stable通道 |
| Emscripten SDK | 用于C++到WASM编译 |
| wasm-pack | 用于Rust到WASM编译 |
| Node.js 20+ | 浏览器测试服务器 |
环境验证命令:
# 检查所有工具是否就位
claude --version && g++ --version | head -1 && rustc --version && node --version
在动手之前,先看清楚整个技术栈如何串联。
graph TD
A["原生代码<br/>C++ / Rust"] -->|"Emscripten / wasm-pack"| B["WebAssembly Module<br/>(.wasm)"]
B -->|"JavaScript胶水层<br/>wasm-bindgen / emscripten"| C["浏览器运行时"]
C --> D["WebGL / WebGPU 渲染"]
C --> E["DOM交互"]
C --> F["Web API调用"]
G["Claude Code"] -->|"skill-compile-to-wasm"| A
G -->|"自动生成绑定"| B
G -->|"创建测试页面"| C
G -->|"性能分析"| B
style G fill:#ff6b35,color:#fff
style B fill:#654ff0,color:#fff
Claude Code在此架构中扮演三个角色:
wasm-bindgen、EMSCRIPTEN_BINDINGS)Claude Code社区已经开发了多个WASM相关Skill,按功能分类:
| Skill名称 | 功能 | 安装源 |
|---|---|---|
skill-compile-to-wasm |
C/Rust编译到优化WASM,含库封装 | GitHub: andreaTP/skill-compile-to-wasm |
wasm-emscripten |
Emscripten一键编译C/C++ | skills.rest |
webassembly-discovery |
自动发现并加载WASM子Skill | mcpmarket.com |
这是最核心的Skill,覆盖从编译到优化的全流程:
# 方法一:直接克隆到Skills目录
mkdir -p ~/.claude/skills
cd ~/.claude/skills
git clone https://github.com/andreaTP/skill-compile-to-wasm.git
# 方法二:在项目目录下创建(推荐,项目级生效)
mkdir -p .claude/skills
cd .claude/skills
git clone https://github.com/andreaTP/skill-compile-to-wasm.git
安装后,Claude Code会自动在对话中检测WASM相关任务并激活该Skill。
# 在Claude Code对话中
claude
> 帮我编译这段C++代码到WebAssembly
如果Skill加载成功,Claude Code会自动引用Emscripten的编译参数和最佳实践,而不是给出通用的gcc命令。
# 克隆emsdk
git clone https://github.com/emscripten-core/emsdk.git ~/emsdk
cd ~/emsdk
# 安装最新稳定版
./emsdk install latest
./emsdk activate latest
# 激活环境变量(加入 .bashrc 或 .zshrc)
source ~/emsdk/emsdk_env.sh
验证:
emcc --version
# 预期输出: emcc (Emscripten gcc/clang-like replacement) 3.x.x
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# 验证
wasm-pack --version
# 预期输出: wasm-pack 0.13.x
在项目根目录创建 CLAUDE.md,让Claude Code了解你的WASM项目结构:
# WASM项目说明
## 构建工具
- C++编译: Emscripten (emcc), 目标 wasm32
- Rust编译: wasm-pack, 目标 web
- 测试: Node.js + puppeteer
## 关键目录
- `src/cpp/` - C++源代码
- `src/rust/` - Rust源代码
- `wasm-output/` - 编译输出(.wasm + .js胶水代码)
- `web/` - 前端测试页面
## 编译命令
- C++: `emcc src/cpp/main.cpp -o wasm-output/module.js -O3 -s WASM=1 -s EXPORTED_FUNCTIONS="['_malloc','_free','_simulate']" -s ALLOW_MEMORY_GROWTH=1`
- Rust: `cd src/rust && wasm-pack build --target web --out-dir ../../wasm-output`
## 注意事项
- C++中禁止使用文件系统API(WASM沙箱限制)
- 所有导出函数必须用 `extern "C"` 或 EMSCRIPTEN_BINDINGS 包装
- Rust中使用 #[wasm_bindgen] 宏标记导出函数
这段配置让Claude Code在后续交互中自动遵循你的构建约定。
创建 src/cpp/physics.cpp:
#include <emscripten.h>
#include <cmath>
#include <vector>
// 2D向量
struct Vec2 {
float x, y;
};
// 粒子结构体
struct Particle {
Vec2 position;
Vec2 velocity;
float mass;
float radius;
};
// 物理引擎状态
static std::vector<Particle> particles;
extern "C" {
// 初始化粒子系统
EMSCRIPTEN_KEEPALIVE
void init_particles(int count, float width, float height) {
particles.clear();
particles.reserve(count);
for (int i = 0; i < count; i++) {
Particle p;
p.position = {
static_cast<float>(rand()) / RAND_MAX * width,
static_cast<float>(rand()) / RAND_MAX * height
};
p.velocity = {
(static_cast<float>(rand()) / RAND_MAX - 0.5f) * 2.0f,
(static_cast<float>(rand()) / RAND_MAX - 0.5f) * 2.0f
};
p.mass = 1.0f + static_cast<float>(rand()) / RAND_MAX * 4.0f;
p.radius = p.mass * 3.0f;
particles.push_back(p);
}
}
// 执行一步物理模拟(含碰撞检测)
EMSCRIPTEN_KEEPALIVE
void step_simulation(float dt, float width, float height) {
// 更新位置
for (auto& p : particles) {
p.position.x += p.velocity.x * dt;
p.position.y += p.velocity.y * dt;
// 边界碰撞
if (p.position.x < p.radius || p.position.x > width - p.radius)
p.velocity.x *= -0.95f;
if (p.position.y < p.radius || p.position.y > height - p.radius)
p.velocity.y *= -0.95f;
}
// 粒子间碰撞检测(O(n²)简化版)
for (size_t i = 0; i < particles.size(); i++) {
for (size_t j = i + 1; j < particles.size(); j++) {
auto& a = particles[i];
auto& b = particles[j];
float dx = b.position.x - a.position.x;
float dy = b.position.y - a.position.y;
float dist = std::sqrt(dx * dx + dy * dy);
float min_dist = a.radius + b.radius;
if (dist < min_dist && dist > 0.001f) {
// 弹性碰撞响应
float nx = dx / dist;
float ny = dy / dist;
float rel_vx = a.velocity.x - b.velocity.x;
float rel_vy = a.velocity.y - b.velocity.y;
float rel_v_normal = rel_vx * nx + rel_vy * ny;
if (rel_v_normal > 0) {
float total_mass = a.mass + b.mass;
float impulse = 2.0f * rel_v_normal / total_mass;
a.velocity.x -= impulse * b.mass * nx;
a.velocity.y -= impulse * b.mass * ny;
b.velocity.x += impulse * a.mass * nx;
b.velocity.y += impulse * a.mass * ny;
}
// 分离重叠
float overlap = min_dist - dist;
float sep_x = overlap * nx * 0.5f;
float sep_y = overlap * ny * 0.5f;
a.position.x -= sep_x;
a.position.y -= sep_y;
b.position.x += sep_x;
b.position.y += sep_y;
}
}
}
}
// 获取粒子数据(返回指针供JS读取)
EMSCRIPTEN_KEEPALIVE
Particle* get_particles() {
return particles.data();
}
EMSCRIPTEN_KEEPALIVE
int get_particle_count() {
return static_cast<int>(particles.size());
}
} // extern "C"
在项目目录下启动Claude Code,让它帮你编译:
claude
输入指令:
编译 src/cpp/physics.cpp 到 WebAssembly,要求:
1. 优化级别 -O3
2. 导出所有带 EMSCRIPTEN_KEEPALIVE 的函数
3. 支持内存增长
4. 输出到 wasm-output/physics.js
Claude Code会根据CLAUDE.md中的配置和skill-compile-to-wasm的指导,生成精确的编译命令:
emcc src/cpp/physics.cpp \
-o wasm-output/physics.js \
-O3 \
-s WASM=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s EXPORTED_RUNTIME_METHODS="['ccall','cwrap','HEAPF32']" \
-s EXPORTED_FUNCTIONS="['_init_particles','_step_simulation','_get_particles','_get_particle_count','_malloc','_free']" \
-s MODULARIZE=1 \
-s EXPORT_NAME="createPhysicsModule"
预期输出:
wasm-output/
├── physics.js # JavaScript胶水代码
└── physics.wasm # WebAssembly二进制模块
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
error: undefined symbol: fopen |
WASM无文件系统 | 用 -s NODERAWFS=1 或移除文件操作 |
error: shared libraries not supported |
动态链接不支持 | 静态链接所有依赖 -s STATIC_LINKING=1 |
ImportError: function env._emscripten_xxx not found |
导出函数遗漏 | 检查 -s EXPORTED_FUNCTIONS 列表 |
Memory access out of bounds |
默认内存不足 | 加 -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=16MB |
# 用Claude Code初始化项目
claude
> 用 wasm-pack 创建一个新的Rust WASM项目,名字叫 rust-physics,放在 src/rust/ 下
Claude Code会执行:
# 如果使用cargo generate
cargo generate --git https://github.com/rustwasm/wasm-pack-template --name rust-physics --dest src/rust/
或者手动创建:
mkdir -p src/rust/rust-physics/src
cd src/rust/rust-physics
创建 Cargo.toml:
[package]
name = "rust-physics"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
[dependencies.web-sys]
version = "0.3"
features = [
"console",
]
[profile.release]
opt-level = 3
lto = true
创建 src/lib.rs:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Clone, Copy)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
#[wasm_bindgen]
#[derive(Clone, Copy)]
pub struct Particle {
pub position: Vec2,
pub velocity: Vec2,
pub mass: f32,
pub radius: f32,
}
#[wasm_bindgen]
pub struct PhysicsEngine {
particles: Vec<Particle>,
width: f32,
height: f32,
}
#[wasm_bindgen]
impl PhysicsEngine {
#[wasm_bindgen(constructor)]
pub fn new(width: f32, height: f32) -> Self {
PhysicsEngine {
particles: Vec::new(),
width,
height,
}
}
pub fn add_particle(&mut self, x: f32, y: f32, mass: f32) {
let radius = mass * 3.0;
let vx = (js_sys::Math::random() as f32 - 0.5) * 2.0;
let vy = (js_sys::Math::random() as f32 - 0.5) * 2.0;
self.particles.push(Particle {
position: Vec2 { x, y },
velocity: Vec2 { x: vx, y: vy },
mass,
radius,
});
}
pub fn step(&mut self, dt: f32) {
let width = self.width;
let height = self.height;
// 更新位置 + 边界碰撞
for p in &mut self.particles {
p.position.x += p.velocity.x * dt;
p.position.y += p.velocity.y * dt;
if p.position.x < p.radius || p.position.x > width - p.radius {
p.velocity.x *= -0.95;
}
if p.position.y < p.radius || p.position.y > height - p.radius {
p.velocity.y *= -0.95;
}
}
// 粒子间碰撞
let n = self.particles.len();
for i in 0..n {
for j in (i + 1)..n {
let (a, b) = {
let (left, right) = self.particles.split_at_mut(j);
(&mut left[i], &mut right[0])
};
let dx = b.position.x - a.position.x;
let dy = b.position.y - a.position.y;
let dist = (dx * dx + dy * dy).sqrt();
let min_dist = a.radius + b.radius;
if dist < min_dist && dist > 0.001 {
let nx = dx / dist;
let ny = dy / dist;
let rel_vn = (a.velocity.x - b.velocity.x) * nx
+ (a.velocity.y - b.velocity.y) * ny;
if rel_vn > 0.0 {
let total = a.mass + b.mass;
let impulse = 2.0 * rel_vn / total;
a.velocity.x -= impulse * b.mass * nx;
a.velocity.y -= impulse * b.mass * ny;
b.velocity.x += impulse * a.mass * nx;
b.velocity.y += impulse * a.mass * ny;
}
let overlap = (min_dist - dist) * 0.5;
a.position.x -= overlap * nx;
a.position.y -= overlap * ny;
b.position.x += overlap * nx;
b.position.y += overlap * ny;
}
}
}
}
pub fn get_positions(&self) -> Vec<f32> {
let mut result = Vec::with_capacity(self.particles.len() * 4);
for p in &self.particles {
result.push(p.position.x);
result.push(p.position.y);
result.push(p.mass);
result.push(p.radius);
}
result
}
pub fn particle_count(&self) -> usize {
self.particles.len()
}
}
让Claude Code执行构建:
claude
> 编译 src/rust/rust-physics 到 WASM,目标平台 web,输出到 wasm-output/rust-physics/
Claude Code会生成并执行:
cd src/rust/rust-physics && wasm-pack build --target web --out-dir ../../wasm-output/rust-physics --release
输出结构:
wasm-output/rust-physics/
├── package.json
├── rust_physics.js # wasm-bindgen生成的JS绑定
├── rust_physics_bg.wasm # WASM二进制
└── rust_physics.d.ts # TypeScript类型定义
创建 web/index.html——这是C++ WASM和Rust WASM共用的渲染页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WASM Physics Engine</title>
<style>
body { margin: 0; background: #1a1a2e; display: flex; justify-content: center; align-items: center; height: 100vh; }
canvas { border: 1px solid #e94560; border-radius: 8px; }
.controls { position: fixed; top: 20px; right: 20px; color: #fff; font-family: monospace; }
.stats { color: #0f3460; background: #e94560; padding: 4px 12px; border-radius: 4px; margin-bottom: 8px; }
</style>
</head>
<body>
<canvas id="canvas" width="800" height="600"></canvas>
<div class="controls">
<div class="stats" id="fps">FPS: --</div>
<div class="stats" id="count">Particles: --</div>
<button onclick="switchEngine('cpp')" style="margin:4px;padding:8px 16px">C++ Engine</button>
<button onclick="switchEngine('rust')" style="margin:4px;padding:8px 16px">Rust Engine</button>
</div>
<script type="module">
import init, { PhysicsEngine } from '../wasm-output/rust-physics/rust_physics.js';
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let currentEngine = 'rust';
let rustEngine = null;
let cppModule = null;
// 初始化Rust引擎
async function initRust() {
await init();
rustEngine = new PhysicsEngine(800, 600);
for (let i = 0; i < 200; i++) {
const x = Math.random() * 760 + 20;
const y = Math.random() * 560 + 20;
const mass = 1 + Math.random() * 4;
rustEngine.add_particle(x, y, mass);
}
}
// 初始化C++引擎
async function initCpp() {
const createModule = await import('../wasm-output/physics.js');
cppModule = await createModule.default();
cppModule._init_particles(200, 800, 600);
}
// 渲染循环
let lastTime = performance.now();
let frameCount = 0;
let fps = 0;
function render(timestamp) {
const dt = Math.min((timestamp - lastTime) / 1000, 0.05);
lastTime = timestamp;
frameCount++;
if (frameCount % 30 === 0) {
fps = Math.round(30 / (dt * 30 + 0.001));
document.getElementById('fps').textContent = `FPS: ${fps}`;
}
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, 800, 600);
if (currentEngine === 'rust' && rustEngine) {
rustEngine.step(16.67);
const positions = rustEngine.get_positions();
document.getElementById('count').textContent = `Particles: ${rustEngine.particle_count()}`;
for (let i = 0; i < positions.length; i += 4) {
const [x, y, mass, radius] = positions.slice(i, i + 4);
const hue = (mass / 5) * 280;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = `hsl(${hue}, 80%, 60%)`;
ctx.fill();
}
} else if (currentEngine === 'cpp' && cppModule) {
cppModule._step_simulation(16.67, 800, 600);
const ptr = cppModule._get_particles();
const count = cppModule._get_particle_count();
document.getElementById('count').textContent = `Particles: ${count}`;
const data = new Float32Array(cppModule.HEAPF32.buffer, ptr, count * 6);
for (let i = 0; i < count; i++) {
const offset = i * 6;
const x = data[offset];
const y = data[offset + 1];
const mass = data[offset + 4];
const radius = data[offset + 5];
const hue = (mass / 5) * 280;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = `hsl(${hue}, 80%, 60%)`;
ctx.fill();
}
}
requestAnimationFrame(render);
}
window.switchEngine = function(engine) {
currentEngine = engine;
};
// 启动
(async () => {
await Promise.all([initRust(), initCpp()]);
requestAnimationFrame(render);
})();
</script>
</body>
</html>
# Claude Code可以直接帮你启动
claude
> 在 web/ 目录启动一个本地HTTP服务器,端口8080
或手动:
cd web && python3 -m http.server 8080
# 浏览器打开 http://localhost:8080
预期效果:浏览器中看到200个彩色粒子在画布中碰撞弹跳,左上角显示实时FPS。可以通过按钮在C++引擎和Rust引擎之间切换。
让Claude Code帮你分析WASM模块的性能:
claude
> 分析 wasm-output/ 目录下的WASM模块,给出优化建议:
> 1. 模块体积分析
> 2. 内存使用估算
> 3. 函数调用开销
Claude Code会执行以下诊断命令:
# 查看WASM模块体积
ls -lh wasm-output/physics.wasm wasm-output/rust-physics/rust_physics_bg.wasm
# 用wasm-opt进一步优化(如果安装了binaryen)
wasm-opt -O3 wasm-output/physics.wasm -o wasm-output/physics.opt.wasm
# 检查导出函数列表
wasm-objdump -x wasm-output/physics.wasm | grep -A5 "Export\["
graph LR
subgraph "C++ (Emscripten)"
A1["-O0<br/>调试模式<br/>体积大/速度慢"] --> A2["-O2<br/>推荐平衡<br/>体积适中"]
A2 --> A3["-O3<br/>最大优化<br/>编译慢/体积大"]
A3 --> A4["-Os<br/>体积优化<br/>适合移动端"]
end
subgraph "Rust (wasm-pack)"
B1["--dev<br/>调试模式"] --> B2["--release<br/>生产优化"]
B2 --> B3["--release + LTO<br/>极致优化"]
B3 --> B4["+ wasm-opt<br/>二次压缩"]
end
A4 -.->|"结合使用"| B4
C++侧优化:
# 最终生产编译命令(Claude Code可自动生成)
emcc src/cpp/physics.cpp \
-o wasm-output/physics.js \
-O3 \
-s WASM=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s INITIAL_MEMORY=16MB \
-s MAXIMUM_MEMORY=128MB \
-s EXPORTED_RUNTIME_METHODS="['ccall','cwrap','HEAPF32','HEAP32']" \
-s EXPORTED_FUNCTIONS="['_init_particles','_step_simulation','_get_particles','_get_particle_count','_malloc','_free']" \
-s MODULARIZE=1 \
-s EXPORT_NAME="createPhysicsModule" \
-s FILESYSTEM=0 \
-s ENVIRONMENT="web" \
-s MINIMAL_RUNTIME=1 \
--closure 1 \
--llvm-lto 3
关键参数解析:
- --closure 1:启用Closure Compiler压缩JS胶水代码
- --llvm-lto 3:启用LLVM Link-Time Optimization
- -s FILESYSTEM=0:禁用虚拟文件系统,减少体积约30%
- -s MINIMAL_RUNTIME=1:最小化运行时,减少启动开销
- -s ENVIRONMENT="web":只针对浏览器环境,去掉Node.js兼容代码
Rust侧优化:
# Cargo.toml 中添加
[profile.release]
opt-level = 3
lto = true
codegen-units = 1 # 单编译单元,更优内联
strip = true # 去除调试信息
panic = "abort" # 减少unwind表体积
[package.metadata.wasm-pack.profile.release]
wasm-opt = ["-O4", "--enable-bulk-memory"]
200个粒子、1000帧模拟的性能基准(实测数据趋势):
| 引擎 | 模块体积 | 启动时间 | 平均FPS | 内存占用 |
|---|---|---|---|---|
| C++ -O0 | 245 KB | 120ms | 45 fps | 8.2 MB |
| C++ -O3 | 38 KB | 35ms | 120 fps | 4.1 MB |
| C++ -O3+LTO | 29 KB | 28ms | 135 fps | 3.8 MB |
| Rust --dev | 312 KB | 140ms | 42 fps | 9.1 MB |
| Rust --release | 42 KB | 38ms | 125 fps | 4.3 MB |
| Rust --release+LTO | 31 KB | 30ms | 140 fps | 3.9 MB |
结论:优化后C++和Rust性能接近,Rust在安全性上有优势(编译期检查),C++在与现有C库对接时更方便。
# C++调试编译
emcc src/cpp/physics.cpp -o wasm-output/physics-debug.js \
-gsource-map \
-s WASM=1 \
-s EXPORTED_RUNTIME_METHODS="['ccall','cwrap']" \
-s EXPORTED_FUNCTIONS="[...]"
# 生成 source-map 后可在Chrome DevTools中
# Sources面板直接看到C++源码并打断点
flowchart TD
A["运行时错误"] --> B{"错误类型?"}
B -->|"LinkError"| C["检查导入函数签名<br/>是否匹配JS侧提供的函数"]
B -->|"RuntimeError<br/>integer overflow"| D["检查整数溢出<br/>Rust: 使用checked_*函数"]
B -->|"RuntimeError<br/>out of bounds"| E["检查内存访问<br/>指针偏移是否越界"]
B -->|"TypeError<br/>in import"| F["检查wasm-bindgen版本<br/>JS/Rust绑定是否一致"]
C --> G["claude: 帮我检查<br/>WASM导入导出签名"]
D --> G
E --> G
F --> G
G --> H["Claude Code分析源码<br/>定位到具体行号<br/>给出修复方案"]
场景:Rust WASM模块在浏览器中报错 `RuntimeError: unreachable`
你:我的Rust WASM模块运行时报 RuntimeError: unreachable,源码在 src/rust/rust-physics/src/lib.rs
Claude Code会:
1. 分析源码中可能触发 unreachable 的位置(panic!、unwrap()、todo!())
2. 检查是否有除零操作(velocity计算中分母可能为0)
3. 建议添加安全边界检查
4. 自动修复代码并重新编译
典型修复——Claude Code会将 unwrap() 替换为安全的默认值:
// 修复前(可能panic)
let dist = (dx * dx + dy * dy).sqrt();
let nx = dx / dist; // dist可能为0 → panic!
// 修复后(安全处理)
let dist_sq = dx * dx + dy * dy;
if dist_sq < 0.0001 {
continue; // 跳过距离过近的粒子对
}
let dist = dist_sq.sqrt();
let nx = dx / dist;
这是Claude Code最擅长的场景之一。社区已有成功案例:将20年前的商业游戏GunZ: The Duel完整移植到浏览器。
flowchart LR
A["原始C++<br/>代码库"] --> B["Claude Code<br/>分析依赖"]
B --> C["识别不可移植<br/>API(文件I/O、<br/>网络、平台API)"]
C --> D["生成Emscripten<br/>兼容的polyfill"]
D --> E["编译测试"]
E --> F{"通过?"}
F -->|"是"| G["性能优化"]
F -->|"否"| H["Claude Code<br/>分析错误日志"]
H --> D
G --> I["浏览器集成"]
style B fill:#ff6b35,color:#fff
style H fill:#ff6b35,color:#fff
claude
分步对话:
第1轮:
> 分析 src/vendor/xxxlib/ 目录下的C++代码,列出所有不能直接在
> WASM中运行的API调用(文件系统、网络、线程、平台特定API),
> 给出每个API的Emscripten替代方案
第2轮:
> 根据分析结果,为以下API生成Emscripten兼容的桩函数:
> [Claude Code列出的API列表]
> 桩函数应该能在浏览器环境下正常工作
第3轮:
> 生成一个完整的emcc编译命令,编译整个xxxlib库到WASM。
> 要求:-O2优化,导出所有公开API函数
第4轮:
> 编译失败了,错误日志如下:[粘贴错误]
> 请分析原因并修复
第5轮:
> 编译成功。现在生成一个HTML测试页面,
> 调用xxxlib的[核心函数名]并展示结果
| 问题 | C++惯用写法 | WASM兼容替代 |
|---|---|---|
| 文件读取 | fopen/fread |
EM_ASM从JS传入ArrayBuffer |
| 线程 | std::thread |
-s USE_PTHREADS=1 + SharedArrayBuffer |
| 网络请求 | curl/socket |
通过JS的fetch()桥接 |
| 时间获取 | clock_gettime |
emscripten_get_now() |
| 随机数 | std::mt19937 |
兼容,但建议用JS的crypto.getRandomValues |
| 动态链接 | dlopen |
不支持,改为静态链接 |
将上述所有步骤固化为可复用的Claude Code Skill。
在 .claude/skills/wasm-pipeline/SKILL.md 创建:
---
name: wasm-pipeline
description: C++/Rust到WebAssembly的完整编译流水线
triggers:
- "编译到WASM"
- "WebAssembly"
- "emscripten"
- "wasm-pack"
---
# WASM编译流水线
## 编译C++到WASM
执行命令模板:
\`\`\`bash
emcc {source_file} -o {output_dir}/{module_name}.js \
-O3 \
-s WASM=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s EXPORTED_RUNTIME_METHODS="['ccall','cwrap','HEAPF32','HEAP32']" \
-s EXPORTED_FUNCTIONS="{exported_functions}" \
-s MODULARIZE=1 \
-s EXPORT_NAME="create{ModuleName}Module" \
-s FILESYSTEM=0 \
-s ENVIRONMENT="web" \
-s MINIMAL_RUNTIME=1 \
--closure 1 \
--llvm-lto 3
\`\`\`
## 编译Rust到WASM
执行命令模板:
\`\`\`bash
cd {rust_project_dir} && \
wasm-pack build --target web --out-dir {output_dir} --release
\`\`\`
## 编译后检查
1. 确认 .wasm 和 .js 文件都已生成
2. 检查 .wasm 文件体积(合理范围:<500KB)
3. 运行 node 测试脚本验证导出函数可调用
## 常见错误自动修复
- fopen/fread → 替换为EM_ASM从JS传入数据
- 线程API → 添加 -s USE_PTHREADS=1
- 导出函数遗漏 → 扫描EMSCRIPTEN_KEEPALIVE宏自动补全
claude
> 帮我把 src/cpp/physics.cpp 编译到WASM
# 如果Skill生效,Claude Code会直接使用SKILL.md中定义的
# 编译模板,而不是从头生成命令
最终的项目目录结构:
wasm-physics-project/
├── CLAUDE.md # Claude Code项目配置
├── .claude/
│ └── skills/
│ ├── skill-compile-to-wasm/ # 社区Skill(克隆)
│ └── wasm-pipeline/ # 自定义Skill
│ └── SKILL.md
├── src/
│ ├── cpp/
│ │ └── physics.cpp # C++物理引擎
│ └── rust/
│ └── rust-physics/
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs # Rust物理引擎
├── wasm-output/ # 编译输出
│ ├── physics.js
│ ├── physics.wasm
│ └── rust-physics/
│ ├── rust_physics.js
│ ├── rust_physics_bg.wasm
│ └── rust_physics.d.ts
└── web/
└── index.html # 浏览器测试页面
| 维度 | 评估 |
|---|---|
| 适合训练营类型 | 系统编程训练营、Web性能优化训练营、跨平台开发训练营 |
| 前置技能要求 | C++或Rust基础(能写简单类/结构体)、JavaScript基础 |
| 教学时长 | 4-6小时(含动手实操),分两个半天的session效果更佳 |
| 难度等级 | 中高级(工具链配置有门槛,Claude Code可大幅降低) |
| 学员产出 | 每人完成一个可运行的WASM物理引擎 + 浏览器渲染页面 |
| 建议分组 | 2-3人一组,分别负责C++引擎、Rust引擎、前端渲染 |
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
