Solved

COM Aggregation

Posted on 2002-04-22
15
456 Views
Last Modified: 2010-04-04
A while ago I asked a question and someone said "use aggregation."  I'm still not seeing how to do this in my situation (probably due to my lack of understanding aggreation) so I'd like some discussion on the matter.  PLEASE HELP...  

Here is the situation, simplified:

Think of a TTreeView in COM.  We would have a ITreeView and an ITreeNode.  The ITreeView should be able to hold ANY ITreeNode (unless programmed to only contain certain "kinds" of nodes), where the ITreeNodes could actually be programmed by multiple different programmers from anywhere in the world (local to my organization and outside) and registered for use (I got the registration part figured out already...).

So how would this be done considering that the ITreeView must have Nodes collection and each ITreeNode must "know" which ITreeView "owns" it?  Additionally, a ITreeNode must be able to "disown" from an ITreeView and switch to being owned by another one.  Is aggregation really how to do this?  If so, how?  If not, how can it be done?

Feel free to offer suggestions and examples if you have any.  Anyone getting me on the right track to implementation will get the points, so put your best foot forward.

Scott
0
Comment
Question by:millerw
15 Comments
 
LVL 1

Expert Comment

by:xsoft
ID: 6962253
listening
0
 
LVL 12

Expert Comment

by:Lee_Nover
ID: 6963900
I also wanna know this :)
0
 
LVL 1

Author Comment

by:millerw
ID: 6965868
Lets see if we can generate some interest.
0
 
LVL 2

Expert Comment

by:mikepj
ID: 7003241
I admit to not having written or used this myself but I found it some day out on the Internet.  This does demonstrate aggregation though.  It's actually also a demonstration of aliasing methods.

MP

Type
  IFoo=interface
    ['{2F439832-9E11-11D4-9107-00E029421384}']
    function F1:Integer;
  End;

  IBar=interface(IFoo)
    ['{2F439833-9E11-11D4-9107-00E029421384}']
    function F1:integer;
  End;

  TFooBar=class(TInterfacedObject,IFoo,IBar)
    // aliased methods
    function IFoo.F1=FooF1;
    function IBar.F1=BarF1;

    // interface methods
    function FooF1:Integer;
    function BarF1:Integer;
  End;

function TFooBar.FooF1:Integer;
begin
  Result:=0;
end;

function TFooBar.BarF1:Integer;
begin
  Result:=0;
end;

You'd get a compiler error if the methods weren't aliased because you must address all methods and any overlaps.

0
 
LVL 1

Author Comment

by:millerw
ID: 7006017
MikePJ:

Unfortunately, your example does not show aggregation.  Aggregation is the process by which you have an inner object and an outer object.  The outer object publishes the inner object and clients do not know the difference since the inner object sends all QIs to the outer object and does not have a reference count itself (it uses the outer object's ref count).  

You have two "interfaces" in the example above--showing implementation of these two interfaces in one CoClass.  You must start with two implementations (CoClasses) to accomplish aggregation.  EG In the above example, IFoo and IBar would each be implemented in separate CoClasses with one of them publishing the other.

Delegation (do not confuse this word with Delphi's keyword) and Aggregation are methods to reuse actual implemented code, not interfaces.  It is the solution to not being able to inherit and polymorph code using interfaces (interfaces don't actually contain implementation code they simply establish a contract between server and client).  Thus, you don't have to rewrite code, you can simply use an inner object--that already implements the needed interfaces--to "implement" needed interfaces on your outer object, but everyone using your object will see the object as implementing the required interfaces itself (when it actually doesn't).  Delegation means you have to write code yourself.  Aggregation means the author of your inner object had to program it to allow for aggregation.

For more info, try the Microsoft MSDN library and search for aggregation and delegation.  They have figures that show what each is and how they differ from each other.

Scott
0
 
LVL 1

Author Comment

by:millerw
ID: 7006022
Increased points to 1000
0
 
LVL 1

Author Comment

by:millerw
ID: 7006029
Guess not, 500 limit, nice of it to abort.
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 

Expert Comment

by:neld
ID: 7018192
I Hope i haven't misunderstood the question but here goes:

in order for the Itreenode to "know" its owner, it obviously would require a property to store the reference to its owner.

ITreeNode = interface
  function GetOwner: IUnknown;
  procedure SetOwner(const Value: IUnknown);

  property Owner: IUnknown read GetOwner write SetOwner
end

