从C++到wasm,并在JavaScript中调用
我将为你提供一个完整的从 C++ 编译到 WebAssembly 并在 JavaScript 中调用的指南。
环境准备
1. 安装 Emscripten
# 克隆 emsdk 仓库
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
# 安装最新版本
./emsdk install latest
./emsdk activate latest
# 设置环境变量
source ./emsdk_env.sh
# 验证安装
emcc --version
基础示例
步骤 1:编写 C++ 代码
math_operations.cpp
#include <emscripten.h>
#include <cmath>
// 使用 extern "C" 防止 C++ 名称修饰
extern "C" {
EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
return a + b;
}
EMSCRIPTEN_KEEPALIVE
double multiply(double a, double b) {
return a * b;
}
EMSCRIPTEN_KEEPALIVE
double squareRoot(double x) {
return sqrt(x);
}
EMSCRIPTEN_KEEPALIVE
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
}
步骤 2:编译为 WebAssembly
# 基础编译命令
emcc math_operations.cpp \
-o math_operations.js \
-s WASM=1 \
-s EXPORTED_FUNCTIONS='["_add", "_multiply", "_squareRoot", "_factorial"]' \
-s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'
# 或者使用更现代的 ES6 模块方式
emcc math_operations.cpp \
-o math_operations.mjs \
-s WASM=1 \
-s MODULARIZE=1 \
-s EXPORT_ES6=1 \
-s EXPORTED_FUNCTIONS='["_add", "_multiply", "_squareRoot", "_factorial"]' \
-s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'
在 JavaScript 中调用
方式 1:使用传统胶水代码
index.html
<!DOCTYPE html>
<html>
<head>
<title>WASM C++ 示例</title>
</head>
<body>
<h1>WebAssembly C++ 函数调用示例</h1>
<div>
<button onclick="testAdd()">测试加法</button>
<button onclick="testMultiply()">测试乘法</button>
<button onclick="testSquareRoot()">测试平方根</button>
<button onclick="testFactorial()">测试阶乘</button>
</div>
<div id="output" style="margin-top: 20px; padding: 10px; border: 1px solid #ccc;"></div>
<!-- 加载 Emscripten 生成的 JS 胶水代码 -->
<script src="math_operations.js"></script>
<script>
let add, multiply, squareRoot, factorial;
// 等待 WASM 模块初始化完成
Module.onRuntimeInitialized = function() {
// 使用 cwrap 包装 C++ 函数
add = Module.cwrap('add', 'number', ['number', 'number']);
multiply = Module.cwrap('multiply', 'number', ['number', 'number']);
squareRoot = Module.cwrap('squareRoot', 'number', ['number']);
factorial = Module.cwrap('factorial', 'number', ['number']);
logOutput("WASM 模块加载完成!");
};
function testAdd() {
if (!add) return;
const result = add(15, 27);
logOutput(`add(15, 27) = ${result}`);
}
function testMultiply() {
if (!multiply) return;
const result = multiply(3.14, 2.5);
logOutput(`multiply(3.14, 2.5) = ${result}`);
}
function testSquareRoot() {
if (!squareRoot) return;
const result = squareRoot(16);
logOutput(`squareRoot(16) = ${result}`);
}
function testFactorial() {
if (!factorial) return;
const result = factorial(5);
logOutput(`factorial(5) = ${result}`);
}
function logOutput(message) {
const output = document.getElementById('output');
output.innerHTML += message + '<br>';
}
</script>
</body>
</html>
方式 2:使用 ES6 模块(推荐)
modern-example.html
<!DOCTYPE html>
<html>
<head>
<title>WASM ES6 模块示例</title>
</head>
<body>
<h1>现代 ES6 模块方式</h1>
<div id="output"></div>
<script type="module">
import createModule from './math_operations.mjs';
class MathWASM {
constructor() {
this.module = null;
this.initialized = false;
}
async init() {
try {
this.module = await createModule();
this.initialized = true;
this.log("WASM 模块初始化成功!");
// 包装所有函数
this.add = this.module.cwrap('add', 'number', ['number', 'number']);
this.multiply = this.module.cwrap('multiply', 'number', ['number', 'number']);
this.squareRoot = this.module.cwrap('squareRoot', 'number', ['number']);
this.factorial = this.module.cwrap('factorial', 'number', ['number']);
this.runTests();
} catch (error) {
this.log(`初始化失败: ${error}`);
}
}
runTests() {
if (!this.initialized) return;
this.log(`加法测试: 8 + 12 = ${this.add(8, 12)}`);
this.log(`乘法测试: 4.5 × 3.2 = ${this.multiply(4.5, 3.2)}`);
this.log(`平方根测试: √25 = ${this.squareRoot(25)}`);
this.log(`阶乘测试: 6! = ${this.factorial(6)}`);
}
log(message) {
const output = document.getElementById('output');
output.innerHTML += message + '<br>';
}
}
// 初始化 WASM 模块
const mathWASM = new MathWASM();
mathWASM.init();
</script>
</body>
</html>
处理字符串和内存操作
C++ 代码(字符串处理)
string_operations.cpp
#include <emscripten.h>
#include <cstring>
#include <string>
extern "C" {
EMSCRIPTEN_KEEPALIVE
int stringLength(const char* str) {
return strlen(str);
}
EMSCRIPTEN_KEEPALIVE
void reverseString(char* str, int length) {
int start = 0;
int end = length - 1;
while (start < end) {
char temp = str[start];
str[start] = str[end];
str[end] = temp;
start++;
end--;
}
}
EMSCRIPTEN_KEEPALIVE
char* concatenateStrings(const char* str1, const char* str2) {
std::string result = std::string(str1) + std::string(str2);
char* output = (char*)malloc(result.length() + 1);
strcpy(output, result.c_str());
return output;
}
}
JavaScript 调用字符串函数
string-example.html
<!DOCTYPE html>
<html>
<head>
<title>WASM 字符串操作</title>
</head>
<body>
<script src="string_operations.js"></script>
<script>
Module.onRuntimeInitialized = function() {
// 包装函数
const stringLength = Module.cwrap('stringLength', 'number', ['string']);
const reverseString = Module.cwrap('reverseString', null, ['number', 'number']);
const concatenateStrings = Module.cwrap('concatenateStrings', 'number', ['string', 'string']);
// 测试字符串长度
const testStr = "Hello WebAssembly";
console.log(`"${testStr}" 的长度: ${stringLength(testStr)}`);
// 测试字符串反转
const strToReverse = "Hello World";
const lengthBytes = Module.lengthBytesUTF8(strToReverse) + 1;
const buffer = Module._malloc(lengthBytes);
Module.stringToUTF8(strToReverse, buffer, lengthBytes);
reverseString(buffer, strToReverse.length);
const reversed = Module.UTF8ToString(buffer);
console.log(`反转 "${strToReverse}": ${reversed}`);
Module._free(buffer);
// 测试字符串连接
const ptr = concatenateStrings("Hello ", "WASM");
const concatenated = Module.UTF8ToString(ptr);
console.log(`连接结果: ${concatenated}`);
Module._free(ptr); // 记得释放内存
};
</script>
</body>
</html>
编译优化选项
生产环境编译
# 优化编译(推荐用于生产环境)
emcc math_operations.cpp \
-o math_operations.js \
-O3 \
-s WASM=1 \
-s MODULARIZE=1 \
-s EXPORT_ES6=1 \
-s EXPORTED_FUNCTIONS='["_add", "_multiply", "_squareRoot", "_factorial"]' \
-s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \
-s ALLOW_MEMORY_GROWTH=1
最小化编译
# 最小化文件大小
emcc math_operations.cpp \
-o math_operations.js \
-Oz \
-s WASM=1 \
-s MODULARIZE=1 \
-s SINGLE_FILE=1 \
-s EXPORTED_FUNCTIONS='["_add", "_multiply", "_squareRoot", "_factorial"]' \
-s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'
运行步骤
-
编译 C++ 代码:
emcc math_operations.cpp -o math_operations.js -s WASM=1 -s EXPORTED_FUNCTIONS='["_add", "_multiply", "_squareRoot", "_factorial"]' -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' -
启动本地服务器(由于浏览器安全限制,不能直接打开 HTML 文件):
# 使用 Python python -m http.server 8000 # 或使用 Node.js npx http-server -
在浏览器中访问:
http://localhost:8000
关键要点
- 使用
extern "C":防止 C++ 名称修饰 - 使用
EMSCRIPTEN_KEEPALIVE:确保函数被导出且不被优化掉 - 正确编译参数:确保导出需要的函数和运行时方法
- 异步加载:WASM 模块需要时间初始化
- 内存管理:对于返回指针的函数,记得在 JavaScript 中释放内存
这个完整的流程应该能帮助你成功地将 C++ 代码编译为 WebAssembly 并在 JavaScript 中调用。
挣钱养家

浙公网安备 33010602011771号