它将 JS 绑定到您的 wasm!
在我上一篇文章中,我提到了 了解如何将 C 库编译为 wasm 库,以便在网页上使用。一件事 让我(和许多读者)感到很不舒服的 您必须手动声明您所使用的 wasm 模块的哪些函数。 让我们来回顾一下,我在前面提到的代码段如下所示:
const api = {
version: Module.cwrap('version', 'number', []),
create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
};
我们在这里声明使用
EMSCRIPTEN_KEEPALIVE
、其返回值类型及其类型
参数。之后,我们可以使用 api
对象上的方法来调用
这些函数。不过,以这种方式使用 wasm 不支持字符串,并且
您需要手动移动内存块
API 使用起来非常繁琐。难道没有更好的办法吗?为什么会有,否则
本文是关于什么内容的?
C++ 名称篡改
虽然开发者体验足以构建一个
实际上有一个更紧迫的原因:在编译 C 语言代码时,
或 C++ 代码,每个文件都是单独编译的。然后,链接器会处理
同时处理所有这些所谓的对象文件,并将其转换为 Wasm
文件。如果使用 C,函数名称仍可在对象文件中使用
供链接器使用调用 C 函数只需提供名称,
我们将其作为字符串提供给 cwrap()
。
另一方面,C++ 支持函数重载,这意味着您可以实现
多次使用同一个函数,只要签名不同(例如
不同类型的参数)。在编译器级别,可以使用一个不错的名称,例如 add
会被破坏为对函数中的签名进行编码
为链接器指定的名称因此,我们无法查找函数,
替换它的名称
进入 embind
embind 是 Emscripten 工具链的一部分,可为您提供大量 C++ 宏 可让您为 C++ 代码添加注释。您可以声明哪些函数、枚举 类或值类型。我们开始吧 只是一些普通函数:
#include <emscripten/bind.h>
using namespace emscripten;
double add(double a, double b) {
return a + b;
}
std::string exclaim(std::string message) {
return message + "!";
}
EMSCRIPTEN_BINDINGS(my_module) {
function("add", &add);
function("exclaim", &exclaim);
}
与我上一篇报道相比,我们不再包含 emscripten.h
,因为
我们不再需要使用 EMSCRIPTEN_KEEPALIVE
为函数添加注解。
而是包含一个 EMSCRIPTEN_BINDINGS
部分,其中列出名称
向 JavaScript 公开函数
要编译此文件,我们可以使用相同的设置(或者,如果您愿意,也可以使用相同的
Docker 映像)上部署的
文章。如需使用 embind,
我们添加 --bind
标志:
$ emcc --bind -O3 add.cpp
现在要做的就是创建 HTML 文件,该文件会加载 已创建 wasm 模块:
<script src="/a.out.js"></script>
<script>
Module.onRuntimeInitialized = _ => {
console.log(Module.add(1, 2.3));
console.log(Module.exclaim("hello world"));
};
</script>
如您所见,我们不再使用 cwrap()
。就是这么简单
。但更重要的是,我们无需担心
内存块来让字符串正常运行!embind 还为您提供了免费的
包含类型检查:
这非常棒,因为我们可以尽早发现一些错误,而无需处理 偶尔相当棘手的 Wasm 错误
对象
许多 JavaScript 构造函数和函数都使用 options 对象。这是一个不错的选择 模式,但在 wasm 中手动实现极其繁琐。Embind 也可以在这里提供帮助!
例如,我想出这个非常有用的 C++ 函数,用于处理我的数据 字符串,我迫切想在网页上使用它。具体操作步骤如下:
#include <emscripten/bind.h>
#include <algorithm>
using namespace emscripten;
struct ProcessMessageOpts {
bool reverse;
bool exclaim;
int repeat;
};
std::string processMessage(std::string message, ProcessMessageOpts opts) {
std::string copy = std::string(message);
if(opts.reverse) {
std::reverse(copy.begin(), copy.end());
}
if(opts.exclaim) {
copy += "!";
}
std::string acc = std::string("");
for(int i = 0; i < opts.repeat; i++) {
acc += copy;
}
return acc;
}
EMSCRIPTEN_BINDINGS(my_module) {
value_object<ProcessMessageOpts>("ProcessMessageOpts")
.field("reverse", &ProcessMessageOpts::reverse)
.field("exclaim", &ProcessMessageOpts::exclaim)
.field("repeat", &ProcessMessageOpts::repeat);
function("processMessage", &processMessage);
}
我要为 processMessage()
函数的选项定义一个结构体。在
EMSCRIPTEN_BINDINGS
代码块,我可以使用 value_object
将 JavaScript 设为
将此 C++值视为对象我还可以使用value_array
将此 C++ 值用作数组。我还绑定了 processMessage()
函数,
其余部分则体现了魔法。现在,我可以从以下代码中调用 processMessage()
函数:
没有任何样板代码的 JavaScript:
console.log(Module.processMessage(
"hello world",
{
reverse: false,
exclaim: true,
repeat: 3
}
)); // Prints "hello world!hello world!hello world!"
类
为了完整起见,我还应该演示一下,通过“embind” 这与 ES6 类有很强的协同作用。您或许能 现在就可以开始看到模式了:
#include <emscripten/bind.h>
#include <algorithm>
using namespace emscripten;
class Counter {
public:
int counter;
Counter(int init) :
counter(init) {
}
void increase() {
counter++;
}
int squareCounter() {
return counter * counter;
}
};
EMSCRIPTEN_BINDINGS(my_module) {
class_<Counter>("Counter")
.constructor<int>()
.function("increase", &Counter::increase)
.function("squareCounter", &Counter::squareCounter)
.property("counter", &Counter::counter);
}
在 JavaScript 端,这就像是原生类:
<script src="/a.out.js"></script>
<script>
Module.onRuntimeInitialized = _ => {
const c = new Module.Counter(22);
console.log(c.counter); // prints 22
c.increase();
console.log(c.counter); // prints 23
console.log(c.squareCounter()); // prints 529
};
</script>
C 语言呢?
embind 是为 C++ 编写的,只能在 C++ 文件中使用,但
意味着您无法链接到 C 文件!要混用 C 和 C++,您只需要
将输入文件分成两组:一组用于 C,另一组用于 C++ 文件,
扩充 emcc
的 CLI 标志,如下所示:
$ emcc --bind -O3 --std=c++11 a_c_file.c another_c_file.c -x c++ your_cpp_file.cpp
总结
embind 可显著提升 使用 wasm 和 C/C++。本文并未涵盖绑定优惠的所有选项。 如果您感兴趣,建议您继续学习 embind 的 文档。 请注意,使用 embind 可能会使您的 wasm 模块和 在进行 gzip 处理时,JavaScript 粘合代码的大小高达 11k,最值得注意的是 模块。如果您只有非常小的 Wasm 表面,则 embind 的费用可能超过 值得在生产环境中使用!但无论如何,您绝对应该提供 。