Friday, May 06, 2005

Circular reference issue of IInterface Object

" class="oa" bg nowrap="nowrap" width="1%">Joanna Carter (TeamB) Jun 13 2004, 2:50 pm show options
Newsgroups: borland.public.delphi.oodesign
From: "Joanna Carter \(TeamB\)" - Find messages by this author
Date: Sun, 13 Jun 2004 19:50:12 +0100
Local: Sun,Jun 13 2004 2:50 pm
Subject: Re: Interfaces - Usage rules
Reply to Author | Forward | Print | Individual Message | Show original | Report Abuse

"Robert MIRCEA" a écrit dans le message de news:
40cc1...@newsgroups.borland.com...

> "Either always use interface references, or object references, but never
mix
> the two of them"

Unless you are very experienced or don't care aboout going insane :-)
> and I would like to know more code patterns that I should follow when I
use
> interfaces. How about some rules regarding the inter-referencing of
> interfaces/objects?

The only real gotcha with mutual references is with aggregated objects. Due
to the mutual reference, neither object can be the first to release the
other's memory, therefore you have a memory leak. However, if you use a week
reference on one of the objects, usually the inner, then the problem goes
away.

IInner = interface

end;

IOuter = interface

end;

TInner = class(TInterfacedObject, IInner)
private
fOuter: Pointer; // weak reference
protected
function GetOuter: IOuter; // access function
public
constructor Create(const Outer: IOuter);
end;

constructor TInner.Create(const Outer: IOuter);
begin
inherited Create;
fOuter := Pointer(Outer); // does not increment refcount on Outer
end;

function TInner.GetOuter: IOuter;
begin
Result := IOuter(fOuter); // increments refcount until caller either
// nils result or it goes out of
scope
end;

TOuter = class(TInterfacedObject, IOuter)
private
fInner: IInner; // normal reference
end;

> I've noticed that TInterfaceList is the "recommended" way to go whenever I
> need to hold a list of interfaces. After studying the code for this class,
> I've noticed that TInterfaceList uses internally a TThreadList. However,
it
> seems to me that iterating thru a TInterfaceList using Items property
might
> be subject of heavy locking/unlocking of the underlying list.

TInterfaceList is not only recommended, it is the only pre-written way to
go. Unless you fancy doing it yourself, just use it :-) Any penalties for
locks don't seem to be significant on most modern processors.
> Since using critical sections for synchronization is a very expensive
> operation, how would you interate efficiently thru a TInterfaceList?

Use the standard way of for i := 0 to List.Count - 1.

I have never had any problems and certainly don't notice any untoward side
effects.

> What is the inner mechanism when passing an interface as a "const"
parameter
> for a method? What is the difference between passing an interface with
> "const" and without "const"?

By specifying const for an interface parameter, you are avoiding the
extraneous _AddRef and _Release calls that would otherwise be generated on
entry and exit (this is the same as for strings). As a rule, always use
const.

Also if you are passing a newly created interface ref object into a const
param, then ensure that you cast the constructor call to the desired
interface otherwise the end of the method that creates the temporary object
will release the memory and you will get an AV.

procedure TestMethodToCall(const Thing: IThing);
begin

end;

procedure TTest.ExecuteBadly;
begin
TestMethodToCall(TThing.Create);
end; // temporary object released here

procedure TTest.ExecuteProperly;
begin
TestMethodToCall(TThing.Create as IThing);
end;

Joanna

--
Joanna Carter (TeamB)

Consultant Software Engineer
TeamBUG support for UK-BUG
TeamMM support for ModelMaker


" class="oa" bg nowrap="nowrap" width="1%">Ritchie Annand Jun 14 2004, 6:07 am show options
Newsgroups: borland.public.delphi.oodesign
From: Ritchie Annand - Find messages by this author
Date: Mon, 14 Jun 2004 04:07:33 -0600
Local: Mon,Jun 14 2004 6:07 am
Subject: Re: Interfaces - Usage rules
Reply to Author | Forward | Print | Individual Message | Show original | Report Abuse

In article <40cc1...@newsgroups.borland.com>, noki...@yahoo.com says...

> Hi everybody,

Hi Robert :)
> I am seeking some advice from some more experienced users of Delphi
> interfaces regarding the rules I should follow whenever I develop my domain
> objects with the use of interfaces. I am aware of only one rule:
> "Either always use interface references, or object references, but never mix
> the two of them"

That's a very good rule of thumb. You -can- break the rule if you
understand exactly what's going on, but by and large it's a bad idea.

-- 8< --

Here's a quick breakdown of why:

