Блог переехал. Актуальная версия поста находится по адресу: http://aakinshin.net/ru/blog/dotnet/delphi-fpu-issue/.
Ситуация: мы пишем основную логику приложения на C#, но есть необходимость использовать её из Delphi. Для этих целей пользуемся COM-обёрткой, которая успешно справляется с поставленной задачей. Целевая функция перед возвращением результата показывает диалоговое WPF-окно, с которым можно сделать что-нибудь полезное. Проверяем на простом примере — всё отлично работает.
Проблема: в некоторых Delphi приложений окно выбрасывает исключение. Но исключение странное: при формировании WPF-окна падает, скажем, выставление ширины некоторого элемента. Но это только в некоторых приложениях. А в остальных — тот же самый код на тех же самых данных отлично работает.
В чём же дело?
Оставив в стороне увлекательную историю о проведённом исследовании, перейду сразу к решению: виновником был бит в FPU-регистре CW. Определённые функции некоторых версий Delphi любят менять его на такое значение, что бедный математический сопроцессор перестаёт переваривать double.NaN
, начиная плеваться на него исключениями. А в WPF, как известно, у доброй половины свойств FrameworkElement
-а значение по умолчанию выставлено именно в NaN
. При малейших манипуляциях над этими свойствами приложение начинает падать.
Что же делать? Имеется два метода решения проблемы. В первом варианте необходимо выставить правильное значение плохого бита в Delphi перед вызовом WPF-окна. Например так:
procedure Foo; var saved8087CW : Word; begin saved8087CW := Default8087CW; Set8087CW($133F); // Вызываем нужный метод Set8087CW(saved8087CW); end;
Этот способ не всегда подходит, т.к. возможно .NET-логика будет использоваться в различных Delphi-приложениях, и у нас нет возможности обернуть все вызовы WPF-окна во всех приложениях. С этих позиций разумно установить бит на стороне .NET-а. В этом нам поможет функция _controlfp
из msvcrt.dll
:
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int _controlfp(int newControl, int mask); const int _RC_NEAR = 0x00000000; const int _PC_53 = 0x00010000; const int _EM_INVALID = 0x00000010; const int _EM_UNDERFLOW = 0x00000002; const int _EM_ZERODIVIDE = 0x00000008; const int _EM_OVERFLOW = 0x00000004; const int _EM_INEXACT = 0x00000001; const int _EM_DENORMAL = 0x00080000; const int _CW_DEFAULT = _RC_NEAR + _PC_53 + _EM_INVALID + _EM_ZERODIVIDE + _EM_OVERFLOW + _EM_UNDERFLOW + _EM_INEXACT + _EM_DENORMAL; public Foo() { _controlfp(_CW_DEFAULT, 0xfffff); // Нужная нам логика }
Что ещё почитать?
- FPU issues when interoping Delphi and .net
- Floating point exception in managed code results in Access Violation crash
- PRB: System.Arithmetic Exception Error When You Change the Floating-Point Control Register in a Managed Application
- SO — How can I set and restore FPU CTRL registers?
- Floating Point Exception When Calling Borland C++Builder or Delphi DLL or Executable
- Delphi: Set8087CW
- Внутренние регистры: Регистр управления FPU
Комментариев нет:
Отправить комментарий