【问题标题】:Delphi 5 compiler bug returning interface pointer rather than return valueDelphi 5编译器错误返回接口指针而不是返回值
【发布时间】:2015-12-08 09:30:10
【问题描述】:

我向您展示 Delphi 5 编译器中的一个错误。我知道不会有任何解决办法。但解决方法是 super

program Project1;

uses
  Dialogs, SysUtils;

{$R *.RES}

type
    IFoo = interface
        ['{D68DA49A-F870-433D-9343-4964BFECFF27}']
        procedure Grob(a: Integer; b: Integer);
    end;

    TFoo = class(TInterfacedObject, IFoo)
    public
        procedure Grob(a: Integer; b: Integer); virtual;
    end;

procedure TFoo.Grob(a: Integer; b: Integer);
begin

end;

function DoStuff(): Integer;
var
    foo: IFoo;
begin
    foo := TFoo.Create;
    try
        Result := 1;
        Exit;
    finally
        foo.Grob(0, 0);
    end;

    Result := 2;
end;

var
    n: Integer;
begin
    n := DoStuff;
    if n <> 0 then
        ShowMessage('Failed: '+IntToStr(n))
    else
        ShowMessage('Passed: '+IntToStr(n));

end.

真正的胆量是函数DoStuff应该返回一个:

function DoStuff(): Integer;
var
    foo: IFoo;
begin
    foo := TFoo.Create;
    try
        Result := 1;
        Exit;
    finally
        foo.Grob(0, 0);
    end;

    Result := 2;
end;

函数应该返回一个。相反,它返回接口对象的地址:

大会

代码实际上确实开始将结果设置为 1:

Project1.dpr.30:结果:= 1;
    移动 ebx,$00000001 ;将返回值 1 放入 EBX
Project1.dpr.31:退出;
    调用 @TryFinallyExit ;调用 finally 块
    jmp DoStuff + $6E

当函数即将返回时,它确实将 EBX 复制到 EAX 中以便返回:

mov eax,ebx ;EBX 到 EAX 中返回

但 finally 块(调用接口方法)是问题所在。它吹走了存储在 EBX 中的返回值:

我们从 调用@TryFinallyExit 到达这里 Project1.dpr.33: foo.Grob(0, 0); 异或 ecx,ecx 异或 edx,edx mov eax,[ebp-$04] mov ebx,[eax] 调用 dword ptr [ebx+$0c] ret

“调用”到finally块后,返回跳转,跳转到:

Project1.dpr.36:结果:= 2;
...
    xor eax,eax
    流行音乐
    流行音乐
    流行音乐
    mov fs:[eax],edx
    推 $00442e1f
    lea eax,[ebp-$04]
    打电话给@IntfClear
    ret
...
    mov eax,ebx 
    流行音乐
    流行音乐
    流行音乐
    ret

返回值不是一或二,而是接口指针的地址。

我知道你们都没有 Delphi 5。即使你有,

“你想让我说什么?”

我知道困难。我真正需要的是某种解决方法。

【问题讨论】:

  • 我在这里看到的问题是,我们可以为这段代码提供一个解决方法,但它可能对真正的代码没有帮助........
  • 我会尝试让“grob”不是虚拟的,看看它是否能解决问题。如果是这样,您总是可以将其称为虚拟“DoGrod”或类似的东西。

标签: delphi delphi-5 compiler-bug


【解决方案1】:

正如您所观察到的,编译器将结果存储到EBX,然后在随后将EBX 复制到EAX 以将结果返回给调用者之前覆盖它。

编译器应该执行以下操作之一:

  1. 使用不同的寄存器临时存储结果值,使其使用EBX不会破坏结果值,或者
  2. 在对Grob 的调用中不使用EBX,或者
  3. 将结果值存储在比寄存器更持久的位置,例如堆栈。

显然,选项 1 和 2 对您来说并不容易使用,但后者是您需要在此示例中实现的解决方法 - 使用局部变量来保存您想要的 Result 值,直到您准备好返回它:

function DoStuff(): Integer;
var
  foo: IFoo;
  MyResult: Integer;
begin
  foo := TFoo.Create;
  try
    try
      MyResult := 1;
      Exit;
    finally
      foo.Grob(0, 0);
    end;

    MyResult := 2;
  finally
    Result := MyResult;
  end;
end;

【讨论】:

  • 这很优雅。看起来编译器没有“看到”函数@TryFinallyExit 会做什么。结果它没有意识到它需要保护EBX
  • TryFinallyExit() 并不是修改 EBX 的人。 Grob() 是。编译器对此无能为力。在决定在try 块退出之前将Result 存储在哪里时,它不能“查看”Grob() 的实现。最好让编译器知道它将Result 存储在EBX 中,因此它需要在CALL 的任何CALL 语句中保留EBX,然后在@987654344 结束时达到最终RET @.
  • 这不是真的。 EBX 必须保留。易失性寄存器是 EAX、ECX 和 EDX。
  • @DavidHeffernan: "EBX 必须保留" - 不是按照微软的说法:Using and Preserving Registers in Inline Assembly: "在 C/C++ 函数中使用 __asm 编写汇编语言时,您不需要保留 EAX、EBX、ECX、EDX、ESI 或 EDI 寄存器"
  • @Remy 您正在阅读错误的文档。这不是 MS 内联汇编,因此您引用的文档不相关。我们正在谈论函数调用。请参阅msdn.microsoft.com/en-us/library/windows/hardware/… 或实际上是docwiki.embarcadero.com/RADStudio/en/…
猜你喜欢
  • 2019-06-29
  • 1970-01-01
  • 2016-11-30
  • 1970-01-01
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多