Jump to content
A.M. Hoornweg

tContainedObject, tAggregatedObject

Recommended Posts

Hello all,

 

does anyone have any links to documentation/examples about the correct use of tContainedObject and tAggregatedObject? 

I find the little documentation I can find rather confusing.

 

Kind regards,

Arthur

 

Share this post


Link to post

You should probably read some literature on COM in general first. Then the documentation will make more sense:

https://docs.microsoft.com/da-dk/windows/win32/com/containment-delegation

https://docs.microsoft.com/da-dk/windows/win32/com/aggregation

 

...but maybe this can help: https://stackoverflow.com/questions/3483680/delphi-how-delegate-interface-implementation-to-child-object

 

FWIW, without having a clue about what problem you are trying to solve, my bet is that you should just concentrate on TAggregatedObject. You very seldom need to deal with TContainedObject.

Share this post


Link to post

COM is not the problem, I've been implementing COM objects for years. 

 

My question is really specific about the correct use and usefulness of tAggregatedObject and tContainedObject, because as far as I can see they are not needed at all to build aggregates, tInterfacedobject can already do so using ordinary tObjects.  So I'm probably missing something essential about their use and usefulness!

 

For example, this aggregate uses a tAggregatedObject:

 

type
  iHello = interface
    ['{58AD07B9-FCE5-494A-94B2-E1CAF7D05B1D}']
    procedure Hello;
  end;

  iGoodbye = interface
    ['{35079C32-AF3D-4E9D-808C-6A6DDED38663}']
    procedure Goodbye;
  end;

  tAggregate = class(taggregatedobject, iHello)
    procedure Hello;
  end;

  tCompositeObject1 = class(tinterfacedobject, iHello, iGoodbye)
  protected
    ainner: tAggregate;
    function GetInner: iHello;
  public
    procedure Goodbye;
    destructor Destroy; override;
    property Inner: iHello read GetInner implements iHello;
  end;

procedure tAggregate.Hello;
begin
  messagebox(0, pchar('Hello'), pchar('Hello'), mb_ok);
end;


destructor tCompositeObject1.Destroy;
begin
  messagebox(0, pchar('Destructor'), pchar('tCompositeObject1'), mb_ok);
  ainner.free;
  inherited;
end;

function tCompositeObject1.GetInner: iHello;
begin
  if not assigned(ainner) then
    ainner := tAggregate.create(self);
  result := ainner;
end;

procedure tCompositeObject1.Goodbye;
begin
  messagebox(0, pchar('Goodbye'), pchar('Goodbye'), mb_ok);
end;


procedure TestCode1;
var
  Hello: iHello;
  Goodbye: iGoodbye;
begin
  Goodbye := tCompositeObject1.create;
  Goodbye.Goodbye;
  Hello := Goodbye as iHello;
  Hello.Hello;
end;

 

... and this one does the very same thing using a plain vanilla tObject:

 

type
  iHello = interface
    ['{58AD07B9-FCE5-494A-94B2-E1CAF7D05B1D}']
    procedure Hello;
  end;

  iGoodbye = interface
    ['{35079C32-AF3D-4E9D-808C-6A6DDED38663}']
    procedure Goodbye;
  end;

  tSimpleObject = class
    procedure Hello;
  end;

  tCompositeObject2 = class(tinterfacedobject, iHello, iGoodbye)
  protected
    ainner: tSimpleObject;
    function GetInner: tSimpleObject;
  public
    procedure Goodbye;
    destructor Destroy; override;
    property Inner: tSimpleObject read GetInner implements iHello;
  end;

procedure tSimpleObject.Hello;
begin
  messagebox(0, pchar('Hello'), pchar('Hello'), mb_ok);
end;


destructor tCompositeObject2.Destroy;
begin
  messagebox(0, pchar('Destructor'), pchar('tCompositeObject2'), mb_ok);
  ainner.free;
  inherited;
end;

function tCompositeObject2.GetInner: tSimpleObject;
begin
  if not assigned(ainner) then
    ainner := tSimpleObject.create;
  result := ainner;
end;

procedure tCompositeObject2.Goodbye;
begin
  messagebox(0, pchar('Goodbye'), pchar('Goodbye'), mb_ok);
end;


procedure TestCode2;
var
  Hello: iHello;
  Goodbye: iGoodbye;
begin
  Hello := tCompositeObject2.create;
  Hello.Hello;
  Goodbye := Hello as iGoodbye;
  Goodbye.Goodbye;
end

 

So is there any advantage in using tAggregatedObject / tcontainedobject  at all ?

 

 

I currently see more advantages in using tObject, because that allows me to take an existing class and encapsulate it inside a wrapper class whenever I want it to be interface based.  For example, these few lines of code make a tStringlist interface-based, expose the methods add, get and put and make the whole thing reference counted:

 

