A.M. Hoornweg 144 Posted July 6, 2020 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
Anders Melander 1782 Posted July 6, 2020 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
A.M. Hoornweg 144 Posted July 6, 2020 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
Anders Melander 1782 Posted July 6, 2020 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
A.M. Hoornweg 144 Posted July 6, 2020 (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 July 6, 2020 by A.M. Hoornweg Share this post Link to post
Anders Melander 1782 Posted July 6, 2020 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
pyscripter 689 Posted July 6, 2020 (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 July 6, 2020 by pyscripter Share this post Link to post
A.M. Hoornweg 144 Posted July 7, 2020 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
David Champion 48 Posted July 7, 2020 (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 July 7, 2020 by David Champion 1 Share this post Link to post
pyscripter 689 Posted July 7, 2020 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