I am reading a book on design patterns whose examples are mostly in Java.
So I am trying to work the examples in Delphi. On several occassions
Interfaces are used and the classes that implement them are instantiated and
assigned to fields in other objects. In one example one such field is
changed at run time, where another object that implements the same interface
is assigned to the field.
When I wrote the example in Delphi it worked fine. But I was nervous about
the reference counting with this technique. My (limited) understanding of
the TInterfacedObject is that _Release decrements the count when the object
goes out of scope. So, if I have an interface and 2 classes like this:
type
IMyInterface = interface
...
end;
TMyIClass1 = class(TInterfacedObject, IMyInterface)
...
end;
TMyIClass2 = class(TInterfacedObject, IMyInterface)
...
end;
// in another class have a field:
TMyOtherClass = class(TObject)
private
FMyField: IMyInterface;
...
end;
// then initially assign this way
FMyField := TMyIClass1.Create;
// and subsequently do this
FMyField := TMyIClass2.Create;
Does the reference counting get messed up? Will this introduce a memory
leak of some sort? Or does the assignment of a new InterfacedObject
automatically take the first one out of scope and call the _Release method?
(I hope this made sense.)
Joe
| Does the reference counting get messed up? Will this introduce a memory
| leak of some sort? Or does the assignment of a new InterfacedObject
| automatically take the first one out of scope and call the _Release
method?
No, the only time you get problems with reference counting is when you have
both object and interface references to the same object; something that Java
doesn't have problems with.
Joanna
--
Joanna Carter [TeamB]
Consultant Software Engineer
This is the right group. You're asking about how a particular facet of
the Delphi language (i.e., interfaces) works.
> I am reading a book on design patterns whose examples are mostly in Java.
> So I am trying to work the examples in Delphi. On several occassions
> Interfaces are used and the classes that implement them are instantiated and
> assigned to fields in other objects. In one example one such field is
> changed at run time, where another object that implements the same interface
> is assigned to the field.
>
> When I wrote the example in Delphi it worked fine. But I was nervous about
> the reference counting with this technique. My (limited) understanding of
> the TInterfacedObject is that _Release decrements the count when the object
> goes out of scope.
Sort of. To begin with, you have the cause and effect reversed. The
_Release method does not cause anything to go out of scope. When an
interface variable goes out of scope, the compiler generates code at
that point to call the object's _Release method.
But scope is just one way for an interface to get released. It can also
be released by having an interface-reference variable get overwritten
with a new value. When a new value gets assigned to a variable, there is
one less reference to the previous value, so the compiler inserts a call
to _Release. Reference-counting _counts_ the number of _references_.
If any call to _Release brings the new reference count to zero, then the
object frees itself. (Look in the implementation of
TInterfacedObject._Release to see one way of doing that.)
> So, if I have an interface and 2 classes like this:
>
> type
> IMyInterface = interface
> ...
> end;
Give a GUID to each of your interfaces. With the cursor after the
"interface" keyword above, press Ctrl+Shift+G.
> TMyIClass1 = class(TInterfacedObject, IMyInterface)
> ...
> end;
> TMyIClass2 = class(TInterfacedObject, IMyInterface)
> ...
> end;
>
> // in another class have a field:
>
> TMyOtherClass = class(TObject)
> private
> FMyField: IMyInterface;
> ...
> end;
>
> // then initially assign this way
>
> FMyField := TMyIClass1.Create;
>
> // and subsequently do this
>
> FMyField := TMyIClass2.Create;
>
> Does the reference counting get messed up?
It shouldn't. Is there anything that suggests your program isn't working
correctly?
> Will this introduce a memory
> leak of some sort? Or does the assignment of a new InterfacedObject
> automatically take the first one out of scope and call the _Release method?
That's what reference counting does. It counts the number of references
to an instance. When the count reaches zero, the object gets destroyed.
Entering and leaving scope is just one way of changing the number of
references. Assigning a new value to a variable is another way, which
has nothing to do with the scope.
--
Rob
A little spoon of tar to the barrel of honey (this is a Russian saying :))
When you are working with interfaces you must be ready to this surprises:
1. http://qc.borland.com/wc/qcmain.aspx?d=31164
2. http://qc.borland.com/wc/qcmain.aspx?d=29238
3. Don't mix objects and interfaces if you are not familiar with interfaces.
If you do this you can get very-very unexpected behavior. The matter is that
Delphi generates code that calls _Release for all local variables of type
equals to _interface_. Therefore if you mix objects and interfaces you can
get direct access to object. Soo you can call free, for example. So if you
call Free before method ends Delphi will try to call _Release of destroyed
method (because delphi has interface variable which was not cleared
automatically when object was destroyed). It's not good :)
For example
procedure ...
var
I: IMyInterface;
O: TMyObject; // implements IMyInterface
begin
O := TMyObject.Create();
I := O;
O.Free();
end;
If you don't get the AV or Privelaged instruction exeptions you are licky :)
This behavior can be fixed like this
procedure ...
var
I: IMyInterface;
O: TMyObject; // implements IMyInterface
begin
O := TMyObject.Create();
I := O;
O.Free();
Pointer(I) := nil; // clear I, soo _Release will not call for I variable.
end;
--
Regards,
Dimitry Timokhov
PS Delphi2006, Prof, Win32, Update2
>> When you are working with interfaces you must be ready to this
>> surprises: 1. http://qc.borland.com/wc/qcmain.aspx?d=31164
>
> I already reported this earlier. TSomeClass.Create does not do an
> _AddRef, so the reference count is still 0. Only if you do
> TSomeClass.Create as ISomeInterface the reference count will be 1.
> For the case where the interface is not used as const, an _AddRef will be
> done, but also a _Release.
I know it very well. I only wanted to accent attantion of author of topic on
it.
>> 2. http://qc.borland.com/wc/qcmain.aspx?d=29238
>
> That is not a bug. It is how the compiler works. It creates two unnamed
> interfaces, and each call to CoDemo.CreateDemo assigns one. Only after
> Test finishes, all these items will be released.
I also don't think that it is a bug. Therefore report has type equals to
suggestion.
But I think that such behavior is not soo obvious.
> IOW, this is as designed.
I understant. But is designe good?
> I see it is time for that article on interfaces (especially on interface
> lifetime in Win32) I have planned to write.
It will be very helpfull for the begginers.
Thanks for your reply.
> Sort of. To begin with, you have the cause and effect reversed. The
> _Release method does not cause anything to go out of scope. When an
> interface variable goes out of scope, the compiler generates code at that
> point to call the object's _Release method.
OK. I probably didn't articulate it correctly. But I my understanding was
as you state above.
> But scope is just one way for an interface to get released. It can also be
> released by having an interface-reference variable get overwritten with a
> new value. When a new value gets assigned to a variable, there is one less
> reference to the previous value, so the compiler inserts a call to
> _Release. Reference-counting _counts_ the number of _references_.
This is exactly what I wanted to know. Thanks.
> Give a GUID to each of your interfaces. With the cursor after the
> "interface" keyword above, press Ctrl+Shift+G.
Yup. I did do that.
> It shouldn't. Is there anything that suggests your program isn't working
> correctly?
Nope. As I mentioned in my original post, it does seem to work fine. Call
it a comfort post from one not too experienced with interfaces. Hehe.
Joe
1) ==============
>> > > 2. http://qc.borland.com/wc/qcmain.aspx?d=29238
<skipped>
> I don't see how it could be done otherwise.
> I don't see an alternative. The lifetime resolution of interfaces is
> method level, not statement level, if you know what I mean.
In the report there is a workaround which was made by Sebastian Modersohn.
It's easy way to make program work as I want.
I think that Delphi during compilation can analize - is result of function
(in my example CoDemo.CreateDemo) saved in any local variable?
If result is saved in local variable, then reference counting should work
like it do in now - call _Release on the end of method or if variable is
changed to new value.
If result isn't saved anywhere _Release must be called immediately.
Soo code
begin
Temp:=CoDemo.CreateDemo().DoSomething;
Temp:=CoDemo.CreateDemo().DoSomething;
end;
in fact equals to this code
var
Temp: IDemo; // automatically added by Delphi
begin
Temp:=CoDemo.CreateDemo().DoSomething;
Temp:=nil; // automatically added by Delphi
Temp:=CoDemo.CreateDemo().DoSomething;
Temp:=nil; // automatically added by Delphi
end;
It seems to me that this is the way.
>> > I see it is time for that article on interfaces (especially on
>> > interface lifetime in Win32) I have planned to write.
>>
>> It will be very helpfull for the begginers.
>
> Any suggestions on what should be in it, except the points raised here?
1) Difference between interfaces with GUID and without GUID.
2) Using of *supports* procedures.
3) Using of *as* keyword.
4) Creating interfaced objects without reference counting.
5) Dangerous while mixing objects and interfaces. Some approachs to avoid
them.
> Any suggestions on what should be in it, except the points raised here?
I think an article is an excellent idea. Raw interfaces (that is,
without COM) are one of the greatest features of Delphi and yet they
aren't as widely known as they should be. We find them an indispensable
tool in architecting extensible and flexible products, so anything done
to raise the profile and awareness of interfaces has got to be good.
Delayed calls to _Release in compiler-generated temporary variables
caught me out a few years ago, so I think that's worth covering. My case
involved a TInterfacedObject and a TContainedObject and I wasn't
expecting something like this
OuterIntf.InnerIntf.Method()
to generate temporaries. (Details here: http://tinyurl.com/f8xgm)
Cheers,
Mark.
> And that will work, but that is not what the compiler can do for you. The
> compiler does not know what you want.
I cannot agree with you at all.
I dont think that compiler should know what I want (I know it myself).
But compiler should work logical. Now I'll try to explain where I see not
logical behavior.
Let's see this code (this is code from my report, but Test procedure
changed).
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
IDemo=interface
['{48FE2E29-4647-4437-A38C-F25B0F94663B}']
Procedure DoSomething;
end;
TDemo=Class(TInterfacedObject,IDemo)
public
Procedure DoSomething;
Procedure BeforeDestruction; override;
end;
CoDemo=Class
Class Function CreateDemo:IDemo;
end;
Procedure TDemo.DoSomething;
begin
Writeln('Method DoSomething was called');
end;
Procedure TDemo.BeforeDestruction;
begin
Writeln('Beeing destroyed');
end;
class function CoDemo.CreateDemo: IDemo;
begin
Result:=TDemo.Create();
end;
procedure Test();
var
kI: Integer;
begin
for kI := 0 to 1 do
CoDemo.CreateDemo().DoSomething;
WriteLn('Finish on method Test');
end;
begin
Test();
WriteLn('Finish of program');
ReadLn;
end.
Can you say me without executing the program about console output?
I guess that *you* will do it OK. But can you explain to begginers what is
the difference between
procedure Test();
var
kI: Integer;
begin
for kI := 0 to 1 do
CoDemo.CreateDemo().DoSomething;
WriteLn('Finish on method Test');
end;
and
procedure Test();
begin
CoDemo.CreateDemo().DoSomething;
CoDemo.CreateDemo().DoSomething;
WriteLn('Finish on method Test');
end;
?
--
Regards,
Dimitry Timokhov
It seems to me that I finally agree with you :)
You gave very good explanation. Of couse I know how it works. But I cannot
find logical explanation for that.
I think it is good to place this nicety to your future article.
I'll close my report and give reference to this topic.
Perhaps the C++ side of interfaces? Mixing C++ and VCL can be interesting,
sometimes. I'd especially be interested in C++ descendants of
TInterfacedObject, TAutoIntfObject, etc (I suppose doing that is possible,
but would there be problems? Is it an easier way to COM support than the
ATL route?)
I want to advaice you, don't write article with many words and few source
code - good source code, *based on you experience*, with a few necessary
comments - this is the best article!
Where are in internet may articles with common words - "wow, interfaces -
it's good, it's very good, wow, wow" - but a few source code :(
Public benefit of such article is very small. More practice!
PS. IMHO