The owner property can be set to the treeview in the add method of the nodes collection and can be unset in the delete/remove method of the nodes collection.  

The nodes collection will obviously need a reference to the treeview, when the treeview creates its internal nodes collection, it sets the owner property of the nodes collection to itself, prior to releasing the nodes collection the owner property is set to nil.  Add sets the owner property of the added node to its own owner property, remove sets the owner property of the node being remove to nil.

ITreeNodes = interface
  function GetCount: Integer;
  function GetNode(Index: Integer): ITreeNode;
  function GetOwner: IUnknown;
  procedure SetOwner(const Value: IUnknown);
  function Add(const ANode: ITreeNode): Integer;
  procedure Remove(const ANode: ITreeNode);
   
  property Owner: IUnknown read GetOwner write SetOwner
  property Count: Integer read GetCount;
  property Nodes[Index: Integer]: ITreeNode read GetNode;
end;

So the owner property of any ITreeNode will either point to its "owning" TreeView or nil (in which case it has not been added to any treeview). ITreeNodes owner property will be the TreeView.

NOW THE TRICK...

If the nodes collection has a reference to the treeview and the treeview has a reference to the nodes collection, you have a dead-lock.  The Treeview will never be destroyed.  Think about it, nodes collection in all likelihood will be released in the destroy of the TreeView, because the nodes collection has a reference to the TreeView, its reference-count cannot reach 0 until the nodes collection is destroyed.  The Treeview will not enter its destroy method, where the nodes collection is destroyed until such time as its reference count = 0, catch 22.  Similary this applies to ITreeNode and its owner property.  Any ITreenode that has a reference to the Treeview will be in the nodes collection, the ITreenode cannot release the Treeview reference until it is removed from the collection (when the collection is destroyed), the collection is not destroyed because the Treeview reference count is > 0.  

Apply the follow as a rule of thumb: A child interface cannot hold a reference to its parent in any manner that increments the parents reference count.

The solution is quite simple, the reference to the owner must be stored as a pointer. e.g.

this code is an extract of the nodes collection class.
interface...

private
  FOwner: pointer;
protected
  function GetOwner: IUnknown;
  procedure SetOwner: IUnknown;


implementation

function TNodesCollection.GetOwner: IUnknown;
begin
  Result := IUnknown(FOwner);
end;

procedure TNodesCollection.SetOwner(const Value: IUnknown);
begin
  FOwner := pointer(Value);
end;
 

Similiarly the TreeNode class must store its owner the same way:

private
  FOwner: pointer;
protected
  function GetOwner: IUnknown;
  procedure SetOwner: IUnknown;


implementation

function TTreeNode.GetOwner: IUnknown;
begin
  Result := IUnknown(FOwner);
end;

procedure TTreeNode.SetOwner(const Value: IUnknown);
begin
  FOwner := pointer(Value);
end;

******
hope this helps, from my understanding of your question, I fail to see why you would need to implement aggregation ?
Aggregated objects require special care and should not be taken lightly.  Personally I dont bother with aggregation, Interface Delegation (Implements directive) is in my view much simpler.

If I've got the cat by the tail let me know and I re-address the question.

Derrick
0
 
LVL 1

Author Comment

by:millerw
ID: 7021537
Derrick:

Thanks for the reply.  I have gotton that far with my research, but I run into the problem with the following (which I hope you can address):

You say we must use the Add and Remove methods and they assign the owner.  What is to prevent an outside source from modifying the owner without calling Add and Remove?  For a "safe" implementation, we have to guarentee that EITHER an assignment of Owner OR a call to Add or Remove do the necessary functions to accomplish parenthood/ownership.  

Do you now see the issue I have been struggling with?

