C++/CLI: SIGFPE, _control87, _fpreset, porting ancient unmanaged Watcom C app to .NET
Asked Answered
C

1

9

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?

Claypan answered 26/7, 2010 at 20:1 Comment(2)
Does it work when you compile without /clr? Could you partition the app into the /clr part for your other managed code to call (or for you to call managed stuff from) and the native part with the SIGPFE? Or are they too entangled?Psychodrama
I have a similar situation with C# interop via pure C interace. I have no C++/CLI code, all my C++ code is unmanaged. I register my callback for SIGFPE (trying to acquire callstack for unmanaged code), but .NET runtime always overrides and throws ArithmeticException instead of calling my signal function.Bousquet
H
7

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.

Homorganic answered 13/6, 2015 at 14:24 Comment(1)
I know this is an old post, but is there any way to translate that into C#. We have some external drivers we work with that are internally changing the floating point precision and then it causes our .Net application to crash in random places (such as WPF controls) that use floating point numbers. Our current solution is to just proactively guess when / where we might need to reset the floating point and do so (even if it doesn't need it). It would be nice to also have a reactive solution in place.Audiogenic

© 2022 - 2024 — McMap. All rights reserved.