2010-07-26 2 views
9

J'ai une application de plusieurs-mille-lignes qui repose sur SIGFPE (géré par un pointeur de fonction passé à signal()) pour changer d'état et que le code s'exécute correctement lorsque certaines conditions de virgule flottante se produisent. Toutefois, sous C++/CLI en mode géré, _control87 génère une exception System.ArithmeticException s'exécutant dans une bibliothèque statique écrite en C. _fpreset et _control87 ne sont pas prises en charge. Comment puis-je faire fonctionner une opération SIGFPE classique, non gérée, dans une application C++/CLI? Le nombre d'emplacements où des trucs en virgule flottante se produisent dans mon application peut être immense et je ne comprends pas entièrement toutes les méthodes numériques écrites il y a des années par d'autres programmeurs.C++/CLI: SIGFPE, _control87, _fpreset, portage ancienne app Watcom C non géré à .NET

Je souhaite que la gestion des exceptions à l'ancienne fonctionne sur une division à virgule flottante par zéro et non sur une valeur INF. Le style d'invocation de la plate-forme ne fonctionne pas, et #pragma managed (off) ne fait pas l'affaire non plus.

Quelles options ai-je?

+0

Cela fonctionne-t-il lorsque vous compilez sans/clr? Pourriez-vous partitionner l'application dans la partie/clr pour que votre autre code managé appelle (ou pour que vous appeliez des éléments gérés) et la partie native avec le SIGPFE? Ou sont-ils trop enchevêtrés? –

+0

J'ai une situation similaire avec C# interop via C interace pur. Je n'ai pas de code C++/CLI, tout mon code C++ est non géré. J'enregistre mon rappel pour SIGFPE (en essayant d'acquérir callstack pour le code non managé), mais .NET runtime remplace toujours et lance ArithmeticException au lieu d'appeler ma fonction de signal. – zahir

Répondre

4

Il existe plusieurs points de douleur très sérieux ici. L'activation des exceptions à virgule flottante est grossièrement incompatible avec l'exécution de code managé. Jusqu'à la base, vous pouvez facilement planter le compilateur JIT. Quel est le problème que vous rencontrez lorsque vous utilisez _control87().

Et oui, vous obtiendrez une exception CLR, il met en place une exception backstop chaque fois qu'il exécute du code natif. Un gestionnaire de signal n'est jamais appelé quand une exception est levée et il n'y a pas de code pour le gérer. Inévitablement, le CLR voit l'exception avant que la bibliothèque d'exécution C puisse la voir. Vous n'obtiendrez donc jamais l'appel du gestionnaire SIGFPE.

Le seul moyen décent d'avoir une chance est d'écrire une enveloppe qui attrape l'exception avant que le CLR ne le puisse. Il est également très, très important que vous gériez soigneusement le mot de contrôle FPU, vous pouvez seulement permettre que les exceptions FPU soient activées pendant que le code natif est en cours d'exécution. Cela prend un tas de code rude, avertissement préalable que vous ne serez pas en profiter beaucoup.

Vous ne poster aucun extrait donc je vais devoir faire un exemple stupide:

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

Afin d'obtenir fpehandler() appelé, vous devez appeler le gestionnaire d'exceptions dans le runtime C bibliothèque. Heureusement, il est exposé et vous pouvez le relier, il vous suffit d'une déclaration pour elle afin que vous puissiez l'appeler:

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

Vous devez vous assurer qu'il est jamais appelée pour les exceptions à virgule flottante. Nous avons donc besoin d'une enveloppe qui accorde une attention au code d'exception:

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

Maintenant, vous pouvez écrire un wrapper pour badmath() qui obtient le gestionnaire de signal appelé:

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

ce qui peut être appelé par une classe C++/CLI que vous pouvez appeler à partir de n'importe quel code managé. Il doit veiller à ce que les exceptions à virgule flottante sont activées avant l'appel et rétabli à nouveau après l'appel:

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

Notez l'appel à _control87(), il permet à toutes les exceptions flottantes, sauf « résultat inexact ». Ceci est nécessaire pour permettre au code d'être jeté. Si vous ne le masquez pas, le CLR meurt d'une mort horrible, jetant des exceptions encore et encore jusqu'à ce que le nom de ce site y mette fin. J'espère que votre gestionnaire de signal n'en a pas besoin.

Questions connexes