Floating point C++/CLI:SIGFPE、_control87、_fpreset,将古代非托管Watcom C应用程序移植到.NET

Floating point C++/CLI:SIGFPE、_control87、_fpreset,将古代非托管Watcom C应用程序移植到.NET,floating-point,c++-cli,dllimport,sigfpe,Floating Point,C++ Cli,Dllimport,Sigfpe,我有一个几千行的应用程序,它依赖于SIGFPE(由传递给signal()的函数指针处理)来更改状态,并在某些浮点条件发生时使代码正确运行。但是,在托管模式下的C++/CLI下,_control87会生成一个System.Artihmetic异常,该异常在用C编写的静态库中执行。_fpreset和_control87不受支持 如何使经典的非托管SIGFPE操作在C++/CLI应用程序中工作?在我的应用程序中,浮点运算发生的位置可能非常多,我并不完全理解其他程序员多年前编写的所有数值方法 我希望老派

我有一个几千行的应用程序,它依赖于SIGFPE(由传递给signal()的函数指针处理)来更改状态,并在某些浮点条件发生时使代码正确运行。但是,在托管模式下的C++/CLI下,_control87会生成一个System.Artihmetic异常,该异常在用C编写的静态库中执行。_fpreset和_control87不受支持

如何使经典的非托管SIGFPE操作在C++/CLI应用程序中工作?在我的应用程序中,浮点运算发生的位置可能非常多,我并不完全理解其他程序员多年前编写的所有数值方法

我希望老派的异常处理能够处理浮点除零,而不是INF值。平台调用样式不起作用,#pragma managed(off)也不起作用


我有什么选择?

这里有几个非常严重的痛点。启用浮点异常与托管代码执行极不兼容。基本上,您可以轻松地使JIT编译器崩溃。这就是您在使用_control87()时遇到的问题

是的,您将得到一个CLR异常,它在执行本机代码时会放置一个异常backstop。只有在引发异常并且没有代码来处理它时,才会调用信号处理程序。不可避免地,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)
您需要确保只针对浮点异常调用它。因此,我们需要一个关注异常代码的包装器:

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()的调用,它启用除“不精确结果”之外的所有浮动异常。这是允许对代码进行JIT所必需的。如果你不屏蔽它,CLR就会死掉,一次又一次地抛出异常,直到这个站点的名字结束它。希望您的信号处理程序不需要它。

在没有/clr的情况下编译时,它能工作吗?您是否可以将应用程序划分为/clr部分,以供其他托管代码调用(或供您从中调用托管内容)和带有SIGPFE的本机部分?还是它们太纠结了?我有一个类似的情况,就是通过纯C交互实现C#interop。我没有C++ + CLI代码,所有的C++代码都是非托管的。我为SIGFPE注册回调(尝试获取非托管代码的调用堆栈),但.NET运行时总是重写并抛出算术异常,而不是调用我的信号函数。
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);
    }
}