这里有几个非常严重的痛点。启用浮点异常与托管代码执行完全不兼容。归根结底,您可以轻松地使 JIT 编译器崩溃。当您使用 _control87() 时,您要解决的问题是什么。
是的,你会得到一个 CLR 异常,它会在执行本机代码时放置一个异常支持。只有在引发异常并且没有代码来处理它时才会调用信号处理程序。CLR 不可避免地会在 C 运行时库看到异常之前看到它。所以你永远不会得到 SIGFPE 处理程序调用。
解决这个问题的唯一体面方法是编写一个在 CLR 之前捕获异常的包装器。仔细管理 FPU 控制字也非常非常重要,您只能在本机代码运行时启用 FPU 异常。这需要一堆坚韧不拔的代码,预先警告你不会很喜欢它。
你没有发布任何片段,所以我将不得不编一个愚蠢的例子:
#include <Windows.h>
#include <signal.h>
#include <float.h>
#pragma managed(push, off)
double divisor;
void __cdecl fpehandler(int sig) {
divisor = 1.0;
}
double badmath() {
divisor = 0.0;
return 1 / divisor;
}
#pragma managed(pop)
为了调用 fpehandler(),您需要调用 C 运行时库中的异常处理程序。幸运的是它是公开的并且你可以链接它,你只需要一个声明就可以调用它:
// Exception filter in the CRT, it raises the signal
extern "C" int __cdecl _XcptFilter(unsigned long xcptnum,
PEXCEPTION_POINTERS pxcptinfoptrs);
您需要确保只为浮点异常调用它。所以我们需要一个关注异常代码的包装器:
int FloatingpointExceptionFilter(unsigned long xcptnum, PEXCEPTION_POINTERS pxcptinfoptrs) {
// Only pass floating point exceptions to the CRT
switch (xcptnum) {
case STATUS_FLOAT_DIVIDE_BY_ZERO:
case STATUS_FLOAT_INVALID_OPERATION:
case STATUS_FLOAT_OVERFLOW:
case STATUS_FLOAT_UNDERFLOW:
case STATUS_FLOAT_DENORMAL_OPERAND:
case STATUS_FLOAT_INEXACT_RESULT:
case STATUS_FLOAT_STACK_CHECK:
case STATUS_FLOAT_MULTIPLE_TRAPS:
case STATUS_FLOAT_MULTIPLE_FAULTS:
return _XcptFilter(xcptnum, pxcptinfoptrs);
break;
default:
return EXCEPTION_CONTINUE_SEARCH;
}
}
现在您可以为 badmath() 编写一个包装器,该包装器将调用信号处理程序:
double badmathWrapper() {
__try {
return badmath();
}
__except (FloatingpointExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
}
}
反过来,它可以由您可以从任何托管代码调用的 C++/CLI 类调用。它需要确保在调用前启用浮点异常,并在调用后再次恢复:
using namespace System;
using namespace System::Runtime::CompilerServices;
public ref class Wrapper {
public:
static double example();
};
[MethodImplAttribute(MethodImplOptions::NoInlining)]
double Wrapper::example() {
signal(SIGFPE, fpehandler);
_clear87();
unsigned oldcw = _control87(_EM_INEXACT, _MCW_EM);
try {
return badmathWrapper();
}
finally {
_control87(oldcw, _MCW_EM);
signal(SIGFPE, nullptr);
}
}
请注意对 _control87() 的调用,它启用除“不精确结果”之外的所有浮动异常。这是允许代码被 jitted 所必需的。如果你不屏蔽它,那么 CLR 就会死得很惨,一遍又一遍地抛出异常,直到这个站点的名称结束它。希望您的信号处理程序不需要它。