C#发生算术运算中发生溢出或下溢的解决方案

一、现象描述

    在调用一个C++ Builder写的dll之后,操作另外的一个2D Chart控件后,报如下错误:

Message :算术运算中发生溢出或下溢。, Source:System.Windows.Forms,    在 System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)

   在 System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
   在 System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
   在 System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
   在 System.Windows.Forms.Application.RunDialog(Form form)
   在 System.Windows.Forms.Form.ShowDialog(IWin32Window owner)

   在 System.Windows.Forms.Form.ShowDialog()

二、分析

    从异常现象来看,显然和浮点数的运算有关。因为dll和2D控件本身是没有源码的,无法获知到具体异常原因。因此一个自然而然的想法是,可能是dll中某些函数更改了浮点数的设置后没有复位,导致此异常出现。

    从网上资料看,确实也存在这种现象:

This solution is only for those guys who r working on different culture like Farsi, Urdu, Arabic and many other, When we change the culture in the .net frame work, they call some unmanaged code(dll's) which change the floating point of the compiler and didn't set it back to its original floating point.So we change the calling sequence to set floating point back to its original floating point before transferring the control to managed code. 

     为验证此想法,在调用dll中函数的前后,观察其浮点掩码的状态情况。

    int  status1 = _controlfp(0, 0);
    ......//调用dll的代码
    int status2 = _controlfp(0, 0);

    运行程序,获取到上述两个状态,得到:status1为0x0009001F,stauts2为0x000C0003。因此断定是组件dll更改了系统缺省的浮点掩码设置。

三、解决方案

    在VS中,有一个函数,用于设置浮点异常掩码,即_controlfp。其原型为:

unsigned int _controlfp(   
   unsigned int new,  
   unsigned int mask   
); 
 
  

参数new,新的控制字位值。mask,设置新的控制字位的掩码。 

如果 mask 不为零,控制字的新值被设置:对于在 mask 中打开的位(即等于 1),在 new 中,对应的位用于更新控制字。 

换而言之,fpcntrl = (fpcntrl & ~mask)|(new & mask) 其中 fpcntrl 是浮点控制字。

返回值按位返回的值指示浮点控制态。  

    常用的mask如下:

掩码 十六进制值 常量 十六进制值
_MCW_DN (不正常的控件) 0x03000000 _DN_SAVE

 _DN_FLUSH
0x00000000

0x01000000
_MCW_EM (中断异常掩码) 0x0008001F _EM_INVALID

 _EM_DENORMAL

 _EM_ZERODIVIDE

 _EM_OVERFLOW

 _EM_UNDERFLOW

 _EM_INEXACT
0x00000010

0x00080000

0x00000008

0x00000004

0x00000002

0x00000001
_MCW_IC (无限制控件)

(不支持 ARM 或 x64 平台。)
0x00040000 _IC_AFFINE

 _IC_PROJECTIVE
0x00040000

0x00000000
_MCW_RC (舍入控件) 0x00000300 _RC_CHOP

 _RC_UP

 _RC_DOWN

 _RC_NEAR
0x00000300

0x00000200

0x00000100

0x00000000
_MCW_PC (精度控件)

(不支持 ARM 或 x64 平台。)
0x00030000 _PC_24 (24 位)

 _PC_53 (53 位)

 _PC_64 (64 位)
0x00020000

0x00010000

0x00000000
    因此只需要在执行完毕dll的调用代码后,调用_controlfp来回复默认值即可达到目的。

    C#应用示例:    

        [DllImport("msvcr70.dll", CallingConvention = CallingConvention.Cdecl)]
        unsafe public static extern int _controlfp(int n, int mask);
         internal void ResetFPCR()
        {
            const int _EM_OVERFLOW = 0x0009001F;//为原来的默认值
            const int _MCW_EM = 0x000FFFFF;//设置默认值相关的掩码为1,即可设置成默认值。
            _controlfp(_EM_OVERFLOW, _MCW_EM);
        }
        void Test()
        {              
           ......//调用dll的代码
            ResetFPCR();
         }

你可能感兴趣的:(C#开发)