Emscripten是一个开源编译器工具链,主要用于将C/C++代码编译为WebAssembly(Wasm)
因为最后生成的WASM是在前端使用和运行,所以少不了c/c++和js代码之间的接口调用。
Emscripten 提供了三种从 C/C++ 调用 JavaScript 的主要方法:
- 使用
ccall直接调用 - 使用
cwrap包装成JS函数 - 通过
Module对象以下划线_开头的函数名直接调用(这个最方便使用)
1. 编写供 JS 调用的 C++ 代码(基本语法和宏)
#include <emscripten.h> // 必须包含的头文件
// 使用 extern "C" 避免 C++ 名称修饰
extern "C" {
// 使用 EMSCRIPTEN_KEEPALIVE 宏确保函数不会被优化删除
EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
return a + b;
}
} // extern "C"
2. JS 调用 c++ 代码
2.1 ccall
// 使用 ccall(函数名,返回类型,[参数类型,...],[参数值,...])
var result = Module.ccall('add', 'number', ['number', 'number'], [10, 20]);
console.log(result); // 输出 30
2.2 cwrap
// 使用 cwrap(函数名,返回类型,[参数类型,...])
var addFunction = Module.cwrap('add', 'number', ['number', 'number']);
var result = addFunction(10, 20);
console.log(result); // 输出 30
优点:自动化程度高,C函数参数类型为char*时,Emscripten会自动分配和释放临时内存
2.3 通过Module对象以下划线_开头的函数名直接调用
var result = Module._add(10, 20);
console.log(result); // 输出 30
减少调用开销,性能更好,需手动管理内存
3. 如何处理复杂数据类型
对于字符串、数组等复杂数据类型,你需要通过Emscripten的堆(Heap)来进行内存操作。
3.1 字符串传递:使用Emscripten提供的字符串转换函数
C++ 返回字符串
// C++ 返回字符串
EMSCRIPTEN_KEEPALIVE
const char* get_greeting() {
return "Hello from C++!";
}
// JavaScript 端调用并转换字符串
var ptr = Module._get_greeting();
var str = Module.UTF8ToString(ptr);
console.log(str); // 输出 "Hello from C++!"
JS 传递字符串
#include <emscripten.h>
#include <string.h>
EMSCRIPTEN_KEEPALIVE
int get_string_length(const char* str) {
return strlen(str);
}
// 为字符串分配内存,并将字符串复制进去
const str = 'Hello Direct Call';
const buffer = Module._malloc(str.length + 1); // 额外字节存放字符串结束符 '\0'
Module.stringToUTF8(str, buffer, str.length + 1); // 将JavaScript字符串转换为UTF8编码存入内存
// 直接调用C函数
const length = Module._get_string_length(buffer);
console.log('String length:', length);
// 务必释放内存!
Module._free(buffer);
编译命令中要加入这段
-sEXPORTED_RUNTIME_METHODS=['UTF8ToString','stringToUTF8']
内存管理是关键
若手动分配内存(如使用Module._malloc和Module.stringToUTF8),务必在最后使用Module._free释放,防止内存泄漏。若使用ccall/cwrap并指定'string'参数类型,Emscripten会自动管理临时分配的内存。