与代码交互

Emscripten 提供多种方法来连接和交互编译后的 C 或 C++ 与 JavaScript 之间

本文解释了上面列出的每种方法,并提供了指向更详细信息的链接。

注意

有关编译后的代码如何与浏览器环境交互的信息,请参见 Emscripten 运行时环境。有关文件系统相关事项,请参见 文件系统概述.

注意

在您可以调用代码之前,运行时环境可能需要加载内存初始化文件,预加载文件,或根据优化和构建设置执行其他异步操作。请参见 如何判断页面完全加载并可以安全调用编译后的函数? 在常见问题解答中。

使用 ccall/cwrap 从 JavaScript 调用编译后的 C 函数

从 JavaScript 调用编译后的 C 函数的最简单方法是使用 ccall()cwrap().

ccall() 使用指定的参数调用编译后的 C 函数,并返回结果,而 cwrap() “包装”编译后的 C 函数,并返回一个您可以正常调用的 JavaScript 函数。因此,cwrap() 对于您计划多次调用编译后的函数的情况更有用。

考虑下面显示的 **test/hello_function.cpp** 文件。要编译的 int_sqrt() 函数包装在 extern "C" 中以防止 C++ 命名修饰。

// Copyright 2012 The Emscripten Authors.  All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License.  Both these licenses can be
// found in the LICENSE file.

#include <math.h>

extern "C" {

int int_sqrt(int x) {
  return sqrt(x);
}

}

要编译此代码,请在 Emscripten 主目录中运行以下命令

emcc test/hello_function.cpp -o function.html -sEXPORTED_FUNCTIONS=_int_sqrt -sEXPORTED_RUNTIME_METHODS=ccall,cwrap

EXPORTED_FUNCTIONS 告诉编译器我们希望从编译后的代码中访问什么(如果未使用,其他所有内容可能会被删除),而 EXPORTED_RUNTIME_METHODS 告诉编译器我们希望使用运行时函数 ccallcwrap(否则它不会包含它们)。

注意

EXPORTED_FUNCTIONS 会影响编译到 JavaScript。如果您先编译到目标文件,然后将目标文件编译到 JavaScript,则需要在第二个命令中使用该选项。如果您像本例中这样一起执行(源代码直接到 JavaScript),那么它当然可以使用。

编译后,您可以使用以下 JavaScript 使用 cwrap() 调用此函数

int_sqrt = Module.cwrap('int_sqrt', 'number', ['number'])
int_sqrt(12)
int_sqrt(28)

第一个参数是要包装的函数的名称,第二个是函数的返回类型(如果没有,则为 JavaScript null 值),第三个是参数类型的数组(如果没有参数,则可以省略)。类型为“number”(对于对应于 C 整数、浮点数或通用指针的 JavaScript 数字)、“string”(对于对应于 C char* 的 JavaScript 字符串,该字符串表示一个字符串)或“array”(对于对应于 C 数组的 JavaScript 数组或类型化数组;对于类型化数组,它必须是 Uint8Array 或 Int8Array)。

您可以通过首先在 Web 浏览器中打开生成的页面 **function.html** 来自己运行它(页面加载时不会发生任何事情,因为没有 main())。打开一个 JavaScript 环境(Firefox 上为 **Control-Shift-K**,Chrome 上为 **Control-Shift-J**),并将以上命令作为三个独立的命令输入,在每个命令之后按 **Enter**。您应该获得结果 35 - 这些输入使用 C++ 整数数学的预期输出。

ccall() 类似,但接收另一个参数,其中包含要传递给函数的参数

// Call C from JavaScript
var result = Module.ccall('int_sqrt', // name of C function
  'number', // return type
  ['number'], // argument types
  [28]); // arguments

// result is 5

注意

