Emscripten是一个开源编译器工具链,主要用于将C/C++代码编译为WebAssembly(Wasm)
因为最后生成的WASM是在前端使用和运行,所以少不了c/c++和js代码之间的接口调用。
Emscripten 提供了三种从 C/C++ 调用 JavaScript 的主要方法:
- 使用运行脚本
emscripten_run_script() - 写 在线 JavaScript
EM_JS - 写 在线 JavaScript
EM_ASM
最直接但略慢的方法是使用 emscripten_run_script() 这实际上运行指定的 使用 C/C++ 编写的 JavaScript 代码eval()
cpp
emscripten_run_script("alert('hi')");
C/C++ 调用 JavaScript 的更快方法是编写"inline JavaScript"
1. EM_JS
1.1 基本语法
cpp
#include <emscripten.h>
EM_JS(return_type, function_name, (parameters), {
// JavaScript 代码
return value;
});
1.2 参数说明
- return_type: C/C++ 函数的返回类型
- function_name: 在 C/C++ 中使用的函数名
- parameters : 参数列表,格式为
(type1 param1, type2 param2) - JavaScript代码: 实际执行的 JS 代码
1.3 使用示例
1.3.1 简单的 JavaScript 调用
cpp
#include <emscripten.h>
#include <stdio.h>
// 定义 EM_JS 函数 - 调用浏览器 alert
EM_JS(void, js_alert, (const char* msg), {
alert(UTF8ToString(msg));
});
// 带返回值的 EM_JS 函数
EM_JS(int, js_get_number, (), {
return 42;
});
// 带参数的 EM_JS 函数
EM_JS(int, js_add, (int a, int b), {
return a + b;
});
int main() {
js_alert("Hello from C++!");
int num = js_get_number();
printf("Got number from JS: %d\n", num);
int result = js_add(5, 3);
printf("5 + 3 = %d\n", result);
return 0;
}
1.3.2 访问 DOM 元素
cpp
#include <emscripten.h>
// 修改页面标题
EM_JS(void, set_page_title, (const char* title), {
document.title = UTF8ToString(title);
});
// 获取元素内容
EM_JS(const char*, get_element_text, (const char* id), {
var element = document.getElementById(UTF8ToString(id));
if (element) {
var text = element.textContent || element.innerText;
var length = lengthBytesUTF8(text) + 1;
var buffer = _malloc(length);
stringToUTF8(text, buffer, length);
return buffer;
}
return null;
});
// 设置元素内容
EM_JS(void, set_element_text, (const char* id, const char* text), {
var element = document.getElementById(UTF8ToString(id));
if (element) {
element.textContent = UTF8ToString(text);
}
});
1.3.3 处理 JavaScript 对象和数组
cpp
#include <emscripten.h>
// 创建 JavaScript 对象
EM_JS(void, create_js_object, (), {
var obj = {
name: "Emscripten",
version: 3.1,
features: ["WebAssembly", "JavaScript"]
};
console.log(obj);
});
// 调用 JavaScript 函数
EM_JS(void, call_js_function, (const char* funcName), {
if (typeof window[UTF8ToString(funcName)] === 'function') {
window[UTF8ToString(funcName)]();
}
});
1.3.4 异步操作示例
cpp
#include <emscripten.h>
#include <stdio.h>
// 异步获取数据
EM_JS(void, fetch_data_async, (const char* url), {
fetch(UTF8ToString(url))
.then(response => response.json())
.then(data => {
console.log("Received data:", data);
// 可以调用回传到 C++ 的函数
if (Module.onDataReceived) {
Module.onDataReceived(JSON.stringify(data));
}
})
.catch(error => console.error('Error:', error));
});
// 在 C++ 中定义回调函数
EM_JS(void, setup_callbacks, (), {
Module.onDataReceived = function(data) {
console.log("Data received in C++ callback:", data);
// 这里可以调用 C++ 函数
_cpp_data_handler(allocateUTF8(data));
};
});
// 需要在 C++ 中实现的函数
extern "C" {
void cpp_data_handler(const char* data) {
printf("C++ received: %s\n", data);
}
}
1.4 类型转换
EM_JS 自动处理基本类型的转换:
int,float,double↔ JavaScript Numberconst char*↔ JavaScript String (使用UTF8ToString()和allocateUTF8())void↔ JavaScript undefined
2. EM_ASM
2.1 基本语法
cpp
EM_ASM(
// 直接嵌入的 JavaScript 代码
console.log('Hello from JavaScript!');
return 42;
);
2.2 参数传递
cpp
EM_ASM({
console.log('Parameters:', $0, $1, $2);
}, 100, "hello", 3.14);
// 操作 DOM
EM_ASM({
var div = document.createElement('div');
div.innerHTML = '由 C++ 创建的 DIV';
document.body.appendChild(div);
});
3. EM_JS 对比 EM_ASM
| 特性 | EM_ASM | EM_JS |
|---|---|---|
| 语法形式 | 宏,直接嵌入 JS 代码 | 函数声明,类似普通 C 函数 |
| 编译处理 | 预处理期展开 | 编译期生成完整函数 |
| 性能 | 每次调用都要解析执行 | 编译为可重用函数,性能更好 |
| 类型检查 | 弱类型检查 | 强类型检查 |
| 代码组织 | 内联在调用处 | 独立的函数定义 |
| 调试支持 | 调用栈不清晰 | 清晰的函数调用栈 |
| 适用场景 | 简单的单次调用 | 复杂的、多次调用的函数 |
选择建议
- 选择 EM_ASM:简单的单次调用、快速调试、代码量小的场景
- 选择 EM_JS:复杂的业务逻辑、需要多次调用、类型安全要求高、性能敏感的场景
在实际项目中,通常建议主要使用 EM_JS 来保证代码质量和性能,只在特殊情况下使用 EM_ASM 进行快速开发或调试。