为什么应用程序以不同于 Default8087CW 的 FPU 控制字开始?

Why an application starts with FPU Control Word different than Default8087CW?

能否请您帮助我了解我在 Win32 平台上的 Delphi 应用程序中 FPU 控制字的情况。

当我们创建一个新的VCL 应用程序时,控制字被设置为1372h。这是我不明白的第一件事,为什么它是 1372h 而不是 1332h 这是 System 单元中定义的 Default8087CW

这两者的区别:

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 );
{$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位(溢出掩码)。我不明白为什么它在之后调用 Reset8087CW,而不是恢复进入函数之前的单词状态?

第6位保留,忽略。从 FPU 行为相同的意义上说,这两个控制字实际上是相等的。系统恰好设置了保留位。即使您尝试将值设置为 32,系统也会将其设置为 72。无论您要求第 6 位具有什么值,它总是会被设置。因此,在比较这些值时,您必须忽略该位。这里没什么可担心的。

至于 CreateOleObject 作者决定,如果您要使用该函数,那么您还需要在使用 COM 对象时屏蔽溢出,甚至更远。谁知道他们为什么这样做,而且只针对 32 位代码?可能他们发现了一堆经常溢出的 COM 对象,因此添加了这个橡皮膏。仅在创建时屏蔽溢出是不够的,在使用对象时也需要这样做,因此 RTL 设计者选择从今以后取消屏蔽溢出。

或者这可能是一个错误。他们决定不针对 32 位代码修复它,因为人们依赖于这种行为,但他们确实针对 64 位代码进行了修复。

无论如何,这个函数并没有什么特别之处。你不需要使用它。您可以编写自己的代码来完成您想要它做的事情。

使用互操作时,浮点控制是一个问题。 Delphi 代码需要未屏蔽的异常。使用其他工具构建的代码通常会掩盖它们。理想情况下,当您调出 Delphi 代码并在 return 上取消屏蔽它们时,您会屏蔽异常。期望其他库任意更改控制字。还要注意 Set8087CW 不是线程安全的,这是 Embarcadero 多年来一直拒绝解决的一个大问题。

前路漫漫。如果您没有在程序中使用浮点数,那么您可以简单地屏蔽异常并且可能没问题。否则,您需要确保在所有线程的所有点都正确设置了控制字。一般来说,使用标准 Delphi RTL 几乎是不可能的。我个人通过用线程安全版本替换 RTL 的关键部分来处理这个问题。我已在这份 QC 报告中记录了如何执行此操作:QC#107411

免责声明:我调试了 Delphi XE 中的问题。

第一,第二个问题

如果您查看 Set8087CW 的代码,您会发现它将新的 FPU CW 值存储在 Default8087CW 变量中,并且 Reset8087CW 从 [=12= 恢复 FPU CW ];所以 Set8087CW 之后的 Reset8087CW 调用什么都不做,

证明了这一点
Memo1.Lines.Clear;
Memo1.Lines.Add(IntToHex(Get8087CW, 4));   // 1372
Set8087CW( Default8087CW or );
Memo1.Lines.Add(IntToHex(Get8087CW, 4));   // 137A
Reset8087CW;
Memo1.Lines.Add(IntToHex(Get8087CW, 4));   // 137A

显然是一个错误。

现在是第一个问题 - 这是一个有趣的调试练习。

Delphi VCL 应用程序的 Default8087CW 值由 Windows.CreateWindowEx 函数从十六进制 1332 更改为 1372,从 Classes.AllocateHWnd 调用,从 TApplication.Create 调用,从 Controls.pas 单元的初始化部分调用。

查看 CreateWindowEx 代码 - 它解释了发生的情况。我真的不想进一步讨论它 - Delphi 中的 FPU 支持太混乱和错误了。