Emscripten 从 C/C++ 调用 JavaScript

Emscripten是一个开源编译器工具链,主要用于将C/C++代码编译为WebAssembly(Wasm)

因为最后生成的WASM是在前端使用和运行,所以少不了c/c++和js代码之间的接口调用。

Emscripten 提供了三种从 C/C++ 调用 JavaScript 的主要方法:

  1. 使用运行脚本emscripten_run_script()
  2. 写 在线 JavaScript EM_JS
  3. 写 在线 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 Number
  • const 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 进行快速开发或调试。

相关推荐
胡萝卜术5 小时前
从API调用到手写LRU:力扣146「LRU缓存」的哈希表+双向链表终极进化之路
前端·javascript·面试
科技道人5 小时前
记录 默认置灰/禁用 app ‘Search Engine Selector‘ 的disable按钮
开发语言·前端·javascript
天疆说5 小时前
Zotero Connector 在 Edge 里找不到桌面 Zotero(Linux / Zotero 9.0.6 / Edge 150)
linux·前端·edge
李伟_Li慢慢7 小时前
02-从硬件说起WebGL
前端·three.js
kyriewen8 小时前
看了微软几万人用AI编程的数据——效率涨24%的代价没人提
前端·ai编程·claude
2601_963771378 小时前
How to Scale Your WordPress Store Traffic in 2026
前端·php·plugin
亿元程序员8 小时前
老板说我的3D箭头游戏用来做试玩太普通了,没人想玩,让我变点花样...
前端
李伟_Li慢慢8 小时前
01-threejs架构原理-课程简介
前端·three.js
旖-旎9 小时前
《LeetCode647 回文子串 || LeetCode 5 最长回文子串》
c++·算法·leetcode·动态规划·哈希算法
Darkwanderor9 小时前
对Linux的进程控制的研究
linux·运维·c++