【问题标题】:Why an application starts with FPU Control Word different than Default8087CW?为什么应用程序以不同于 Default8087CW 的 FPU 控制字开头?
【发布时间】:2017-02-02 16:40:29
【问题描述】:

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

【问题讨论】:

    标签: delphi com fpu delphi-10.1-berlin


    【解决方案1】:

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

    第一,第二个问题。

    如果你看Set8087CW的代码,你会看到它把新的FPU CW值存储在Default8087CW变量中,Reset8087CWDefault8087CW恢复FPU CW;所以Set8087CW 之后的Reset8087CW 调用根本不执行任何操作,这可以通过

    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
    

    显然是一个错误。

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

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

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

    【讨论】:

    • Reset8087CW 确实做了一些事情。它将控制字设置为当前在Default8087CW 中找到的任何内容。如果CoCreateInstance 修改了控制字,这很可能会产生影响,就像经常发生的那样。你的其他分析是错误的。 CreateWindowEx 确实调用了Set8087CW,但它将调用返回的值传递给Get8087CW。祝你好运让 FP 单位持有$1332。它只是不允许你这样做。
    • 因此,如果您更仔细地调试它,您将看到 FPU 在从 System 的初始化部分调用 _FpuInit 时被初始化。它通过Default8087CW 中的任何内容,通常为$1332,但系统强制设置第6 位,因此设置的值为$1372。与CreateWindowEx 的任何呼叫无关。
    • @DavidHeffernan - 分析是正确的,我已经追踪了Default8087CW 在调试器中的变化。 FPU CW 从未设置为 $1332CreateWindowEx 通过读取 FPU CW 获取 $1372Default8087CW 设置为 $1372
    • 分析完全不正确。再试一次。正如我解释的那样。从 Windows 默认的$027F 开始,然后在System 单元调用_FpuInit 中设置为$1332,但系统强制位6,因此值为$1372。在从System 初始化代码调用_FpuInit 时设置断点,看看我是对的。
    • @DavidHeffernan - 我说过我用的是 Delphi XE,分析完全正确。
    【解决方案2】:

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

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

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

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

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

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

    【讨论】:

    • 已下载您的 QC 报告中的文件。请注意,文件名被切掉并且没有扩展名。添加.zip 解决了这个问题。遗憾的是,@AllenBauer 在他加入时无法修复长期存在的 RTL 错误。
    • @LURD 我想他想这么做,但管理层不允许。
    • 谢谢,我就是这么想的。我没有想到没有可能将 CW 设置为 1332 美元。
    • 请注意,Delphi RTL 在各种地方都使用浮点,包括内存管理(例如Move() 的某些变体),因此几乎不可能保证您没有在某处使用浮点。跨度>
    • @Marc 在那个例子中这不是问题,因为所做的只是使用寄存器而不是执行算术。
    猜你喜欢
    • 1970-01-01
    • 2022-08-22
    • 2012-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-06
    • 1970-01-01
    • 2011-10-31
    相关资源
    最近更新 更多