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

相关推荐
一晌贪欢i1 小时前
WebContainer 重点介绍
前端·webcontainer
TANGLONG2221 小时前
【C++】继承详解——基类/派生类、作用域、默认函数、菱形继承(超详细)
java·c语言·c++·经验分享·笔记·ajax
鹏程十八少1 小时前
12. Android 协程通关秘籍:31 道资深工程师面试题精讲
前端·后端·面试
小侯不躺平.1 小时前
C++ Boost库【2】 --stringalgo字符串算法
linux·c++·算法
Dlrb12111 小时前
C语言-字符串指针与函数指针
java·c语言·前端
PBitW2 小时前
组件封装注意事项
前端·vue.js
code_whiter2 小时前
C++11(stack和queue)
开发语言·c++
weiggle2 小时前
Android 输入事件分发流程:从物理触控到 Activity 的完整旅程
前端
yingyima2 小时前
开发者必备在线工具集合 2025:实战案例解析
前端