Why TForm's _release does not call destructor?

254 views Asked by At

Why TForm's _release method does not call destructor?

var
   F, U : IUnknown;

procedure TForm1.btn1Click(Sender: TObject);
begin
  U := IUnknown(TMyObject.Create);    // MyIterfacedObject (inherits TInterfacedObject)
  F := IUnknown(TMyForm.Create(nil)); 
end;

procedure TForm1.btn2Click(Sender: TObject);
begin
  U := nil;    // Calls destructor
  F._Release;  // Does not call destructor
  F := nil;    // Does not call destructor
end;

I took a look at _release methods of TInterfaceObject and TComponent classes:

function TInterfacedObject._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;

function TComponent._Release: Integer;
begin
  if FVCLComObject = nil then
    Result := -1   // -1 indicates no reference counting is taking place
  else
    Result := IVCLComObject(FVCLComObject)._Release;
end;

TInterfacedObject's _release seems quite intelligible, but what TComponent's _release do? Seems weird to me...

1

There are 1 answers

5
David Heffernan On

The reason is that TComponent adopts the policy that lifetime management is the responsibility of the user of the class, and is not to be managed automatically by any interface references taken. That policy is expressed clearly in TComponent._Release.

function TComponent._Release: Integer;
begin
  if FVCLComObject = nil then
    Result := -1   // -1 indicates no reference counting is taking place
  else
    Result := IVCLComObject(FVCLComObject)._Release;
end;

The common scenario, the one which you describe, has FVCLComObject equal to nil. And so the code explicitly says that there is no reference counting by returning -1. It's even commented that way.

Lifetime management needs to be done one way or another. The two commonly used patterns with Delphi code are:

Lifetime managed by caller

var
  obj: TMyObject;
....
obj := TMyObject.Create;
try
  DoSomething(obj);
finally
  obj.Free; // the object is explicitly destroyed here
end;

Although, TComponent is often used a little differently from this in that its constructor is passed an owner component. And this owner is then charged with the responsibility of destroying the owned component. So that pattern looks like this:

component := TMyComponent.Create(OwnerComponent);
... // now component will be destroyed when OwnerComponent is destroyed

Lifetime managed by interface references

var
  intf: IMyInterface;
....
intf := TMyObject.Create;
DoSomething(intf);
// the implementing object behind intf is destroyed when the last 
// interface reference leaves scope

You cannot mix the two patterns. The design choice was made that TComponent would follow the first pattern. And so the interface reference counting must be disabled. By way of contrast, TInterfacedObject adopts the other policy.