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

发布于 2024-09-11 15:44:53 字数 372 浏览 13 评论 0原文

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

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

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

我有什么选择?

I have a several-thousand-line application that relies on SIGFPE (handled by a function pointer passed to signal()) to change state and have the code run correctly when certain floating point conditions happen. However, under C++/CLI in managed-mode, _control87 generates a System.ArithmeticException executing in a static lib written in C. _fpreset and _control87 are not supported.

How do I get classic, unmanaged SIGFPE operation to work in a C++/CLI application? The number of locations where floating point stuff happens in my application could be immense and I do not fully understand all of the numerical methods written years ago by other programmers.

I want old-school exception handling to work on a floating point division by zero, not an INF value. Platform invoke style doesn't work, and #pragma managed(off) doesn't do the trick either.

What options do I have?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

删除会话 2024-09-18 15:44:53

这里有几个非常严重的痛点。启用浮点异常与托管代码执行完全不兼容。从根本上讲,您很容易使 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() 的调用,它启用除“不精确结果”之外的所有浮点异常。这是允许代码被抖动所必需的。如果你不屏蔽它,那么 CLR 就会惨死,一遍又一遍地抛出异常,直到这个站点的名字结束它。希望您的信号处理程序不需要它。

There are several very serious pain points here. Enabling floating point exceptions is grossly incompatible with managed code execution. Down to the basics, you can easily crash the JIT-compiler. Which is the problem you are battling when you use _control87().

And yes, you'll get a CLR exception, it puts an exception backstop in place whenever it executes native code. A signal handler is only ever called when an exception is raised and there's no code to handle it. Inevitably the CLR sees the exception before the C runtime library can see it. So you'll never get the SIGFPE handler call.

The only decent way to have a shot at this is write a wrapper that catches the exception before the CLR can. Also very, very important that you carefully manage the FPU control word, you can only afford having FPU exceptions enabled while the native code is running. This takes a bunch of gritty code, up-front warning that you are not going to enjoy it very much.

You didn't post any snippet so I'll have to make up a silly example:

#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)

In order to get fpehandler() called, you need to call the exception handler inside the C runtime library. Luckily it is exposed and you can link it, you only need a declaration for it so you can call it:

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

You need to make sure that it is only ever called for floating point exceptions. So we need a wrapper that pays attention to the exception code:

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;
    }
}

Now you can write a wrapper for badmath() that gets the signal handler called:

double badmathWrapper() {
    __try {
        return badmath();
    }
    __except (FloatingpointExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
    }
}

Which in turn can be called by a C++/CLI class that you can call from any managed code. It needs to ensure that floating point exceptions are enabled before the call and restored again after the call:

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);
    }
}

Note the call to _control87(), it enables all floating exceptions except "inexact result". This is necessary to allow the code to be jitted. If you don't mask it then the CLR dies a horrible death, throwing exceptions over and over again until this site's name puts an end to it. Hopefully your signal handler doesn't need it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文