type
  // Just expose a selected bunch of methods of tStrings
  iStrings = interface
    ['{242798EB-0A53-4284-BA16-306FF354E2AC}']
    function Add(const S: string): Integer;
    function Get(Index: Integer): string;
    procedure Put(Index: Integer; const S: string);
    property Strings[index: Integer]: string read Get write Put; default;
  end;

  tInterfacedStrings = class(tinterfacedobject, iStrings)
  protected
    fList: tStringlist;
    function getList: tStringlist;
  public
    destructor Destroy; override;
    property list: tStringlist read getList implements iStrings;
  end;


destructor tInterfacedStrings.Destroy;
begin
  fList.free;
  inherited;
end;

function tInterfacedStrings.getList: tStringlist;
begin
  if not assigned(fList) then
    fList := tStringlist.create;
  result := fList;
end;

 

 

 

Share this post


Link to post
1 hour ago, A.M. Hoornweg said:

My question is really specific about the correct use and usefulness of tAggregatedObject and tContainedObject, because as far as I can see they are not needed at all to build aggregates, tInterfacedobject can already do so using ordinary tObjects.  So I'm probably missing something essential about their use and usefulness!

You've answered the question yourself with your example: Use TAggregatedObject when the inner object is reference counted.

Share this post


Link to post
Posted (edited)
25 minutes ago, Anders Melander said:

You've answered the question yourself with your example: Use TAggregatedObject when the inner object is reference counted.

But tAggregatedObject doesn't DO the reference counting, it lets the controller (the owner)  do that.  And the controller explicitly manages the inner object's lifetime, not through reference counting.

function TAggregatedObject._AddRef: Integer;
begin
  Result := FController._AddRef;
end;

And when using a plain vanilla tObject (my examples 2 and 3), the owner ALSO does the reference counting.  Same thing, but faster (since the _addref isn't chained).     So I still don't see what tAggregatedObject is actually meant for, what problem it is supposed to solve!  

 

 

 

Edited by A.M. Hoornweg

Share this post


Link to post
48 minutes ago, A.M. Hoornweg said:

So I still don't see what tAggregatedObject is actually meant for, what problem it is supposed to solve!

I think they are primarily meant to be used when you're working with COM. For non-COM interfaces I agree that there's little point.

 

Let's say your inner object is itself a wrapper of an external COM object. In order to have the inner object aggregate the external object, using implements, the inner object must implement IUnknown but delegate it's methods to the containing object and this is where I can see a purpose for TAggregatedObject.

Share this post


Link to post
Posted (edited)
1 hour ago, A.M. Hoornweg said:

And the controller explicitly manages the inner object's lifetime, not through reference counting.

Exactly.  If you are familiar with Excel automation think about the Applications and the Workbooks collection.  The second exists only as part of the first.  The second could be implemented as a TContainedObject.

 

What TAggregatedObject and TContainedObject have in common:

  • They have no separate reference counting
  • They should be created and destroyed by the Controller.

How do they differ? (from the source code)

  • TAggregatedObject simply reflects QueryInterface calls to its controller.  From such an aggregated object, one can  obtain any interface that the controller supports, and  only interfaces that the controller supports.  This is  useful for implementing a controller class that uses one
      or more internal objects to implement the interfaces declared  on the controller class.  Aggregation promotes implementation  sharing across the object hierarchy.
  • TContainedObject is an aggregated object that isolates  QueryInterface on the aggregate from the controller.    TContainedObject will return only interfaces that the  contained object itself implements, not interfaces  that the controller implements.
Edited by pyscripter

Share this post


Link to post
15 hours ago, pyscripter said:
  • TAggregatedObject simply reflects QueryInterface calls to its controller.  From such an aggregated object, one can  obtain any interface that the controller supports, and  only interfaces that the controller supports.  This is  useful for implementing a controller class that uses one
      or more internal objects to implement the interfaces declared  on the controller class.  Aggregation promotes implementation  sharing across the object hierarchy.
  • TContainedObject is an aggregated object that isolates  QueryInterface on the aggregate from the controller.    TContainedObject will return only interfaces that the  contained object itself implements, not interfaces  that the controller implements.

Okay, that makes sense; so if I use tAggregatedObject then I can use the "AS" keyword to freely switch between the inner and the outer interface (see example 1) whereas in tContainedObject that isn't possible (it would only let me switch from the outer to the inner interface, right?) .

 

 

 

 

 

Share this post


Link to post
Posted (edited)

Here's a translated link to a Brazialian developer (Marcos Douglas B. Santos) who was wrestling with a better use of aggregates in his code.

https://www.translatetheweb.com/?from=&to=en&dl=en&rr=UC&a=https%3a%2f%2fobjectpascalprogramming.com%2fposts%2fobjetos-agregados%2f

 

Not exactly what you are asking but I found it interesting reading.

 

Original article in Portuguese https://objectpascalprogramming.com/page6/

Edited by David Champion
  • Like 1

Share this post


Link to post
5 hours ago, A.M. Hoornweg said:

Okay, that makes sense; so if I use tAggregatedObject then I can use the "AS" keyword to freely switch between the inner and the outer interface (see example 1) whereas in tContainedObject that isn't possible (it would only let me switch from the outer to the inner interface, right?) .

Yes

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×