views:

232

answers:

3

This Delphi code will show a memory leak for an instance of TMyImplementation:

program LeakTest;

uses
  Classes;

type
  MyInterface = interface
  end;

  TMyImplementation = class(TComponent, MyInterface)
  end;

  TMyContainer = class(TObject)
  private
    FInt: MyInterface;
  public
    property Impl: MyInterface read FInt write FInt;
  end;

var
  C: TMyContainer;
begin
  ReportMemoryLeaksOnShutdown := True;

  C := TMyContainer.Create;
  C.Impl := TMyImplementation.Create(nil);
  C.Free;
end.

If TComponent is replaced by TInterfacedObject and the constructor changed to Create(), the leak disappears. What is different with TComponent here?

Many thanks for the answers. To sum up: it is easy, but wrong, to say "If you are using interfaces, they are reference counted and hence they are freed for you." - Actually any class which implements an interface can break this rule. (And there will be no compiler hint or warning be shown.)

+8  A: 

The implementation of _Release in TComponent does not free your instance.
The implementation of _Release in TInterfacedObject does free the object.

Perhaps someone can chime in but my guess is that TComponent is not meant to be used as a reference counted object the way we normally use interfaces.

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;
Lieven
Correct! The live of a TComponent is normally coontrolled by its Owner.
Uwe Raabe
In other words, pick one semantic rule set and follow it. If you inherit from TComponent, you are having your object tell the whole world, and the developer using it, "I'm a TComponent". If you inherit from TComponent and then you fix _Release up to have it use interfaced object memory semantics, you confuse everyone. And if anyone adds your component to a form, well, that would be a mess.
Warren P
+3  A: 

A component is supposed to be owned and destroyed by something else, typically a form. In that scenario, reference count is not used. If you pass a component as an interface reference it would be very unfortunate if it was destroyed when the method returns.

Therefore, reference counting in TComponent has been removed.

Lasse V. Karlsen
+9  A: 

TComponent doesn't implement its _AddRef and _Release methods the same as TInterfacedObject does. It defers its reference counting to its VCLComObject property, which should be some other interfaced object. Since TComponent doesn't count references, it can't detect when its reference count reaches zero, so it doesn't free itself.

The VCLComObject property holds an interface reference, which should implement IVCLComObject. If a component's associated VCLComObject object has been told that it owns the component, then when that interface's reference count reaches zero, it will destroy its associated component. It's told it owns the component by calling its FreeOnRelease method.

All this is designed to make it easier to wrap VCL components into COM objects. If that's not your goal, then you'll probably be fighting against several other unexpected design aspects along the way, so you might wish to re-evaluate your motivation for making your components implement interfaces in the first place.

Rob Kennedy