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

use*_*400 9 floating-point c++-cli dllimport sigfpe

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

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

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

我有什么选择?

Han*_*ant 7

这里有几个非常严重的痛点。启用浮点异常与托管代码执行严重不兼容。归根结底,您可以轻松地使 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)
Run Code Online (Sandbox Code Playgroud)

为了调用 fpehandler(),您需要调用 C 运行时库中的异常处理程序。幸运的是它是公开的,你可以链接它,你只需要一个声明,这样你就可以调用它:

// Exception filter in the CRT, it raises the signal
extern "C" int __cdecl _XcptFilter(unsigned long xcptnum, 
                                   PEXCEPTION_POINTERS pxcptinfoptrs);
Run Code Online (Sandbox Code Playgroud)

您需要确保只为浮点异常调用它。所以我们需要一个关注异常代码的包装器:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在您可以为 badmath() 编写一个包装器来调用信号处理程序:

double badmathWrapper() {
    __try {
        return badmath();
    }
    __except (FloatingpointExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
    }
}
Run Code Online (Sandbox Code Playgroud)

反过来,它可以被 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意对 _control87() 的调用,它启用除“不精确结果”之外的所有浮动异常。这对于允许代码被 jitt 是必要的。如果你不屏蔽它,那么 CLR 就会死得很惨,一遍又一遍地抛出异常,直到这个站点的名字结束它。希望您的信号处理程序不需要它。