COM: how to share worker-objects between tAutoObject based components

This is ofcourse a dummy-project, my real project has some meaning :)

given this TLB:
unit prjTest_TLB;

{ This file contains pascal declarations imported from a type library.
  This file will be written during each import or refresh of the type
  library editor.  Changes to this file will be discarded during the
  refresh process. }

{ prjTest Library }
{ Version 1.0 }


uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL;

  LIBID_prjTest: TGUID = '{339E4381-D52A-11D1-859C-0060080EA301}';


{ Component class GUIDs }
  Class_een: TGUID = '{339E4383-D52A-11D1-859C-0060080EA301}';
  Class_twee: TGUID = '{339E4385-D52A-11D1-859C-0060080EA301}';


{ Forward declarations: Interfaces }
  iEen = interface;
  iEenDisp = dispinterface;
  iTwee = interface;
  iTweeDisp = dispinterface;

{ Forward declarations: CoClasses }
  een = iEen;
  twee = iTwee;

{ Dispatch interface for een Object }

  iEen = interface(IDispatch)
    function Get_twee: iTwee; safecall;
    property twee: iTwee read Get_twee;

{ DispInterface declaration for Dual Interface iEen }

  iEenDisp = dispinterface
    property twee: iTwee readonly dispid 1;

  iTwee = interface(IDispatch)
    function Get_count: Integer; safecall;
    function Get_needed: WordBool; safecall;
    procedure Set_needed(Value: WordBool); safecall;
    property count: Integer read Get_count;
    property needed: WordBool read Get_needed write Set_needed;

{ DispInterface declaration for Dual Interface iTwee }

  iTweeDisp = dispinterface
    property count: Integer readonly dispid 1;
    property needed: WordBool dispid 2;

{ eenObject }

  Coeen = class
    class function Create: iEen;
    class function CreateRemote(const MachineName: string): iEen;

  Cotwee = class
    class function Create: iTwee;
    class function CreateRemote(const MachineName: string): iTwee;


uses ComObj;

class function Coeen.Create: iEen;
  Result := CreateComObject(Class_een) as iEen;

class function Coeen.CreateRemote(const MachineName: string): iEen;
  Result := CreateRemoteComObject(MachineName, Class_een) as iEen;

class function Cotwee.Create: iTwee;
  Result := CreateComObject(Class_twee) as iTwee;

class function Cotwee.CreateRemote(const MachineName: string): iTwee;
  Result := CreateRemoteComObject(MachineName, Class_twee) as iTwee;


--------> and this implementation: ------>

unit ximpl;


  ComObj, ActiveX, prjTest_TLB;

  ttwee = class;

  tDrie = class
    fNeeded : boolean;
    property needed : boolean read fNeeded write fNeeded;

  tEen = class(TAutoObject, Ieen)
    ftwee : itwee;
    fdrie : tDrie;
    procedure initialize; override;
    function Get_twee: iTwee; safecall;

  tTwee = class (tAutoObject, iTwee)
    fcount : integer;
    fDrie  : tDrie;
    procedure initialize; override;
    function Get_count: Integer; safecall;
    function get_needed : wordbool; safecall;
    procedure set_needed (value:wordbool); safecall;
    property count: Integer read Get_count;
    property needed : wordbool read get_needed;


uses ComServ;

procedure tEEN.initialize;
var twee : ttwee;
  fTwee.needed:=not fTwee.needed;

function Teen.Get_twee: iTwee;

procedure ttwee.initialize;

function ttwee.Get_count: Integer; safecall;

function ttwee.get_needed;

procedure ttwee.set_needed;

  TAutoObjectFactory.Create(ComServer, Teen, Class_een, ciMultiInstance);
  TAutoObjectFactory.Create(ComServer, Ttwee, Class_twee, ciMultiInstance);

--->>>> problem:

at tEen.initialize, the last line reads
fTwee.needed:=not fTwee.needed.
What i'm trying to do is to have a single tDrie component, shared by the tEen and tTwee instances.
Point is, I can't set the tTwee.fDrie variable. If you step through that last line (ftwee.needed:=), you'll see that in tTwee.get_needed and tTwee.set_needed, it's fDrie pointer is NIL!..
How can I overcome this?

I've tried to declare tEen.fTwee as tTwee instead of iTwee (with an fTwee:=tTwee.create instead fTwee:=coTwee.create;), but that will result in tEen.fTwee getting .destroy'ed as soon as I ask for a member-variable of tEen.fTwee... Which is really funky, if you ask me.
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

lowlevelAuthor Commented:
Edited text of question
You are hacking lowlevel.... (so do i lots of times)

