 |
BorlandTalk.com Borland discussion newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Ian Boyd Guest
|
Posted: Thu Aug 24, 2006 7:07 pm Post subject: How to maintain interface based objects |
|
|
Consider an interface:
IBottle = interface(IUnknown)
function Get_Capacity: Long; safecall;
function Get_CurrentVolume: Long; safecall;
property Capacity: Long read Get_Capacity;
property CurrentVolume: Long read Get_CurrentVolume;
end;
And the object that implements it:
TBottle = class(TComObject)
private
FCapacity: Long;
FVolume: Long;
protected
procedure Sip;
{ IBottle }
function Get_Capacity: Long;
function Get_Volume: Long;
end;
function TBottle.Get_Capacity: Long;
begin
Result := FCapacity;
end;
function TBottle.Get_Volume: Long;
begin
Result := FVolume;
end;
procedure TBottle.Sip;
begin
FVolueme := FVolume -25;
if FVolume < 0 then
FVolume := 0;
end;
And someone creates a bottle by calling
var
Bottle: IBottle;
begin
Bottle := TBottle.Create;
FBottleList.Add(Bottle);
end;
How do i update FCapacity and FVolume of the bottle?
You cannot override the constructor of a TComObject, you have to put any
initialization code in Initialize, but you still aren't allowed to pass
parameters to Initialize.
i would ideally like to use
var
Bottle: TBottle;
begin
Bottle := TBottle.Create;
Bottle.FCapacity := 325; //mL
Bottle.FVolume := 127; //mL
FBottleList.Add(Bottle as IBottle);
end;
and later i can update the values by using:
var
intf: IBottle;
Bottle: TBottle;
begin
intf := FBottleList.Items[3];
Bottle := TBottle(intf);
Bottle.Volume := 315; //mL
Bottle.Sip;
end;
But this is, aside from being ill-advised and wrong, can lead to objects
being freed when you don't expect.
i could do something horribly disgusting and painful like
ISecretBottle = interface(IBottle)
procedure Set_Volume;
procedure Set_Capacity;
procedure Sip;
end;
TBottle = class(TObject, IBottle, ISecretBottleInterface)
private
{ ISecretBottle }
procedure Set_Volume;
procedure Set_Capacity;
procedure Sip;
protected
{ IBottle }
function Get_Capacity: Long;
function Get_Volume: Long;
end;
But that's really impractical.
So how am i supposed to maintain the private values in an object that
exposes those values through a read-only interface, and i am not allowed to
talk to the VCL object anymore? |
|
| Back to top |
|
 |
Ian Boyd Guest
|
Posted: Thu Aug 24, 2006 7:16 pm Post subject: Re: How to maintain interface based objects |
|
|
| Quote: | And the object that implements it:
TBottle = class(TComObject)
private
FCapacity: Long;
FVolume: Long;
protected
procedure Sip;
{ IBottle }
function Get_Capacity: Long;
function Get_Volume: Long;
end;
|
Another reason why i can't just create another private interface to maintain
the object state. There can be dozens of data members and methods, most of
which are not automatable types. |
|
| Back to top |
|
 |
Chris Cheney Guest
|
Posted: Fri Aug 25, 2006 12:03 am Post subject: Re: How to maintain interface based objects |
|
|
"Ian Boyd" <ian.borlandnews010 (AT) avatopia (DOT) com> wrote in
news:44edb4d6$1 (AT) newsgroups (DOT) borland.com:
| Quote: |
And the object that implements it:
TBottle = class(TComObject)
private
FCapacity: Long;
FVolume: Long;
protected
procedure Sip;
{ IBottle }
function Get_Capacity: Long;
function Get_Volume: Long;
end;
Another reason why i can't just create another private interface to
maintain the object state. There can be dozens of data members and
methods, most of which are not automatable types.
|
An interface (e.g. IBottle) provides an, er, interface that is independent
of the implementation.
You have provided one particular implementation of that interface by your
TBottle object. It seems as though you wish to do implementation-dependent
things whilst maintaining the IBottle interface. That's fine - you can do
those implementation-dependent things using TBottle - just because IBottle
exists doesn't mean that you have to forget about TBottle (though users of
IBottle won't necessarily know anything about TBottle). As you have already
realised, you might also provide another interface to do those
implementation-dependent things.
You, as the programmer of the object that is providing the IBottle
interface, have to make the decisions on how you are going to implement the
things that you want to do.
BTW re "which are not automatable types", this may be stating the obvious:
there is a difference between COM and Automation.
--
For e-mail address, remove the XXs |
|
| Back to top |
|
 |
