Solved

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

Posted on 1998-04-16
12
340 Views
Last Modified: 2013-11-23
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 }

interface

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

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

const

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

type

{ 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)
    ['{339E4382-D52A-11D1-859C-0060080EA301}']
    function Get_twee: iTwee; safecall;
    property twee: iTwee read Get_twee;
  end;

{ DispInterface declaration for Dual Interface iEen }

  iEenDisp = dispinterface
    ['{339E4382-D52A-11D1-859C-0060080EA301}']
    property twee: iTwee readonly dispid 1;
  end;

  iTwee = interface(IDispatch)
    ['{339E4384-D52A-11D1-859C-0060080EA301}']
    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;
  end;

{ DispInterface declaration for Dual Interface iTwee }

  iTweeDisp = dispinterface
    ['{339E4384-D52A-11D1-859C-0060080EA301}']
    property count: Integer readonly dispid 1;
    property needed: WordBool dispid 2;
  end;

{ eenObject }

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

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



implementation

uses ComObj;

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

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

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

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


end.

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

unit ximpl;

interface

uses
  ComObj, ActiveX, prjTest_TLB;

type
  ttwee = class;

  tDrie = class
  private
    fNeeded : boolean;
  published
    property needed : boolean read fNeeded write fNeeded;
  end;

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

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

implementation

uses ComServ;

procedure tEEN.initialize;
var twee : ttwee;
begin
  fdrie:=tdrie.create;
  ftwee:=coTwee.create;
  twee:=ttwee(fTwee);
  twee.fDrie:=fDrie;
  fdrie.needed:=false;
  fTwee.needed:=not fTwee.needed;
end;

function Teen.Get_twee: iTwee;
begin
  result:=fTwee;
end;

procedure ttwee.initialize;
begin
  fdrie:=nil;
  fcount:=3;
end;

function ttwee.Get_count: Integer; safecall;
begin
  result:=fCount;
end;

function ttwee.get_needed;
begin
  result:=fdrie.needed;
end;

procedure ttwee.set_needed;
begin
  fdrie.needed:=value;
end;

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

--->>>> 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.
0
Comment
Question by:lowlevel
  • 6
  • 5
12 Comments
 
LVL 1

Author Comment

by:lowlevel
ID: 1361594
Edited text of question
0
 
LVL 3

Expert Comment

by:altena
ID: 1361595
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)
  private
    ftwee : itwee;
    fdrie : tDrie;
...

and

tTwee = class (tAutoObject, iTwee)
  private
    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.
0
 
LVL 1

Author Comment

by:lowlevel
ID: 1361596
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?
0
 
LVL 1

Expert Comment

by:cmain
ID: 1361597
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)
  ftwee:=coTwee.create;
  twee:=ttwee(fTwee);
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 :-)

Regards
Craig.
0
 
LVL 3

Expert Comment

by:altena
ID: 1361598
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)

0
 
LVL 1

Author Comment

by:lowlevel
ID: 1361599
cmain:

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

altena:

  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.


0
Why You Should Analyze Threat Actor TTPs

After years of analyzing threat actor behavior, it’s become clear that at any given time there are specific tactics, techniques, and procedures (TTPs) that are particularly prevalent. By analyzing and understanding these TTPs, you can dramatically enhance your security program.

 
LVL 3

Expert Comment

by:altena
ID: 1361600
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
others.

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.
0
 
LVL 1

Author Comment

by:lowlevel
ID: 1361601
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?
0
 
LVL 3

Accepted Solution

by:
altena earned 250 total points
ID: 1361602
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?

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...)
0
 
LVL 1

Author Comment

by:lowlevel
ID: 1361603
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.

0
 
LVL 3

Expert Comment

by:altena
ID: 1361604
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".
0
 
LVL 1

Author Comment

by:lowlevel
ID: 1361605
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?

0

Featured Post

Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

Join & Write a Comment

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
Illustrator's Shape Builder tool will let you combine shapes visually and interactively. This video shows the Mac version, but the tool works the same way in Windows. To follow along with this video, you can draw your own shapes or download the file…
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…

706 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

20 Experts available now in Live!

Get 1:1 Help Now