* Using interface references inserts (Object)._AddRef calls during
assignment and non-const parameter calls (this usually isn't a problem)

* Using interface references inserts (Object)._Release calls during re-
assignment, at the end of methods or while freeing an object they're
contained within. - This is the one that causes half of the problems,
because if you manually .Free an object that has an interface reference
on it, you'll get an (Invalid pointer)._Release being called.

* If you assign the result of a constructor to an object reference (X :
TMyObject; X := TMyObject.Create), it starts with a RefCount of 0 - if
you assign it to an interface reference (X : IMyInterface; X :=
TMyObject.Create;), it starts with a RefCount of 1. So, if you do the
object reference first, the first call you pass it into as an interface
reference will go (Object)._AddRef, (do stuff), (Object)._Release, which
will drop the RefCount down to 0, and say "oh, no more references" and
.Free it

-- 8< --

I just ran into some the other day where I *had* to make a TComponent
implement an interface. I got access violations when I was holding onto
one of its children in my main form when I tried loading another file or
exiting the program. And that's me "already knowing better than to do
that" :)

> and I would like to know more code patterns that I should follow when I use
> interfaces. How about some rules regarding the inter-referencing of
> interfaces/objects?

Here are a few 'rules' I go by when using interfaces:

* Keep the interfaces as technology-independent as possible (so, for
example, if you've got an IEventLogger interface, *don't* put a FileName
property in it - your TEmailLogger and TOutputDebugStringLogger won't use
it, and likewise, your TOutputFileLogger won't be using an e-mail address
:)

(ideally, you should be able to completely fake the object behind your
interface, and the other objects wouldn't know - that's one of the
principles behind unit testing, which interfaces are *very* good for)

* Put the technology-dependent items into the constructor if possible (so
for your log-to-file object, create it with the FileName, a la:
TOutputFileLogger.Create(AFileName: String) );

* Make your interfaces cohesive. If you have an object that needs to do
two different jobs, it's best to make two different interfaces and have
the object implement both than to make one giant interface

As for inter-referencing, that's always a tough question. It's always
just a matter of reference-counting.

Essentially, anywhere you may have circular references, you have two
choices:

* Make all back-references 'weak' (i.e. non-reference-counted) -
typically done by storing them as Pointer and typecasting on the spot as
necessary

* Don't just let go of the references or let them go out of scope - tell
them to go away

There are a lot of variations on that last theme. A simple means might be
something like this:

Make an interface:

IBye = interface
[guid]
procedure Goodbye;
end;

Make a Bye() procedure like this:

procedure Bye(var AInterface);
var
Intf : IInterface;
ByeIntf : IBye;
begin
Intf := IInterface(AInterface);
if Supports(Intf,IBye,ByeIntf) then
ByeIntf.Goodbye;
Intf := nil;
end;

Add IBye to any object that refers back to its parent. Implement it by
letting go of any parent, child or other references, e.g.:

type
TMyObject = class(TMyBaseObject,IBye)
private
...
public
procedure Goodbye;
end;

procedure TMyObject.Goodbye;
var
Index : Integer;
CurrentChild : IMyChild;
begin
FParent := nil;
FScreenCaptureObject := nil;
FPreferences := nil;
for Index := 0 to ChildCount-1 do
begin
CurrentChild := Children[Index];
Bye(CurrentChild);
end;
end;

> Second question:
> I've noticed that TInterfaceList is the "recommended" way to go whenever I
> need to hold a list of interfaces. After studying the code for this class,
> I've noticed that TInterfaceList uses internally a TThreadList. However, it
> seems to me that iterating thru a TInterfaceList using Items property might
> be subject of heavy locking/unlocking of the underlying list.
> Since using critical sections for synchronization is a very expensive
> operation, how would you interate efficiently thru a TInterfaceList?

Critical sections being a very expensive operation is,
by and large, a complete myth; it doesn't even rank up there with adding
two strings together :) They're not interrupts, and they don't do
anything funny to the processor - you'd be surprised how mundane they are
beneath the radar.

The actual expensive part of the critical section is the
WaitForSingleObject (bringing that thread to a standstill, but it doesn't
chew up any CPU time once the wait is started) that gets called by the
operating system if you request a critical section that's already in use.

You only get the WaitForSingleObject happening if you try to Acquire the
critical section from another thread while you've got it Acquired in one
thread. You'll never run into it in single-threaded programming, and it's
pretty easy to avoid in multi-threaded programming.

You honestly won't notice. A profiler will prove it as well ;)

That said, remember to use it as an IInterfaceList:

var
List : IInterfaceList;
begin
List := TInterfaceList.Create;

...and you can create a non-critical-section-based list yourself later,
if you see fit. (Copy the implementation, make the FList a TList and
remove all the locking calls :)

> Third question:
> What is the inner mechanism when passing an interface as a "const" parameter
> for a method? What is the difference between passing an interface with
> "const" and without "const"?

(1)
procedure A(const X: IMyInterface);
begin
...
end;

(2)
procedure A(X: IMyInterface);
begin
...
end;

The non-const (2) is exactly like the const (1) with a couple of
additions:

There's an X._AddRef during the call to A in (2).
There's an X._Release during the 'end;' in (2).

By and large, you can use const to not bother with those two calls.

And, of course, as with other 'const' versus 'non-const' parameters, you
can't reassign the local X := ; in example (1), but you can in
example (2).

There are a couple of presentations I put together that you can add to
the pile you may have discovered on your travels, at:
http://nimble.nimblebrain.net/delphi.html, which includes weak pointer
examples, and my cyclic-reference-capable TFundamental class.

My apologies in advance for my tendency towards information overload.

> Thank you,

I hope it helps, Robert!
> Robert

-- Ritchie Annand
Senior Software Architect
Malibu Engineering & Software Ltd.
http://www.malibugroup.com
http://www.nimblebrain.net

0 Comments:

Post a Comment

<< Home