However, it has gotten you in trouble this time.

Forget about the COM stuff. What you have here is a whopper of
a design mistake:

"What i'm trying to do is to have a single tDrie component, shared by the tEen and tTwee instances"

and the declarations:

tEen = class(TAutoObject, Ieen)
    ftwee : itwee;
    fdrie : tDrie;


tTwee = class (tAutoObject, iTwee)
    fcount : integer;
    fDrie  : tDrie;

it is these declatations that get you in trouble in the first place. (COM or no COM)

I can see two more or less elegant ways out of here:

1. Create a "manager" object that knows how to initialize een and twee correctly. (and also knows hoe to clean them up)

2. make drie a so called "singleton". One nice way of programming
these in Delphi is to have each client of drie "think" they have
a private instance of drie. However the implementation of drie
assures that all instances of drie use the same "RealDrie" object.
lowlevelAuthor Commented:
i'm not so convinced i'm hacking here. iEen is a container object for iTwee. They both need to use tDrie internally, but the user (of the component, = app programmer) is never to see this tDrie.

Therefore, I cannot make a manager object that initializes iEen and iTwee. It simply doesn't make any sense, because iEen is initialized by the client (appprogrammer), and iEen needs to initialize both iTwee and tDrie. iTwee is really a property of iEen to the app-programmer. So, iEen needs to pass a pointer of tDrie to the component that implements iTwee. Like i said, the user is never to see this tDrie component!

I tried looking for "singleton" material. Couldn't find any in the delphi documentation, and a search on the web resulted in too many garbage-links. Please explain this further, maybe with an example?
Keep up with what's happening at Experts Exchange!

Sign up to receive Decoded, a new monthly digest with product updates, feature release info, continuing education opportunities, and more.

I am sorry that I cannot give you a complete answer, but perhaps I can steer you in the right direction.

If you want to share COM objects you need to use connection points. Delphi has an implementation of IConnectionPoint in TConnectionPoint.
Since I have not used connection points in delphi I am loath to call this an answer, hence the comment.
You will need to use a connection point to have your container / component metaphor implemented correctly.

You are accessing a private member fDrie in twee.fDrie := fDrie. ITwee.fDrie is not an accessable member???? (I am confused here myself)
I think you should design the interface so that the cast above is not necessary. Perhaps use a public interface property to access fDrie.

It would be better to use the ConnectionPoint method, as you (theoretically) should not retain pointers between com objects without connection points. Perhaps at least issue a token addref / release :-)

For an explanation of a "singelton" look in the book
"design paterns". You need this book...
(I can tell from your remarks.)

Still, good luck.
(sorry, but I dont give away code)

lowlevelAuthor Commented:

  i'll look into this.
  iTwee.fDrie is not accessible, right.


  i will get that book and thanx for pointing me to it (just bought it), but you're not helping me very much here. I don't have that book, and/or the knowledge in it.

  So explain to me how to solve this problem: component 1 has a sub-component (property) component 2, and they both need 3. The client cannot see or touch 3.

OK Ill give it another try...

You have a number of options to  do this.

1. No tricks, simple method:

-Client Creates C1
-C1 creates C3
-C1 creates C2 and passes C3 (in constructor...)
C1 and C2 have same C3, Client knows nothing about C3

This looks like a clean solution. The only remark i would
like to make is that it is good practice to give C2 a
"pointer to C3" instead of a C3. That will make your
intentions clear to yourself(after 6 months) and to

2. "singleton", Delphi (or Altena?) style.

-Client Creates C1
-C1 Creates C2
-C2 Creates C3
-C1 Creates C3
or soo it seems. The implementation of C3 assures that the
C1(C3) and C2(C3) both use the same data. (In fact, every
instance of C3 will use the same data).

Since you are offering a planeload of points for this question,
here is how to make an elegant singleton in Delphi:

You create a unit C3_Real. This is where you put the C3 data.

Then you create a unit C3. In the "implementation" section of
C3, you create an instance of C3_Real. (You add C3_Real to the "uses" of the implementation)

All methods/properties of C3 get their data from the modal
C3_real instance.

You will have to judge which solution is best for your real
problem. For the "Dummy" 1-2-3 problem I would choose the first
solution. The Singleton may give rise to bugs when (maintenance)
developers create "scratch" C3-objects and corrupt the data.

Good luck.
lowlevelAuthor Commented:
altena: i'm not going to give u the points just yet. We are getting a lot closer though; from what you've discribed, i'm already using your singleton solution as a temp-solution :)