John Carlyle-Clarke Guest
|
Posted: Fri Aug 25, 2006 12:19 am Post subject: Re: How to maintain interface based objects |
|
|
Chris Cheney <XXChris.CheneyXX (AT) tesco (DOT) net> wrote in
news:Xns9829CC186A637ChrisCheneytesconet (AT) 207 (DOT) 105.83.66:
| Quote: | "Ian Boyd" <ian.borlandnews010 (AT) avatopia (DOT) com> wrote in
news:44edb4d6$1 (AT) newsgroups (DOT) borland.com:
Another reason why i can't just create another private interface
to maintain the object state. There can be dozens of data members
and methods, most of which are not automatable types.
BTW re "which are not automatable types", this may be stating the
obvious: there is a difference between COM and Automation.
|
Indeed, and an automatable COM object can have non-automation
compatible interfaces that are only used within the Delphi program
without any of the problems associated with mixing interface and object
references. The interface is genuinely secret because it is not
declared in the type library.
I'm assuming that you (Ian) _are_ talking about automation objects here
because you mention automation compatibility. It goes without saying
that interfaces themselves as a language feature do not depend on COM
_or_ automation. |
|
| Back to top |
|
 |
Ian Boyd Guest
|
Posted: Fri Aug 25, 2006 12:39 am Post subject: Re: How to maintain interface based objects |
|
|
| Quote: | just because IBottle exists doesn't mean that you have to forget about
TBottle.
|
The problem is that object variables do not use reference counts.
var
Bottle: TBottle;
intf: IBottle;
begin
Bottle := TBottle.Create;
intf := Bottle as IBottle; //Delphi increments reference count by one
CallSomeFunctionThatUsesIBottle(intf); //Delph increments count and then
decrements count
intf := nil; //Delphi decrements count by one - object destroyed
Bottle.FVolume := 327; //Trying to access freed object. Acess violation.
end;
So, in response to your statement "just because IBottle exists doesn't mean
you have to forget about TBottle":
You do have to forget about TBottle; else you will be losing objects.
| Quote: | You, as the programmer of the object that is providing the IBottle
interface, have to make the decisions on how you are going to implement
the
things that you want to do.
|
This is fine, i'm okay with only the things available through the interface
being available through the interface - that's the only interface i want the
"outside" to have to my object. Problem is trying to talk to an object
through an object reference and though an interface reference.
| Quote: | BTW re "which are not automatable types", this may be stating the obvious:
there is a difference between COM and Automation.
|
Since i'm writing a service, my service is unable to load my COM dll's
"in-process" due to then being in different apartments. So the classes in my
dll, and the interfaces i give to the objects in the dll need to be
marshalled by COM - hence the automation. It originally was simply COM, but
that it's being redone as a service, it needs type libraries and "automation
compatible types" |
|
| Back to top |
|
 |
Ian Boyd Guest
|
Posted: Fri Aug 25, 2006 12:44 am Post subject: Re: How to maintain interface based objects |
|
|
| Quote: | I'm assuming that you (Ian) _are_ talking about automation objects here
because you mention automation compatibility. It goes without saying
that interfaces themselves as a language feature do not depend on COM
_or_ automation.
|
Yes. However in this case, my interfaces are available from classes exported
from a Dll. And the standard way of creating objects that are implemented
inside a DLL is to use COM. What's more, the person calling COM's
LoadLibrary+GetProcAddress+DllGetClassObject are in an apartment
incompatible with the interfaces exported by the classes in the dll; and so
COM is now trying to marshall the interface across apartment boundaries;
requiring that i make a type library and use automatically marshallable data
types. |
|
| Back to top |
|
 |
