2010-07-26 34 views
9

Tôi có ứng dụng vài nghìn dòng dựa trên SIGFPE (được xử lý bởi con trỏ hàm được truyền tới signal()) để thay đổi trạng thái và có mã chạy đúng khi các điều kiện điểm động nhất định xảy ra. Tuy nhiên, dưới C++/CLI trong chế độ quản lý, _control87 tạo ra một thực thi System.ArithmeticException trong một lib tĩnh viết bằng C. _fpreset và _control87 không được hỗ trợ.C++/CLI: SIGFPE, _control87, _fpreset, chuyển ứng dụng Watcom C không được quản lý cũ sang .NET

Làm cách nào để có được thao tác SIGFPE cổ điển, không được quản lý để hoạt động trong ứng dụng C++/CLI? Số lượng các địa điểm mà công cụ điểm nổi xảy ra trong ứng dụng của tôi có thể là bao la và tôi không hoàn toàn hiểu tất cả các phương pháp số được viết cách đây nhiều năm bởi các lập trình viên khác.

Tôi muốn xử lý ngoại lệ trường học cũ để làm việc trên một phân chia dấu chấm động bằng không, không phải là giá trị INF. Kiểu gọi nền tảng không hoạt động và #pragma được quản lý (tắt) cũng không thực hiện được thủ thuật.

Tôi có các tùy chọn nào?

+0

Tính năng này có hoạt động khi bạn biên dịch không có/clr không? Bạn có thể phân vùng ứng dụng thành phần/clr cho mã được quản lý khác của bạn để gọi (hoặc để bạn gọi các công cụ được quản lý từ) và phần gốc với SIGPFE không? Hay chúng quá vướng víu? –

+0

Tôi có một tình huống tương tự với C# interop thông qua tinh khiết C interace. Tôi không có mã C++/CLI, tất cả mã C++ của tôi không được quản lý. Tôi đăng ký gọi lại của tôi cho SIGFPE (cố gắng để có được callstack cho mã không được quản lý), nhưng thời gian chạy .NET luôn ghi đè và ném ArithmeticException thay vì gọi hàm tín hiệu của tôi. – zahir

Trả lời

4

Có một số điểm đau rất nghiêm trọng ở đây. Việc bật ngoại lệ dấu phẩy động là không tương thích với việc thực thi mã được quản lý. Về cơ bản, bạn có thể dễ dàng phá vỡ trình biên dịch JIT. Đó là vấn đề bạn đang chiến đấu khi bạn sử dụng _control87().

Và có, bạn sẽ nhận được một ngoại lệ CLR, nó đặt một ngoại lệ backstop tại chỗ bất cứ khi nào nó thực thi mã gốc. Một trình xử lý tín hiệu chỉ được gọi khi một ngoại lệ được nâng lên và không có mã để xử lý nó. Chắc chắn CLR thấy ngoại lệ trước khi thư viện thời gian chạy C có thể nhìn thấy nó. Vì vậy, bạn sẽ không bao giờ nhận được cuộc gọi xử lý SIGFPE.

Cách duy nhất để chụp ảnh ở đây là viết trình bao bọc bắt ngoại lệ trước khi CLR có thể. Ngoài ra, rất quan trọng là bạn quản lý cẩn thận từ kiểm soát FPU, bạn chỉ có thể đủ khả năng có các ngoại lệ FPU được kích hoạt trong khi mã gốc đang chạy. Điều này có một loạt các mã gritty, cảnh báo trước rằng bạn sẽ không tận hưởng nó rất nhiều.

Bạn đã không gửi bất kỳ đoạn vì vậy tôi sẽ phải tạo nên một ví dụ ngớ ngẩn:

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

Để có được fpehandler() được gọi, bạn cần phải gọi xử lý ngoại lệ trong thời gian chạy C thư viện. May mắn là nó được tiếp xúc và bạn có thể liên kết nó, bạn chỉ cần một tuyên bố cho nó để bạn có thể gọi nó là:

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

Bạn cần đảm bảo rằng nó chỉ được gọi là ngoại lệ dấu chấm động. Vì vậy, chúng ta cần một wrapper mà quan tâm đến mã ngoại lệ:

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

Bây giờ bạn có thể viết một wrapper cho badmath() mà được xử lý tín hiệu gọi:

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

nào đến lượt nó có thể được gọi bởi một lớp C++/CLI mà bạn có thể gọi từ bất kỳ mã được quản lý nào. Nó cần phải đảm bảo rằng trường hợp ngoại lệ điểm nổi được kích hoạt trước khi cuộc gọi và phục hồi lại sau khi cuộc gọi:

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

Lưu ý các cuộc gọi đến _control87(), nó cho phép tất cả các trường hợp ngoại lệ nổi ngoại trừ "kết quả không chính xác". Điều này là cần thiết để cho phép mã được jitted. Nếu bạn không che dấu nó thì CLR chết một cái chết khủng khiếp, bỏ đi nhiều lần ngoại lệ cho đến khi tên của trang web này chấm dứt nó. Hy vọng rằng trình xử lý tín hiệu của bạn không cần nó.

Các vấn đề liên quan