C ++ / CLI: SIGFPE, _control87, _fpreset, перенос старого неуправляемого приложения Watcom C в .NET - PullRequest
9 голосов
/ 27 июля 2010

У меня есть приложение из нескольких тысяч строк, которое использует SIGFPE (обрабатывается указателем функции, передаваемым в signal ()) для изменения состояния и правильного выполнения кода при возникновении определенных условий с плавающей запятой. Однако в C ++ / CLI в управляемом режиме _control87 генерирует исключение System.ArithmeticException, выполняемое в статической библиотеке, написанной на C. _fpreset и _control87 не поддерживаются.

Как заставить классическую неуправляемую операцию SIGFPE работать в приложении C ++ / CLI? Число мест, где в моем приложении происходят операции с плавающей запятой, может быть огромным, и я не до конца понимаю все численные методы, написанные годами назад другими программистами.

Я хочу, чтобы обработка исключений старой школы работала с делением с плавающей запятой на ноль, а не на значение INF. Стиль вызова платформы не работает, и управляемая (выкл) #pragma тоже не справляется.

Какие у меня есть варианты?

1 Ответ

4 голосов
/ 13 июня 2015

Здесь есть несколько очень серьезных болевых точек.Включение исключений с плавающей точкой крайне несовместимо с выполнением управляемого кода.Вплоть до основ, вы можете легко разбить 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())) {
    }
}

, который вturn может быть вызван классом 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 умирает ужасной смертью, бросая исключения снова и снова, пока имя этого сайта не положит этому конец.Надеюсь, вашему обработчику сигналов это не нужно.

...