Ian Boyd Guest
|
Posted: Fri Aug 25, 2006 12:52 am Post subject: Re: How to maintain interface based objects |
|
|
| Quote: | var
Bottle: TBottle;
intf: IBottle;
begin
Bottle := TBottle.Create;
intf := Bottle as IBottle; //Delphi increments reference count by one
CallSomeFunctionThatUsesIBottle(intf); //Delph increments and then
decrements ref count
intf := nil; //Delphi decrements count by one - object destroyed
Bottle.FVolume := 327; //Trying to access freed object. Acess
violation.
end;
|
i guess it would have been clearer if i made my original post with these 5
lines of code. How do YOU get around this problem?
If i have a gigantic object, and i want to simply pass an interface to a
very limited view of that object: how do i maintain the object as a TObject,
and pass around interfaces on that object, without running into reference
counting pitfalls?
i could try to override ObjAddRef/ObjRelease and prevent reference counting.
i'm just worried that there might be a situation i haven't thought of where
that would cause problems. i suppose if someone could tell me straight out
that:
"As long as you can guarantee that you will maintain the object lifetime
through your object reference, you will have no need for reference counted
object lifetime, and not only can you, but you MUST disable the internal
object reference counting lifetime mechanism. You just have to guarantee
that all reference counts really are gone before you .Free the object.
Alternativly, you could maintain a TObject reference, and simultaneously
maintain an IUnknown reference. Then you're free to reference the object as
much as you want using TObject, and then when you want to get destroy the
object, nil the object and the interface references. The reference count
finally decrementing to zero will destroy the object for you."
That entire paragraph seems reasonable, especially part#2. What does
everyone/anyone else think? |
|
| Back to top |
|
 |
Patrick Kursawe Guest
|
Posted: Fri Aug 25, 2006 8:12 am Post subject: Re: How to maintain interface based objects |
|
|
Ian Boyd wrote:
| Quote: | Consider an interface:
IBottle = interface(IUnknown)
function Get_Capacity: Long; safecall;
function Get_CurrentVolume: Long; safecall;
property Capacity: Long read Get_Capacity;
property CurrentVolume: Long read Get_CurrentVolume;
end;
And the object that implements it:
TBottle = class(TComObject)
[...]
How do i update FCapacity and FVolume of the bottle?
You cannot override the constructor of a TComObject, you have to put any
initialization code in Initialize, but you still aren't allowed to pass
parameters to Initialize.
[...] |
If you only need to set parameters on initialization and don't directly
create instances of this object from COM clients but have methods of other
objects which return IBottle there's a very easy way out:
Don't inherit from TComObject but from TAutoIntfObject.
In other cases, just introduce an interface that inherits from IBottle but
which is not in the type library.
IBottleExt = interface(IBottle)
[...]
and put everything there you need from within your program.
HTH,
Patrick |
|
| Back to top |
|
 |
Ian Boyd Guest
|
Posted: Fri Aug 25, 2006 6:15 pm Post subject: Re: How to maintain interface based objects |
|
|
| Quote: | If you only need to set parameters on initialization and don't directly
create instances of this object from COM clients but have methods of other
objects which return IBottle there's a very easy way out:
Don't inherit from TComObject but from TAutoIntfObject.
|
What does TAutoIntfObject get me? It's still reference counted.
| Quote: | In other cases, just introduce an interface that inherits from IBottle but
which is not in the type library.
IBottleExt = interface(IBottle)
[...]
and put everything there you need from within your program.
|
Ugh. That's not the answer i wanted to hear. i wanted the easy way out. |
|
| Back to top |
|
 |
