我正在尝试使用 WebSssembly 让一些 C++ 函数在浏览器上运行。我正在关注 tutorial .我想知道:
emcc
生成的 WebAssembly 模块以某种方式避免内存泄漏? 添加异常捕获功能 (
DISABLE_EXCEPTION_CATCHING=0
) 似乎会过多地增加文件大小。任何帮助将不胜感激。
示例 C++ 代码如下:
// C++ source code (fib.cc)
#include <stdexcept>
#include <emscripten.h>
extern "C" {
EMSCRIPTEN_KEEPALIVE
int fib(int n) {
if (n > 12) {
throw std::out_of_range("input out of range");
}
int i, t, a = 0, b = 1;
for (i = 0; i < n; i++) {
t = a + b;
a = b;
b = t;
}
return b;
}
// >>
// other functions with allocations/deallocations
} // end of extern C
它是使用以下命令构建的:
emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' fib.cc
用网页测试:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WASM Test Page</title>
</head>
<body>
<script src="a.out.js"></script>
<script>
"use strict";
Module.onRuntimeInitialized = _ => {
const fib = Module.cwrap('fib', 'number', ['number']);
console.log(fib(10));
console.log(fib(14)); // causes exception
};
</script>
</body>
</html>
最佳答案
How to detect (at JS side) an 'uncaught exception' coming from C++ code?
您必须使用
try-catch
捕获异常 block (tutorial)。例子:
try {
console.log( fib(14) );
}
catch ( e ) {
console.error( e );
}
但是,到目前为止,它作为指针传播,因此您将在控制台中看到一些数字:
5249672
如果你想在 JS 中得到正确的错误信息,那么你必须写一个 binding在您的 C++ 代码中:
#include <emscripten/bind.h>
std::string getExceptionMessage(int eptr)
{
return reinterpret_cast<std::exception*>(eptr)->what();
}
EMSCRIPTEN_BINDINGS(getExceptionMessageBinding)
{
emscripten::function("getExceptionMessage", &getExceptionMessage);
};
这将通过
Module
在 JS 代码中公开。目的。您可以像这样在 JS 代码中使用它:try {
console.log( fib(14) );
}
catch ( e ) {
console.error( Module.getExceptionMessage(e) );
}
输出(抛出异常):
input out of range
这是 GitHub issue讨论过的地方和suggested .
我编译了这段代码,使用 C++11 和 bindings 启用了异常。像这样:
~/emsdk/upstream/emscripten$ ./em++ -std=c++11 -Os -fexceptions --bind
-s WASM=1
-s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]'
-s DISABLE_EXCEPTION_CATCHING=0
fib.cc
这是另一个类似的 GitHub issue讨论另一种方法。
Adding exception catching functionality (
DISABLE_EXCEPTION_CATCHING=0
) seems to increase the file size too much.
如果您担心输出文件的大小增加,您可以完全禁用异常处理并使用无效值或函数返回的错误代码进行错误检查,例如-1 如果输入无效。
但是,这里有一个观察:
先前版本的文件大小为:
110K - a.out.js
187K - a.out.wasm
绑定(bind)的异常处理和 RTTI 是其中的一部分。
我剥离了代码并使用 EM_ASM 使用内联 JS在以下代码段中引发 JS 错误:
#include <emscripten.h>
extern "C" {
EMSCRIPTEN_KEEPALIVE
int fib(int n) {
if (n > 12) {
EM_ASM(
throw Error("out_of_range"); // JS error with EM_ASM
);
}
int a {0}, b {1};
for ( int i {0}; i < n; ++i ) {
const auto t = a + b;
a = b;
b = t;
}
return b;
}
}
编译时禁用异常:
$ ./em++ -std=c++11 -Os
-fno-exceptions
-s WASM=1
-s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]'
fib1.cc
这是 HTML 文件(
fib1.html
):<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WASM Test Page</title>
</head>
<body>
<script src="a.out.js"></script>
<script>
"use strict";
Module.onRuntimeInitialized = _ => {
const fib = Module.cwrap('fib', 'number', ['number']);
try {
console.log(fib(10));
console.log(fib(14));
}
catch ( e ) {
console.error(e);
}
};
</script>
</body>
</html>
控制台输出(捕获异常):
89
fib1.html:21 Error: out_of_range
at Array.ASM_CONSTS (a.out.js:1)
at _emscripten_asm_const_i (a.out.js:1)
at wasm-function[1]:0x6b
at Module._fib (a.out.js:1)
at Object.Module.onRuntimeInitialized (fib1.html:18)
at doRun (a.out.js:1)
at run (a.out.js:1)
at runCaller (a.out.js:1)
at removeRunDependency (a.out.js:1)
at receiveInstance (a.out.js:1)
而且,文件大小为:
15K - a.out.js
246 - a.out.wasm (bytes)
抛出 JS 错误仍然可以在禁用异常的情况下工作,并且生成的文件大小要小得多。您可能想对此进行更多探索。也许,创建一些继承自 Error 并具有扩展功能的类。但是,从标准 API 引发的异常,例如
std::vector::at()
将不起作用并导致终止。因此,您需要在禁用异常时考虑这些问题。How to reset/restart WebAssembly Module generated by
emcc
in a way that avoids memory leaks?
到目前为止,还没有这样的 API 来重置/重启模块。模块本身在不再被引用时会自动进行垃圾回收。在这种情况下,您不必关心内存泄漏。 JS 运行时对此负责。
但是,由 JS 代码创建的 C++ 对象应该使用
Module.destroy
销毁。如果它正在管理资源(内存、文件句柄等)。垃圾收集器 (GC) 在收集会导致内存/资源泄漏的对象时不会调用析构函数。调用Module.destory
将调用析构函数并且不会有任何内存泄漏。现在,您的问题没有这样的对象。所以,当你打电话时要留心 Module.destory
在需要的时候。至于 C++ 代码中的分配/解除分配,您自己负责分配的资源的解除分配。在这方面,以下几点可能对您有所帮助:
std::unique_ptr
/ std::shared_ptr
连同 std::make_unique
/ std::make_shared
. std::vector
, std::map
等用于存储和管理集合,而不是求助于编写自己的集合。标准的东西经过充分测试,因此对错误的担忧更少。 这是加载 WASM 模块的线程:Loading WebAssembly modules efficiently
关于javascript - 如何检测故障并重置/重启 webassembly 模块?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61015985/