此示例说明了其他几点,在使用 ccall()cwrap() 时,您应该记住这些要点

  • 这些方法可以与编译后的 **C** 函数一起使用 - 命名修饰后的 C++ 函数将无法使用。

  • 我们强烈建议您导出要从 JavaScript 调用的函数

    • 导出是在编译时完成的。例如:-sEXPORTED_FUNCTIONS=_main,_other_function 导出 main()other_function()

    • 请注意,您需要在 EXPORTED_FUNCTIONS 列表中的函数名称开头添加 _

    • 请注意,该列表中提到了 _main。如果您没有它,编译器将将其作为死代码消除。导出的函数列表是将保持存活的**整个**列表(除非其他代码以其他方式保持存活)。

    • Emscripten 执行 死代码消除 以最小化代码大小 - 导出确保您需要的函数不会被删除。

    • 在更高优化级别(-O2 及更高版本)下,代码会进行压缩,包括函数名称。导出函数使您可以继续使用全局 Module 对象通过原始名称访问它们。

    • 如果您要导出 JS 库函数(例如,来自 src/library*.js 文件中的某个函数),那么除了 EXPORTED_FUNCTIONS 之外,您还需要将其添加到 DEFAULT_LIBRARY_FUNCS_TO_INCLUDE 中,因为后者将强制将该方法实际包含在构建中。

  • 编译器将删除它没有看到使用的代码,以改善代码大小。如果您在编译器可以看到的地方使用 ccall,例如 --pre-js--post-js 中的代码,它就可以正常工作。如果您在编译器没有看到的地方使用它,例如 HTML 上的另一个脚本标签或 JS 控制台中(就像我们在本教程中所做的那样),那么由于优化和压缩,您应该从运行时导出 ccall,使用 EXPORTED_RUNTIME_METHODS,例如使用 -sEXPORTED_RUNTIME_METHODS=ccall,cwrap,并在 Module 上调用它(它包含所有导出的内容,以一种不受压缩或优化影响的安全方式)。

从 NodeJS 与用 C/C++ 编写的 API 交互

假设您有一个 C 库,它公开了一些过程

//api_example.c
#include <stdio.h>
#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
void sayHi() {
  printf("Hi!\n");
}

EMSCRIPTEN_KEEPALIVE
int daysInWeek() {
  return 7;
}

使用 emcc 编译库

emcc api_example.c -o api_example.js -sMODULARIZE -sEXPORTED_RUNTIME_METHODS=ccall

从节点中引入库并调用其过程

var factory = require('./api_example.js');

factory().then((instance) => {
  instance._sayHi(); // direct calling works
  instance.ccall("sayHi"); // using ccall etc. also work
  console.log(instance._daysInWeek()); // values can be returned, etc.
});

使用 MODULARIZE 选项可以让 emcc 以模块化格式发出代码,方便使用 require() 导入和使用:require() 模块返回一个工厂函数,可以实例化编译后的代码,并返回一个 Promise 以告知我们代码何时准备就绪,并提供模块实例作为参数。

(请注意,我们在这里使用 ccall,因此我们需要将其添加到导出的运行时方法中,与之前一样。)

从 JavaScript 直接调用编译后的 C/C++ 代码

原始源代码中的函数会成为 JavaScript 函数,因此如果您自己进行类型转换,就可以直接调用它们 - 这比使用 ccall()cwrap() 更快,但也更复杂。

要直接调用方法,您需要使用生成的代码中出现的完整名称。这与原始的 C 函数相同,但前面有一个 _

注意

如果您使用 ccall()cwrap(),则无需在函数调用前加上 _ - 只需使用 C 名称即可。

传递给函数的参数和从函数接收的参数需要是基本值。

  • 整数和浮点数可以按原样传递。

  • 指针也可以按原样传递,因为它们在生成的代码中只是整数。

  • JavaScript 字符串 someString 可以使用 ptr = stringToNewUTF8(someString) 转换为 char *

    注意

    转换为指针会分配内存,需要通过调用 free(ptr) 来释放内存(在 JavaScript 侧为 _free) -

  • 从 C/C++ 接收到的 char * 可以使用 UTF8ToString() 转换为 JavaScript 字符串。

    preamble.js 中还有其他用于转换字符串和编码的便捷函数。

  • 其他值可以通过 emscripten::val 传递。查看有关 as_handle 和 take_ownership 方法 的示例。

