Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moment of last IUnknown.Release call

Tags:

delphi

I have two variants of the same code:

{$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.

Output

Variant1 end of proc
Destroy
---
Destroy
Variant2 end of proc
---

Why is the behaviour of the two variants different?

like image 860
Denis Anisimov Avatar asked Jun 29 '15 06:06

Denis Anisimov


1 Answers

Test environment: Delphi XE7 Windows compilers

In Variant1 the compiler decides that it needs to create an implicit local variable to hold an extra interface reference. Quite why it does so I cannot discern. But here's the emitted code to show that this is what happens:

Variant1

Project1.dpr.46: Obj2 := GetMyObject;
0041A3BD 55               push ebp
0041A3BE 8D45F8           lea eax,[ebp-$08]
0041A3C1 E836FFFFFF       call GetMyObject
0041A3C6 59               pop ecx
0041A3C7 8B55F8           mov edx,[ebp-$08]
0041A3CA 8D45FC           lea eax,[ebp-$04]
0041A3CD E816F5FEFF       call @IntfCopy      // copies into the implicit local

Variant2

Project1.dpr.70: Obj2 := GetMyObject;
0041A53B 8D45FC           lea eax,[ebp-$04]
0041A53E E839FFFFFF       call GetMyObject    // no such copy here

Implicit locals are finalized at the very end of the function in which they are declared, just like any other local variable. Which is why Writeln('Variant1 end of proc') executes, and then the implicit local is finalized, and then the final reference to the object is released.

One factor that is quite interesting here is that if you enable optimization then the output changes to:

Destroy
Variant1 end of proc
---
Destroy
Variant2 end of proc
---

For some reason the compiler decides not to create an implicit local when optimization is enabled.

Of course, all the above is for the 32 bit compiler. The 64 bit compiler is different again, wouldn't you know it. In the 64 bit compiler the output is as per the question irrespective of optimization.

I think that this issue is perhaps related to my question Is the compiler treatment of implicit interface variables documented? To the very best of my knowledge, there is no official specification or documentation of what the compiler does, and the best you can do here is learn empirically by observing its output. That optimization changes behaviour means that you should be trying to avoid code that is susceptible to such behavioural changes. If indeed you can predict them.

like image 99
David Heffernan Avatar answered Nov 15 '22 14:11

David Heffernan