【问题标题】:Moment of last IUnknown.Release call上次 IUnknown.Release 调用的时刻
【发布时间】:2015-09-15 12:20:50
【问题描述】:

我有两个相同代码的变体:

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  IMyObject1 = interface
    ['{4411181F-3531-4D30-AB18-A8326F8C2CD0}']
  end;

  IMyObject2 = interface
    ['{41C88E1A-0360-4AC3-B021-125880B23DE5}']
  end;

  TMyObject = class(TInterfacedObject, IMyObject1, IMyObject2)
  public
    destructor Destroy; override;
  end;


destructor TMyObject.Destroy;
begin
  Writeln('Destroy');
  inherited;
end;

procedure Variant1;

  function GetMyObject: IMyObject2;
  var
    Obj1: IMyObject1;
  begin
    Obj1 := TMyObject.Create;
    try
      Obj1.QueryInterface(IMyObject2, Result);
    finally
      Obj1 := nil;
    end;
  end;

var
  Obj2: IMyObject2;
begin
  Obj2 := GetMyObject;
  try
  finally
    Obj2 := nil;
  end;
  Writeln('Variant1 end of proc');
end;

function GetMyObject: IMyObject2;
var
  Obj1: IMyObject1;
begin
  Obj1 := TMyObject.Create;
  try
    Obj1.QueryInterface(IMyObject2, Result);
  finally
    Obj1 := nil;
  end;
end;

procedure Variant2;
var
  Obj2: IMyObject2;
begin
  Obj2 := GetMyObject;
  try
  finally
    Obj2 := nil;
  end;
  Writeln('Variant2 end of proc');
end;

begin
  Variant1;
  Writeln('---');
  Variant2;
  Writeln('---');
  Readln;
end.

输出

Variant1 过程结束 破坏 --- 破坏 Variant2 进程结束 ---

为什么两个变体的行为不同?

【问题讨论】:

  • 我希望你不介意我把问题改写一下,把代码变成一个MCVE
  • 我确定您知道GetMyObject 的返回值是未定义的,会触发未定义的行为。
  • @Johan,返回值在 QueryInterface 调用中设置。在这种情况下为零。
  • @LURD 不是nil,因为实现对象实现了IMyObject2
  • @DavidHeffernan,我的错,我在之前的测试中从类定义中注释掉了 IMyObject2。

标签: delphi


【解决方案1】:

测试环境:Delphi XE7 Windows 编译器

Variant1 中,编译器决定它需要创建一个隐式局部变量来保存一个额外的接口引用。很明显为什么会这样,我无法辨别。但这里发出的代码表明这是发生了什么:

变体1

Project1.dpr.46:Obj2:= GetMyObject; 0041A3BD 55推ebp 0041A3BE 8D45F8 lea eax,[ebp-$08] 0041A3C1 E836FFFFFF 调用 GetMyObject 0041A3C6 59 流行音乐 0041A3C7 8B55F8 mov edx,[ebp-$08] 0041A3CA 8D45FC lea eax,[ebp-$04] 0041A3CD E816F5FEFF call @IntfCopy // 复制到隐式本地

变体2

Project1.dpr.70:Obj2:= GetMyObject; 0041A53B 8D45FC lea eax,[ebp-$04] 0041A53E E839FFFFFF 调用 GetMyObject // 这里没有这样的副本

隐式局部变量在声明它们的函数的最后完成,就像任何其他局部变量一样。这就是为什么Writeln('Variant1 end of proc')会执行,然后隐式局部被终结,然后对对象的最终引用被释放。

这里非常有趣的一个因素是,如果您启用优化,那么输出将变为:

破坏 Variant1 过程结束 --- 破坏 Variant2 进程结束 ---

由于某种原因,编译器决定在启用优化时不创建隐式本地。

当然,以上都是针对 32 位编译器的。 64 位编译器又不一样了,你不知道吗。在 64 位编译器中,无论优化如何,输出都是根据问题。

我认为这个问题可能与我的问题Is the compiler treatment of implicit interface variables documented? 有关,据我所知,没有关于编译器功能的官方规范或文档,您可以在这里做的最好的事情是通过观察经验性地学习它的输出。优化会改变行为意味着您应该尽量避免容易受到这种行为变化影响的代码。如果您确实可以预测它们。

【讨论】:

  • 感谢您的调查 :)
猜你喜欢
  • 1970-01-01
  • 2016-08-10
  • 2021-12-09
  • 1970-01-01
  • 2014-09-26
  • 1970-01-01
  • 2020-03-11
  • 2015-09-02
  • 1970-01-01
相关资源
最近更新 更多