You can see this by looking at TTreeView and TTreeNode (TCollection and TCollectionItem may be better).  You can create the nodes externally and assign their Owner/Parent properties directly or you can use the Items.Add method.  Or, you can use Items.Add and assign Owner to nil and say good-bye to the TTreeView.  This makes sure than both ways accomplish ownership and parenthood.  (but of course they have the advantage of being able to meddle into the implementation of other objects--which we don't have the luxury of doing).

So, in my thinking--and correct me if I'm wrong--we must "intercept" the call to SetOwner and do some extra processing to actually add the node into the internal collection of nodes of an ITreeView CoClass.  That is why I think people have been pointing me to aggregation and delegation.  The "actual" node CoClass (written by someone else) is contained in another CoClass (that I wrote) that intercepts the call and does the necessary processing before actually handing the call over to the "actual" node CoClass.  But exactly how do we do this without publishing an interface from the parent ITreeView that users could meddle with and possibly crash the object?

Another problem with that approach is it now puts a contraint on how the objects are created or puts a, possibly unnecessary, contraint on the "plug-in author."  It can also add to confusion in that setting Owner without the interception object in place does nothing where, if the interception object were in place, the parenthood/ownership would be accomplished in the background.

Then we run into the problem of what happens if different ITreeView CoClass have different "interceptor" implementations (or do they?).  A particular node can only be owned by particular ITreeView CoClasses--how is this guaranteed?

Scott
0
 

Expert Comment

by:neld
ID: 7022647
Hi Scott,

okay, I think i now understand your problem and why people have suggested aggregation, firstly lets  check if we’re on the same wave length.  A Treenode should be able to get in to the treenodes collection of the the treeview by either explicitly calling the add method of the collection or by setting the owner/treeview property of the treenode to the treeview to whom the treenode wishes to belong, a two-way tool kind of mechanism.  A third-party developer who creates their own treenode,  would need to understand of the internal workings of this mechanism because part of the mechanism code needs to be implemented in the treenode to ensure its correct working.  Firstly a third-party developer should not need this understanding (the blackbox concept) and secondly you would prefer all the mechanism code to be your own there by ensuring its correct implementation.  Have I got it ??

If so then yes you are probably gonna have to use aggregation, or rather the treenode developer will need to use aggregation or interface delegation (provided the developer is using Delphi).  The mechanism code required in the treenode will have its own interface (IBindTreeNode) and will be implemented in its own coclass. Third-party developers will be required to implement both IBindTreeNode and ITreenode, the IBindTreeNode interface will however be delegated to the coclass (that you wrote) which really just leaves ITreenode to be implemented.  You wont be able to shield the third partly developer completely but atleast this way he wont need to understand the internal workings.  The two-way tool approach will also protect the mechanism against any fiddling and provide consistancy with the vcl classes mentioned.

If this is what you want to do ? then I’ll try to provide you with an example.

Derrick  
0
 
LVL 1

Author Comment

by:millerw
ID: 7024115
Derrick:

Yes.  That is VERY close to what I want.  The only two issues that remain are that, from my understanding, you need to already have a controller coclass in order to accomplish aggregation.  I've been thinking of it as my coclass (the one that implements your IBindTreeNode) being the outside object and the third-developer's object being the inner object.  You have reversed them....which may work better than my approach.  However there is this interesting little object called a "universal delegator" (UD) that I've been looking at which sounds like it might handle my approach while making the "IBindTreeNode" invisible to where everyone looking at the coclass for a node will only see the ITreeNode and all calls are "preprocessed" by the UD before actual processing by ITreeNode.  I might be adding some pretty major and unnecessary complexity, so let me know.  Here is the link for the UD:

http://www.microsoft.com/msj/0199/intercept/intercept.htm

Problem here is I am not too familiar with COM implementation code in C++ and my assembler is rusty.

The last issue is that there might and will probably be several different ITreeView CoClasses floating around.  I am hoping to just have one IBindTreeNode coclass that works with them all, if possible--or however it gets implemented.

Yeah, the black-box approach is a necessity.  No one but the ITreeView implementation programmer should understand what is going on in the background.

Thanks,
Scott
0
 
LVL 1

Author Comment

by:millerw
ID: 7027101
One thing I forgot....the caption of each ITreeNode must be unique and guaranteed to be unique within the context of a ITreeView.

Scott
0
 
LVL 1

Author Comment

by:millerw
ID: 7071433
Derrick:

You still working on an example or have you given up?

Scott
0
 
LVL 1

Expert Comment

by:pnh73
ID: 9003982
No comment has been added lately, so it's time to clean up this TA.
I will leave a recommendation in the Cleanup topic area that this question is:

PAQ/No Refund

Please leave any comments here within the next seven days.
 
PLEASE DO NOT ACCEPT THIS COMMENT AS AN ANSWER!
 
Paul (pnh73)
EE Cleanup Volunteer
0
 

Accepted Solution

by:
YensidMod earned 0 total points
ID: 9096385
Question is PAQ'd and points refunded.

YensidMod
Community Support Moderator @Experts Exchange
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…
This tutorial demonstrates a quick way of adding group price to multiple Magento products.

747 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now