C++ 异常支持

默认情况下,Emscripten 中禁用了异常捕获。例如,如果编译以下程序

#include <stdio.h>

int main() {
  try {
    puts("throw...");
    throw 1;
    puts("(never reached)");
  } catch(...) {
    puts("catch!");
  }
  return 0;
}

第一个 throw 将中止程序,你将在输出中看到类似以下内容

throw...
Aborted(Assertion failed: Exception thrown, but exception catching is not enabled. Compile with -sNO_DISABLE_EXCEPTION_CATCHING or -sEXCEPTION_CATCHING_ALLOWED=[..] to catch.)

如果你想选择加入,你还有以下两种选择。

Emscripten(基于 JavaScript)异常支持

首先,你可以通过 Emscripten 的基于 JavaScript 的支持启用异常。要启用它,在编译时和链接时都传递 -fexceptions

使用此标志重新构建上面的示例后,输出将变为

throw...
catch!

请注意,此选项的开销相对较高,但它将在所有支持 WebAssembly 的 JavaScript 引擎上运行。你可以通过指定允许异常启用的函数列表来减少开销,请参阅 EXCEPTION_CATCHING_ALLOWED 设置。

基于 WebAssembly 异常处理的支持

或者,你可以选择加入 原生 WebAssembly 异常处理 提案。要启用它,在编译时和链接时都传递 -fwasm-exceptions

使用此标志重新构建示例将产生与上面使用 -fexceptions 相同的输出

throw...
catch!

此选项利用了一项新功能,该功能为 WebAssembly 带来了用于抛出和捕获异常的内置指令。因此,与基于 JavaScript 的实现相比,它可以减少代码大小和性能开销。此选项目前在几个主要的 Web 浏览器中受支持,但 可能尚未在所有 WebAssembly 引擎中受支持.

调试异常

堆栈跟踪

对于 原生 Wasm 异常,当 断言 启用时,未捕获的异常将打印堆栈跟踪以进行调试。 断言 默认情况下在 -O0 中启用,并在优化构建中禁用(-O1 及更高版本)。你也可以在优化构建中通过将 -sASSERTIONS 传递给 emcc 命令行来启用它。要在堆栈跟踪中显示 Wasm 函数名称,你还需要 –profiling-funcs(或 -g-gsource-map)。

在 JavaScript 中,你还可以使用 WebAssembly.Exception.prototype.stack 属性检查堆栈跟踪。例如

try {
  ... // some code that calls WebAssembly
} catch (e) {
  // Do something with e.stack
  console.log(e.stack);
}

基于 JavaScript 的异常 中,不支持 Wasm 代码内的堆栈跟踪。

从 JavaScript 处理 C++ 异常

你也可以从 JavaScript 捕获和检查 C++ 异常的类型和消息,以防它们继承自 std::exception 并且因此具有 what 方法。

getExceptionMessage 返回一个包含两个字符串的列表:[type, message]message 是在异常是 std::exception 的子类时调用 what 方法的结果。否则,它将只是一个空字符串。

var sp = stackSave();
try {
  ... // some code that calls WebAssembly
} catch (e) {
  stackRestore(sp);
  console.log(getExceptionMessage(e).toString());
} finally {
  ...
}

如果抛出的值为整数 3,则这将打印 int,,因为消息部分为空。如果抛出的值为 MyException 的实例,它是 std::exception 的子类,并且它的 what 消息是 My exception thrown,则此代码将打印 MyException,My exception thrown

要使用此函数,你需要将 -sEXPORT_EXCEPTION_HANDLING_HELPERS 传递给选项。你需要启用 Emscripten EH 或 Wasm EH 中的任何一个才能使用此选项。

如果堆栈指针在抛出异常之前由于 Wasm 函数内的堆栈分配而移动,则可以使用 stackSave()stackRestore() 来恢复堆栈指针,以便不会泄漏堆栈内存。

注意

如果你捕获了一个 Wasm 异常,并且没有重新抛出它,你需要使用 decrementExceptionRefcount 方法在 JS 中释放与异常关联的存储,因为 Wasm 中的异常捕获代码没有机会释放它。但目前由于 Wasm EH 和 Emscripten(基于 JS)EH 的实现问题,你需要在 Emscripten EH 的情况下额外调用 incrementExceptionRefcount。有关详细信息和代码示例,请参阅 https://github.com/emscripten-core/emscripten/issues/17115

将异常和 setjmp-longjmp 一起使用

参阅 将异常和 setjmp-longjmp 一起使用.