从 C/C++ 调用 JavaScript

Emscripten 提供了两种从 C/C++ 调用 JavaScript 的主要方法:使用 emscripten_run_script() 运行脚本或编写“内联 JavaScript”。

最直接但速度稍慢的方法是使用 emscripten_run_script()。这实际上使用 eval() 从 C/C++ 运行指定的 JavaScript 代码。例如,要使用文本“hi”调用浏览器的 alert() 函数,您将调用以下 JavaScript

emscripten_run_script("alert('hi')");

注意

函数 alert 存在于浏览器中,但不存在于 node 或其他 JavaScript shell 中。更通用的替代方法是调用 console.log.

从 C 调用 JavaScript 的更快方法是编写“内联 JavaScript”,使用 EM_JS()EM_ASM()(以及相关的宏)。

EM_JS 用于在 C 文件中声明 JavaScript 函数。“alert”示例可以使用 EM_JS 编写,如下所示

#include <emscripten.h>

EM_JS(void, call_alert, (), {
  alert('hello world!');
  throw 'all done';
});

int main() {
  call_alert();
  return 0;
}

EM_JS 的实现本质上是 实现 JavaScript 库 的简写。

EM_ASM 用于以类似于内联汇编代码的方式。可以使用内联 JavaScript 编写“alert”示例,如下所示

#include <emscripten.h>

int main() {
  EM_ASM(
    alert('hello world!');
    throw 'all done';
  );
  return 0;
}

编译并运行后,Emscripten 将执行这两行 JavaScript 代码,就像它们直接出现在生成的代码中一样。结果将是一个警报,然后是一个异常。(但是,请注意,在幕后,即使在这种情况下,Emscripten 仍然会进行函数调用,这会带来一定程度的开销。)

您还可以将值从 C 发送到 EM_ASM 中的 JavaScript,例如

EM_ASM({
  console.log('I received: ' + $0);
}, 100);

这将显示 I received: 100

您还可以接收返回值,例如,以下代码将打印 I received: 100,然后打印 101

int x = EM_ASM_INT({
  console.log('I received: ' + $0);
  return $0 + 1;
}, 100);
printf("%d\n", x);

有关更多详细信息,请参阅 emscripten.h docs

