Почему приложение запускается с FPU Control Word, отличным от Default8087CW?

Не могли бы вы помочь мне понять, что происходит с FPU Control Word в моем приложении Delphi на платформе Win32.

Когда мы создаем новое приложение VCL, управляющее слово устанавливается до 1372 часов. Это первое, что я не понимаю, почему это 1372h вместо 1332h, который является Default8087CW, определенным в System.

Разница между этими двумя:

1001101110010  //1372h
1001100110010  //1332h

- это 6-й бит, который согласно документации зарезервирован или не используется.

Второй вопрос касается CreateOleObject.

function CreateOleObject(const ClassName: string): IDispatch;
var
  ClassID: TCLSID;
begin
  try
    ClassID := ProgIDToClassID(ClassName);
{$IFDEF CPUX86}
    try
      Set8087CW( Default8087CW or $08);
{$ENDIF CPUX86}
      OleCheck(CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or
        CLSCTX_LOCAL_SERVER, IDispatch, Result));
{$IFDEF CPUX86}
    finally
      Reset8087CW;
    end;
{$ENDIF CPUX86}
  except
    on E: EOleSysError do
      raise EOleSysError.Create(Format('%s, ProgID: "%s"',[E.Message, ClassName]),E.ErrorCode,0) { Do not localize }
  end;    
end;

Вышеуказанная функция меняет управляющее слово на 137Ah, поэтому он включает 3-й бит (Overflow Mask). Я не понимаю, почему он вызывает Reset8087CW после, вместо восстановления состояния слова, которое было перед входом в функцию?

Ответ 1

6-й бит зарезервирован и проигнорирован. Эти два контрольных слова на самом деле равны в том смысле, что FPU ведет себя одинаково. Система просто устанавливает зарезервированный бит. Даже если вы попытаетесь установить значение $1332, система установит его на $1372. Независимо от того, какое значение вы зададите 6-му биту, оно всегда будет установлено. Поэтому, сравнивая эти значения, вы должны игнорировать этот бит. Здесь не о чем беспокоиться.

Что касается CreateOleObject, авторы решили, что если вы собираетесь использовать эту функцию, тогда вы также будете маскировать переполнение при использовании COM-объекта и даже за его пределами. Кто знает, почему они это сделали, и только для 32-битного кода? Вероятно, они обнаружили кучу COM-объектов, которые обычно переполнялись, и поэтому добавили эту штукатурку. Недостаточно маскировать переполнение при создании, это также необходимо делать при использовании объекта, поэтому разработчики RTL решили разоблачить переполнение впредь.

Или, возможно, это была ошибка. Они решили не исправлять это для 32-битного кода, потому что люди полагались на поведение, но они исправили для 64-битного кода.

В любом случае эта функция не делает ничего особенного. Вам не нужно использовать его. Вы можете написать свой собственный, который делает то, что вы хотите.

Управление плавающей точкой является проблемой при работе с interop. Код Delphi ожидает исключения маскировки. Код, созданный другими инструментами, как правило, маскирует их. В идеале вы должны маскировать исключения, когда вы вызываете из своего кода Delphi и вскрываете их при возврате. Ожидайте, что другие библиотеки будут произвольно менять управляющее слово. Также имейте в виду, что Set8087CW не является потокобезопасным, что является серьезной проблемой, с которой Embarcadero отказывался обращаться в течение многих лет.

Нет простого пути. Если вы не используете плавающие точки в своей программе, вы можете просто маскировать исключения и, вероятно, быть в порядке. В противном случае вам нужно убедиться, что управляющее слово установлено надлежащим образом во всех точках всех потоков. В общем, это почти невозможно, используя стандартный Delphi RTL. Я лично справляюсь с этим, заменяя ключевые части RTL потоковыми версиями. Я зарегистрировал, как это сделать в этом отчете QC: QС# 107411.

Ответ 2

Отказ от ответственности: я отлаживал вопросы в Delphi XE.

Во-первых, второй вопрос.

Если вы посмотрите на код Set8087CW, вы увидите, что он сохраняет новое значение CW FPU в переменной Default8087CW, а Reset8087CW восстанавливает FPU CW от Default8087CW; поэтому вызов Reset8087CW после Set8087CW ничего не делает, что демонстрируется

Memo1.Lines.Clear;
Memo1.Lines.Add(IntToHex(Get8087CW, 4));   // 1372
Set8087CW( Default8087CW or $08);
Memo1.Lines.Add(IntToHex(Get8087CW, 4));   // 137A
Reset8087CW;
Memo1.Lines.Add(IntToHex(Get8087CW, 4));   // 137A

Очевидно, ошибка.

Теперь первый вопрос - это было интересное упражнение для отладки.

Значение приложения Default8087CW приложения Delphi VCL изменяется от шестнадцатеричного значения 1332 до 1372 с помощью функции Windows.CreateWindowEx, вызванной из Classes.AllocateHWnd, вызываемой из TApplication.Create, вызываемой из секции инициализации блока Controls.pas.

Взгляните на код CreateWindowEx - он объясняет, что происходит. Я не хочу больше обсуждать это - поддержка FPU в Delphi слишком грязная и багги.