John Carlyle-Clarke Guest
|
Posted: Fri Aug 25, 2006 8:27 pm Post subject: Re: How to maintain interface based objects |
|
|
"Ian Boyd" <ian.borlandnews010 (AT) avatopia (DOT) com> wrote in
news:44ee0197$1 (AT) newsgroups (DOT) borland.com:
| Quote: | I'm assuming that you (Ian) _are_ talking about automation
objects here because you mention automation compatibility. It
goes without saying that interfaces themselves as a language
feature do not depend on COM _or_ automation.
Yes. However in this case, my interfaces are available from
classes exported from a Dll. And the standard way of creating
objects that are implemented inside a DLL is to use COM. What's
more, the person calling COM's
LoadLibrary+GetProcAddress+DllGetClassObject are in an apartment
incompatible with the interfaces exported by the classes in the
dll; and so COM is now trying to marshall the interface across
apartment boundaries; requiring that i make a type library and use
automatically marshallable data types.
|
I guess in theory you can do custom marshalling, implementing
IMarshall, but I've never tried it :)
Do you need to access your private interface from the host EXE, or just
within the DLL? |
|
| Back to top |
|
 |
Ian Boyd Guest
|
Posted: Fri Aug 25, 2006 10:40 pm Post subject: Re: How to maintain interface based objects |
|
|
| Quote: | I guess in theory you can do custom marshalling, implementing
IMarshall, but I've never tried it
|
Never gonna write custom marshallers. That's why if my TObject has some
interface, i want the interface to be defined in a type library with
automatable types.
| Quote: | Do you need to access your private interface from the host EXE, or just
within the DLL?
|
The problem is that i have a full object inside my host, but i need to pass
an interface containing 3 methods to the object in a dll.
TMyJunk = class(TObject, IMyJunk)
public
Socket: TWinSock;
CurrentUserGUID: TGUID;
dc: HDC;
PreviousMouseCursor: TCursor;
CurrentUsername: string;
procedure Assign(Source: TMyObject);
procedure WriteToSocket(s: string); overload;
procedure WriteToSocket(s: WideString); overload;
procedure WriteToSocket(guid: TGUID); overload;
procedure WriteToSocket(dc: HDC); overload;
function GetCurrentDatabaseConnection: TADOConnection;
function EtcEtcYouGetTheIdea: Boolean;
functon ALotOfNonAutomatableThings: Boolean;
{ IMyJunk }
procedure Write(Value: WideString); safecall;
procedure BinaryWrite(Value: OleVariant); safecall; //variant array of
bytes
function GetCurrentUsername: WideString;
end;
i need to pass to the DLL an IMyJunk interface. The three methods in IMyJunk
is all that the interface exposes, and they are all read only things;
However, the host application needs to maintain and update some of these
values, such as "CurrentUsername"
Imagine at some early point i create a TMyJunk object, and save it:
var
junk: TMyJunk;
begin
junk := TMyJunk.Create;
FJunkList.Add(junk); //add to a TList
end;
and now later i create an object exposed from a dll:
var
c: ISomeOtherObject;
junk: TMyJunk;
begin
c := CreateComObject('BorlandVisualBlindBling.PeanutMandM') as
ISomeOtherObject;
//Give c some junk
junk := FJustList.Items[0] as TMyJunk;
c.AdviseJunk(junk as IMyJunk); //junk is freed out from beneath me due
to reference counting
//Store C around in my list of things
FSomeOtherObjects.Add(c); //add to a TInterfaceList
end;
Now, let's go back and change junk's currentUsename
var
junk: TMyJunk;
begin
junk := FJunkList.Items[0] as TMyJunk; //bad pointer
junk.CurrentUsername := 'Ian'; //junk is freed, access violation
end;
And finally, cleanup
var
c: ISomeOtherObject;
junk: TMyJunk;
begin
//remove some other object out of the list
c := FSomeOtherObjects.Items[0] as ISomeOtherObject;
FSomeOtherObjects.Delete(0);
//Make some other object let go of it's held reference to My Junk
c.LetGoOfYourReferenceToIMyJunk;
//Now free junk
junk := FJunkList.Items[0] as TMyJunk; //bad pointer
FJunkList.Delete(0);
junk.Free; //freeing already freed object, access violation
end;
Discuss. |
|
| Back to top |
|
 |