注意

  • 您需要使用相应的宏 EM_ASM_INTEM_ASM_DOUBLEEM_ASM_PTR 指定返回值是 intdouble 还是指针类型。(EM_ASM_PTREM_ASM_INT 相同,除非使用 MEMORY64,因此主要是在需要与 MEMORY64 兼容的代码中使用。)

  • 输入值显示为 $0$1 等。

  • return 用于提供从 JavaScript 发送回 C 的值。

  • 请注意如何使用 {} 将代码括起来。这是为了区分代码与稍后传递的参数,即输入值(这就是 C 宏的工作原理)。

  • 使用 EM_ASM 宏时,确保只使用单引号 (')。双引号 (") 会导致语法错误,编译器不会检测到该错误,只有在运行有问题的代码时查看 JavaScript 控制台才能看到该错误。

  • clang-format 可能会破坏 Javascript 结构,例如 => 变成 = >。为了避免这种情况,请在您的 .clang-format 中添加:WhitespaceSensitiveMacros: ['EM_ASM', 'EM_JS', 'EM_ASM_INT', 'EM_ASM_DOUBLE', 'EM_ASM_PTR', 'MAIN_THREAD_EM_ASM', 'MAIN_THREAD_EM_ASM_INT', 'MAIN_THREAD_EM_ASM_DOUBLE', 'MAIN_THREAD_EM_ASM_DOUBLE', 'MAIN_THREAD_ASYNC_EM_ASM']。或者,通过在 EM_ASM 部分之前编写 // clang-format off 并在之后编写 // clang-format on关闭 clang-format

在 JavaScript 中实现 C API

可以在 JavaScript 中实现 C API!这是 Emscripten 的许多库(如 SDL1 和 OpenGL)中使用的方法。

您可以使用它从 C/C++ 编写自己的 API。为此,您定义接口,使用 extern 装饰以将 API 中的方法标记为外部符号。然后,您只需在 library.js(默认情况下)中添加其定义即可在 JavaScript 中实现这些符号。编译 C 代码时,编译器会在 JavaScript 库中查找相关的外部符号。

默认情况下,实现将添加到 library.js 中(这就是您在其中找到 Emscripten 的 libc 部分的地方)。您可以将 JavaScript 实现放在您自己的库文件中,并使用 emcc 选项 --js-library 添加它。有关完整的示例,包括您应在 JavaScript 库文件中使用的语法,请参阅 test/test_other.py 中的 test_js_libraries

作为一个简单的例子,考虑以下 C 代码

extern void my_js(void);

int main() {
  my_js();
  return 1;
}

注意

使用 C++ 时,应将 extern void my_js(); 封装在 extern "C" {} 块中,以防止 C++ 命名修饰

extern "C" {
  extern void my_js();
}

然后,您可以通过简单地将实现添加到 **library.js**(或您自己的文件)中,在 JavaScript 中实现 my_js。就像我们从 C 调用 JavaScript 的其他示例一样,下面的示例只是使用简单的 alert() 函数创建一个对话框。

my_js: function() {
  alert('hi');
},

如果您将其添加到您自己的文件中,您应该编写类似以下内容

addToLibrary({
  my_js: function() {
    alert('hi');
  },
});

addToLibrary 将输入对象的属性复制到 LibraryManager.library(所有 JavaScript 库代码所在的全局对象)。在本例中,它将一个名为 my_js 的函数添加到此对象上。

JavaScript 在库文件中的限制

如果您不熟悉 JavaScript,例如,如果您是 C/C++ 程序员并且只是使用 emscripten,那么以下问题可能不会出现,但如果您是经验丰富的 JavaScript 程序员,您需要知道某些常见的 JavaScript 实践在 emscripten 库文件中无法以某些方式使用。

为了节省空间,默认情况下,emscripten 只包含从 C/C++ 引用的库属性。它通过对链接的 JavaScript 库中每个使用的属性调用 toString 来实现这一点。这意味着您无法直接使用闭包,例如,因为 toString 与之不兼容 - 就像使用字符串创建 Web Worker 时一样,您也不能传递闭包。(请注意,此限制仅适用于传递到 JS 库中的 addToLibrary 的对象的键的值,即,顶层键值对是特殊的。函数内部的内部代码当然可以包含任意 JS)。

为了避免 JS 库的这种限制,您可以在使用 --pre-js--post-js 选项的另一个文件中放入代码,这些选项允许任意正常的 JS,并且它将与输出的其余部分一起包含和优化。对于大多数情况,这是推荐的方法。另一个选项是另一个 <script> 标签。

或者,如果您更喜欢使用 JS 库文件,您可以让一个函数替换自身并在初始化期间调用它。

addToLibrary({

  // Solution for bind or referencing other functions directly
  good_02__postset: '_good_02();',
  good_02: function() {
    _good_02 = document.querySelector.bind(document);
  },

  // Solution for closures
  good_03__postset: '_good_03();',
  good_03: function() {
    var callCount = 0;
    _good_03 = function() {
      console.log("times called: ", ++callCount);
    };
  },

  // Solution for curry/transform
  good_05__postset: '_good_05();',
  good_05: function() {
    _good_05 = curry(scrollTo, 0);
 },

});

一个 __postset 是编译器将直接输出到输出文件的字符串。对于上面的示例,将发出此代码。

 function _good_02() {
   _good_o2 = document.querySelector.bind(document);
 }

 function _good_03() {
   var callCount = 0;
   _good_03 = function() {
     console.log("times called: ", ++callCount);
   };
 }

 function _good_05() {
   _good_05 = curry(scrollTo, 0);
};

// Call each function once so it will replace itself
_good_02();
_good_03();
_good_05();

您也可以将大部分代码放入 xxx__postset 字符串中。下面的示例,每个方法都声明了对 $method_support 的依赖,否则都是虚拟函数。 $method_support 本身具有一个相应的 __postset 属性,其中包含所有代码,以将各种方法设置为我们真正想要的函数。

addToLibrary({
  $method_support: {},
  $method_support__postset: [
    '(function() {                                  ',
    '  var SomeLib = function() {                   ',
    '    this.callCount = 0;                        ',
    '  };                                           ',
    '                                               ',
    '  SomeLib.prototype.getCallCount = function() {',
    '    return this.callCount;                     ',
    '  };                                           ',
    '                                               ',
    '  SomeLib.prototype.process = function() {     ',
    '    ++this.callCount;                          ',
    '  };                                           ',
    '                                               ',
    '  SomeLib.prototype.reset = function() {       ',
    '    this.callCount = 0;                        ',
    '  };                                           ',
    '                                               ',
    '  var inst = new SomeLib();                    ',
    '  _method_01 = inst.getCallCount.bind(inst);   ',
    '  _method_02 = inst.process.bind(inst);        ',
    '  _method_03 = inst.reset.bind(inst);          ',
    '}());                                          ',
  ].join('\n'),
  method_01: function() {},
  method_01__deps: ['$method_support'],
  method_02: function() {},
  method_01__deps: ['$method_support'],
  method_03: function() {},
  method_01__deps: ['$method_support'],
 });

注意:如果您使用的是 node 4.1 或更新版本,您可以使用多行字符串。它们仅在编译时使用,而不是在运行时使用,因此输出仍然可以在基于 ES5 的环境中运行。

另一个选项是将大部分代码放在一个对象中,而不是一个函数中,

addToLibrary({
  $method_support__postset: 'method_support();',
  $method_support: function() {
    var SomeLib = function() {
      this.callCount = 0;
    };

    SomeLib.prototype.getCallCount = function() {
      return this.callCount;
    };

    SomeLib.prototype.process = function() {
      ++this.callCount;
    };

    SomeLib.prototype.reset = function() {
      this.callCount = 0;
    };

    var inst = new SomeLib();
    _method_01 = inst.getCallCount.bind(inst);
    _method_02 = inst.process.bind(inst);
    _method_03 = inst.reset.bind(inst);
  },
  method_01: function() {},
  method_01__deps: ['$method_support'],
  method_02: function() {},
  method_01__deps: ['$method_support'],
  method_03: function() {},
  method_01__deps: ['$method_support'],
 });

有关其他示例,请参阅 library_*.js 文件。

注意

  • JavaScript 库可以声明依赖关系 (__deps),但是它们仅用于其他 JavaScript 库。请参阅 /src 中名称格式为 library_*.js 的示例

  • 您可以使用 autoAddDeps(myLibrary, name) 为所有方法添加依赖项,其中 myLibrary 是包含所有方法的对象,而 name 是它们都依赖的项。当所有实现的方法都使用包含帮助器方法的 JavaScript 单例时,这很有用。请参阅 library_webgl.js 以获取示例。

  • 传递给 addToLibrary 的键生成以 _ 为前缀的函数。换句话说, my_func: function() {}, 变成 function _my_func() {},因为 emscripten 中的所有 C 方法都有一个 _ 前缀。以 $ 开头的键将剥离 $ 并且不会添加下划线。

从 C 调用 JavaScript 函数作为函数指针

您可以使用 addFunction 返回一个表示函数指针的整数值。然后将该整数传递给 C 代码,它可以将该值作为函数指针调用,并且您发送给 addFunction 的 JavaScript 函数将被调用。

有关示例,请参阅 test/test_core.py 中的 test_add_function

您应该使用 -sALLOW_TABLE_GROWTH 构建,以允许将新函数添加到表中。否则,默认情况下,表具有固定大小。

当使用 addFunction 与 JavaScript 函数一起使用时,您需要提供一个额外的第二个参数,一个 Wasm 函数签名字符串,如下所述。有关示例,请参阅 test/interop/test_add_function_post.js

函数签名

LLVM Wasm 后端在使用 addFunction 以及在 JavaScript 库中时需要 Wasm 函数签名字符串。签名字符串中的每个字符都代表一个类型。第一个字符代表函数的返回类型,其余字符代表参数类型。

  • 'v': void 类型

  • 'i': 32 位整数类型

  • 'j': 64 位整数类型(见下文注释)

  • 'f': 32 位浮点类型

  • 'd': 64 位浮点类型

  • 'p': 32 位或 64 位指针 (MEMORY64)

例如,如果您添加一个接受一个整数并且不返回任何内容的函数,则签名为 'vi'

当使用 'j' 时,参数值将以多种方式传递给 JavaScript。默认情况下,该值将作为单个 BigInt 或一对 JavaScript 数字 (double) 传递,具体取决于是否启用了 WASM_BIGINT 设置。此外,如果您只需要 53 位精度,您可以添加 __i53abi 装饰器,它将忽略高位,并且该值将作为单个 JavaScript 数字 (double) 接收。它不能与 addFunction 一起使用。以下是一个库函数的示例,该函数使用作为 53 位 (double) 传递的 64 位值设置文件大小,并返回一个整型错误代码

extern "C" int _set_file_size(int handle, uint64_t size);
_set_file_size__i53abi: true,  // Handle 64-bit
_set_file_size__sig: 'iij',    // Function signature
_set_file_size: function(handle, size) { ... return error; }

在链接时使用 -sWASM_BIGINT 是处理库中 64 位类型的另一种方法。 JavaScript 端可能需要 `Number()` 来将其转换为可用的值。请参阅 设置参考

从 JavaScript 访问内存

您可以使用 getValue(ptr, type)setValue(ptr, value, type) 访问内存。第一个参数是一个指针(表示内存地址的数字)。 type 必须是 LLVM IR 类型,分别是 i8i16i32i64floatdouble 或指针类型,如 i8*(或只是 *)。

在测试中有一些这些函数的使用示例 - 请参阅 test/core/test_utf.intest/test_core.py

注意

这是一个比 ccall()cwrap() 更低级的操作 - 我们确实需要关心使用什么特定的类型(例如整数)。

您还可以通过操作表示内存的数组来“直接”访问内存。除非您确信自己知道自己在做什么,并且需要比 getValue()setValue() 更快的速度,否则不建议这样做。

在需要此操作的情况下,如果您想从 JavaScript 导入大量数据以供编译代码处理。例如,以下代码分配一个缓冲区,将一些数据复制到其中,调用一个 C 函数来处理数据,最后释放缓冲区。

var buf = Module._malloc(myTypedArray.length*myTypedArray.BYTES_PER_ELEMENT);
Module.HEAPU8.set(myTypedArray, buf);
Module.ccall('my_function', 'number', ['number'], [buf]);
Module._free(buf);

这里 my_function 是一个 C 函数,它接收一个整数参数(或指针,它们对我们来说都是 32 位整数)并返回一个整数。这可能类似于 int my_function(char *buf)

当使用 -sALLOW_MEMORY_GROWTH 编译时,将分配的内存导出到 JavaScript 的反向操作会很棘手,因为基于 Wasm 的内存允许 **增长**。增大内存大小会更改为新的缓冲区,并且现有的数组视图本质上会失效,因此您不能简单地执行此操作

function func() {
  let someView = HEAPU8.subarray(x, y);
  compute(someView);

  // This may grow memory, which would invalidate all views.
  maybeGrow();

  // If we grew, this use of an invalidated view will fail. Failure in this
  // case will return undefined, the same as reading out of bounds from a
  // typed array. If the operation were someView.subarray(), however, then it
  // would throw an error.
  return someView[z];
}

Emscripten 会为您刷新规范视图,例如 HEAPU8,您可以使用这些视图来刷新您自己的视图。

function func() {
  let someView = HEAPU8.subarray(x, y);
  compute(someView);

  // This may grow memory, which would invalidate all views.
  maybeGrow();

  // Create a new, fresh view after the possible growth.
  someView = HEAPU8.subarray(x, y);
  return someView[z];
}

另一个避免此类问题的选择是在有意义的情况下复制数据。

影响执行行为

Module 是一个全局 JavaScript 对象,它具有 Emscripten 生成的代码在其执行过程中的各个点调用的属性。

开发人员提供 Module 的实现来控制如何显示来自 Emscripten 的通知,在运行主循环之前加载哪些文件,以及许多其他行为。有关更多信息,请参见 Module 对象

环境变量

有时编译后的代码需要访问环境变量(例如,在 C 中,通过调用 getenv() 函数)。Emscripten 生成的 JavaScript 无法直接访问计算机的环境变量,因此提供了一个“虚拟化”环境。

JavaScript 对象 ENV 包含虚拟化的环境变量,通过修改它,您可以将变量传递给编译后的代码。必须注意确保在修改 ENV 变量之前,它已由 Emscripten 初始化——使用 Module.preRun 是一种方便的方法。

例如,要将环境变量 MY_FILE_ROOT 设置为 "/usr/lib/test/",您可以在 Module 设置代码 中添加以下 JavaScript 代码

Module.preRun = () => {ENV.MY_FILE_ROOT = "/usr/lib/test"};

请注意,如果尚未设置自己的值,Emscripten 会在您配置 ENV 后为某些环境变量(例如 LANG)设置默认值。如果您希望此类变量保持未设置状态,可以将其值显式设置为 undefined。例如

Module.preRun = () => {ENV.LANG = undefined};

绑定 C++ 和 JavaScript — WebIDL 绑定器和 Embind

调用编译后的 C 函数的 JavaScript 方法效率很高,但不能与名称修饰的 C++ 函数一起使用。

WebIDL 绑定器Embind 在 C++ 和 JavaScript 之间创建绑定,允许以自然的方式从 JavaScript 中使用 C++ 代码实体。Embind 还支持从 C++ 调用 JavaScript 代码。

Embind 可以绑定几乎任何 C++ 代码,包括复杂的 C++ 结构(例如 shared_ptrunique_ptr)。WebIDL 绑定器 支持可以用 WebIDL 表示的 C++ 类型。虽然此子集比 Embind 支持的子集小,但它足以满足大多数用例——使用绑定器移植的项目的示例包括 Box2DBullet 物理引擎。

两种工具都允许以类似的方式从 JavaScript 中使用映射的项目。但是,它们在不同的级别上运行,并且使用非常不同的方法来定义绑定。

  • Embind 在 C/C++ 文件中声明绑定。

  • WebIDL 绑定器 在单独的文件中声明绑定。它通过绑定器工具运行以创建“粘合”代码,然后与项目一起编译。

注意

没有强有力的证据表明一种工具在性能方面比另一种工具“更好”(没有比较基准测试),并且这两种工具都已成功地在多个项目中使用。选择一种工具而不是另一种工具通常取决于哪种工具最适合项目及其构建系统。

绑定 C/C++ 和 JavaScript - Node-API

Emnapi 是一个非官方的 Node-API 实现,可在 Emscripten 上使用。如果您想将现有的 Node-API 附加程序移植到 WebAssembly 或将相同的绑定代码编译到 Node.js 本机附加程序和 WebAssembly,您可以尝试一下。有关更多详细信息,请参见 Emnapi 文档