currently the client creates C1, which in turn creates C3 and sets a dll-global pointer to C3. C1 then proceeds to create C2, and the C2 implementation just uses the dll-global C3 pointer.
This is fine for the current C3 i'm using (I only need one of those per system, not per created C1). But what if I need a C3-type component that does need to be created for each instance of C1?

I like your first solution, which is just about exactly what I'd like to do. Problem here is, however:

-- quoted from original post, updated --
tC1 = delphi component that implements iC1, its interface.
fBlah = internal variable (pointer to) component BLAH. can be a pointer to tBlah or iBlah.
coC2.create = coClass.create declaration taken from any given TLB.
tC1.pC2 = C1's property to C2, assume this always points to tC1.fC2

I've tried to declare tC1.fC2 as tC2 instead of iC2 (with an tC1.fC2:=tC2.create instead of fC2:=coC2.create), but that will result in tC1.fC2 getting .destroy'ed right after the client calls tC1.pC2.anything (property)

--- end updated quote ---

I'm now fairly sure that the destroying of tC1.fC2 (fC2=tC2) is caused by COM reference counting. Problem: How do I overcome this?
If I try to declare tC1.fC2 as an iC2 ref instead of an tC2 ref, i cannot pass the reference to C3 in the C2.constructor, now can I?
Well, I am glad my comments are getting helpfull...

Here are some answers/comments to your remarks

>But what if I need a C3-type component that does need to be
> created for each instance of C1?

Thats easy: Method 1 in the answer does just that.
(we get to the passing of the pointer later)

>I'm now fairly sure that the destroying of tC1.fC2 (fC2=tC2) is
>caused by COM reference counting. Problem: How do I overcome

This also should give not too much trouble. If you are right, and COM refence counting is the cause, then you should call
addref yourself as soon as you get the interface. (and calling release when time comes to clean up).

>If I try to declare tC1.fC2 as an iC2 ref instead of an tC2
>ref, i cannot pass the reference to C3 in the C2.constructor,
>now can I?

Now THAT is a problem...
Maybe you will not like my remark about this, but i think this
is an indication of a design flaw elsewhere.

Heres the problem
-Client creates tC1 (or iC1)
- C1 creates C3
- C1 creates iC2 (cannot pass anything)
.....but we want C2 to use C3

The easy way out is to specify that iC2 MUST have a method to
receive a pointer to C3. However, this will make life quite hard
on C2, as it has to check again and again whether his/her
C3 pointer is set correctly.

A "Hack" would be to pass the C3 pointer though a memory
mapped file, but i will deny having suggested that.
(that is the kind of stuff that makes software non-maintainable)

If I were in this situation, I would go over the design again
and again looking for the real cause of the C1-iC2-C3
construction. a more elegant model may not be too far away.

p.s. instead  of rejecting: just post your comment
If I then "dont answer" or "post rubbish" then you can reject.
(looking at "answer rejected" e-mails is not cool...)

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
lowlevelAuthor Commented:
a d-grade, because:

Your first solution won't work, it's exactly what i wanted in the first place, but it won't fix the problem for automation objects, now will it?

Your singleton solution, as said, is undesired because it creates problems for multiple clients connecting to my server-object.

however, the final solution which I found myself:

(all of these are tAutoObject descendants)
tC1.initialize creates tC2 via tC2.create (not coC2.create), and calls tC2._addref. The automatic deleting of tC2 was caused by tComObject.create, which sets the reference count to 0(!?). If any client then calls _release (which they do all the time), the component will kill itself.
the coC2.create calls up a myriad of functions and classes, but to put it simply: it executes tComObject.create (refcount=0), and then triddles back up to a point where it increases that reference count again.. doh..

Anyway, by creating it as a delphi-object instead of an interface, and calling _addref on it, you then have a reference to the delphi-implementation, and you can set all the tC2.private variables you want, so long as tC2 is in the same unit as tC1 ofcourse.

The first d with an explanation...

And although i feel like it, I am not going to call you names.
Clearly you used my answers/comments to arrive at your solution.

Anyway, it is your right to award a d.

It is my right not to answer any questions from "lowlevel".
lowlevelAuthor Commented:
really, i gave you a d-grade because i did NOT use your comments to arrive at the solution. I could have thought of the "singleton" myself, I just didn't know it was called a singleton.

I just re-read the original answer, and when grading your original answer, i forgot about the remark you made about calling _addref myself. I must have over-looked that when grading your answer. (I read the answer, then played around with the problem myself for about half a week, etc, etc).
Therefore, yes. The grade should be upgraded.
i could make a 10 point dummy question, for which i'll give ya an B, ok?

It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.