Patrick Kursawe Guest
|
Posted: Mon Aug 28, 2006 6:04 pm Post subject: Re: How to maintain interface based objects |
|
|
Ian Boyd wrote:
| Quote: | If you only need to set parameters on initialization and don't directly
create instances of this object from COM clients but have methods of other
objects which return IBottle there's a very easy way out:
Don't inherit from TComObject but from TAutoIntfObject.
What does TAutoIntfObject get me? It's still reference counted.
|
It gets you overridable constructors which all kinds of arguments you need.
| Quote: | In other cases, just introduce an interface that inherits from IBottle but
which is not in the type library.
IBottleExt = interface(IBottle)
[...]
and put everything there you need from within your program.
Ugh. That's not the answer i wanted to hear. i wanted the easy way out.
|
Well, I think this _is_ an easy way out. Especially if you have something
like function GetImpl: TBottle; in your IBottleExt for the case you want to
mess around with your bare object and know what you're doing.
YMMV.
Patrick |
|
| Back to top |
|
 |
Ian Boyd Guest
|
Posted: Mon Aug 28, 2006 6:24 pm Post subject: Re: How to maintain interface based objects |
|
|
| Quote: | What does TAutoIntfObject get me? It's still reference counted.
It gets you overridable constructors which all kinds of arguments you
need.
|
But i can't talk to those member variables after construction.
i've created an unreference counted IUnknown implementing class.
type
TUncountedObject = class(TObject, IUnknown)
protected
{ IUnknown }
function QueryInterface(const IID: TGUID; out Obj): HResult;
stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
{ TUncountedObject }
function TUncountedObject.QueryInterface(const IID: TGUID;
out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := S_OK
else
Result := E_NOINTERFACE;
end;
function TUncountedObject._AddRef: Integer;
begin
Result := 2;
end;
function TUncountedObject._Release: Integer;
begin
Result := 1;
end;
Now i can create
type
TBottle = class(TUncountedObject, IBottle)
protected
function Get_Capacity: Long; safecall;
function Get_CurrentVolume: Long; safecall;
public
property Capacity: Long read Get_Capacity;
property CurrentVolume: Long read Get_CurrentVolume;
end;
So now as long as IBottle is declared in a type library registered on the
system, COM will marshall it for me, and i don't have to worry about mixing
object references and interface references causeing premature
freeactulation. |
|
| Back to top |
|
 |
Patrick Kursawe Guest
|
Posted: Tue Aug 29, 2006 8:12 am Post subject: Re: How to maintain interface based objects |
|
|
Ian Boyd wrote:
[...]
| Quote: | function TUncountedObject._AddRef: Integer;
begin
Result := 2;
end;
function TUncountedObject._Release: Integer;
begin
Result := 1;
end;
|
Maybe it's just a cosmetic issue, but the usual thing here would be
returning -1.
[...]
| Quote: | So now as long as IBottle is declared in a type library registered on the
system, COM will marshall it for me, and i don't have to worry about mixing
object references and interface references causeing premature
freeactulation.
|
Great. So you just have to worry about freeing your object while there's
still an interface reference to it somewhere else :-)
Bye, Patrick |
|
| Back to top |
|
 |
Ian Boyd Guest
|
Posted: Thu Aug 31, 2006 12:46 am Post subject: Re: How to maintain interface based objects |
|
|
| Quote: | function TUncountedObject._Release: Integer;
begin
Result := 1;
end;
Maybe it's just a cosmetic issue, but the usual thing here would be
returning -1.
|
No, release is supposed to return the current reference count. i want anyone
who calls Relese to think there are still outstanding references.
| Quote: | Great. So you just have to worry about freeing your object while there's
still an interface reference to it somewhere else
|
Probably will never happen  |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|