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 进行快速开发或调试。

相关推荐
kyriewen18 小时前
别再 console.log 了:5 个 Chrome DevTools 调试技巧,用过就回不去了
前端·javascript·面试
IT_陈寒19 小时前
Python搞不定字符串编码?这破玩意坑我两小时!
前端·人工智能·后端
To_OC19 小时前
LC 1 两数之和:面试第一道必考题,暴力解法直接被面试官 pass
javascript·算法·leetcode
DigitalOcean21 小时前
Laravel 开发者已在 DigitalOcean 上开通超过 10 万台服务器
前端·laravel
星始流年21 小时前
从 Tool 到 Skill——基于 LangChain 的服务端Skill实现
前端·langchain·agent
李惟21 小时前
开源本地通信库,纯客户端 RPC,像聊天一样通信
前端
YAwu1121 小时前
深入解析 React 炫彩鼠标跟随标题组件:从坐标定位到动画性能
前端·react.js
GuWenyue21 小时前
排序效率低?5分钟吃透快速排序,性能飙升至O(nlogn)
前端·javascript·面试
OpenTiny社区21 小时前
🎨 看完 GenUI SDK 源码我悟了!
前端·vue.js·github
叁两21 小时前
前端转型AI Agent该如何学习?(前置篇)
前端·人工智能·node.js