Interfaces are not tied to COM specifically. You can use interfaces in
Delphi without touching COM at all. COM happens to use interfaces, but they
are a feature of the Delphi Pascal language itself and thus can be used
as-is. For example:
type
IDoSomething = interface
// a GUID of your own choosing, or use Ctrl++Shift+G in the code
editor
['{########-####-####-####-############}']
procedure DoSomething;
end;
TMyClass = class(TMyBase)
end;
TMyOtherClass = class(TMyBase, IDoSomething)
public
procedure DoSomething;
end;
procedure DoIt;
var
Base: TMyBase;
Intf: IDoSomething;
begin
//...
if Supports(Base, IDoSomething, Intf) then
Intf.DoSomething;
//...
end;
Hi,
If I could pick up again the above point from the thread on abstract
methods, it seems that options for avoiding the problem of "abstract error"
stemming from unimplemented abstract methods include:
Option 1. Use a base class with abstract methods to specify the
functionality of inherited classes. However, any leaf class descending from
the base class (i.e. from which no further subclasses descend) must have all
the methods in its VMT, including any inherited abstract methods,
implemented. This is something that the Delphi compiler could and should
check when a class is instantiated, but doesn't. The compiler should
generate an error if the program code attempts to instantiate a class
containing or inheriting any unimplemented abstract methods. With such a
discipline, a generic object may be queried against the base class to
determine whether or not it implements (directly or indirectly) the base
class abstract methods.
Option 2. Use interfaces instead of abstract methods to define a segment of
functionality to be possessed by a subset of descendent classes. Then the
"Supports" function or the "IUnknown.QueryInterface" method may be used to
determine whether or not a generic object descending from a higher level
class implements a given interface.
I have some questions about the approach using interfaces.
1. I understand that any interface implicitly descends from IUnknown, in the
same way that any class descends from TObject. Therefore in the above
example any class implementing interface IDoSomething should also implement
the QueryInterface, _AddRef, _Release methods of IUnknown, at least
somewhere along its inheritance path. Hence the class TMyBase in the example
cannot simply descend directly from TObject, but must descend from
TInterfacedObject. Is this correct, or will the code work without IUnknown
and TInterfacedObject?
2. If it is true that in order to use interfaces to define selective
functionality an object/class *must* implement the IUnknown interface
somewhere along its inheritance path, then we may determine whether or not a
generic object descending from base class TMyBase implements a given
interface using the (inherited) QueryInterface method, as in:
if Base.QueryInterface(IDoSomething, Intf) then
Intf.DoSomething;
Is this correct?
3. The on-line help states that objects may be queried for compatibility
with a given interface using the "as" operator, but apparently the object
*must* implement the interface in the "as" expression, or else an exception
is thrown. So this is not really a practical technique for querying whether
an object/class supports an interface. A more effective technique would be
to use the "is" operator (or perhaps a new operator like "supports"), as in
the following example:
if Base is IDoSomething then {or: if Base supports IDoSomething then}
begin
Intf:= Base as IDoSomething; {Cast object as required interface}
Intf.DoSomething; {Now use method prescribed by
interface}
end;
Alternatively:
Intf:= Base as IDoSomething; {No exception must occur here if Base does
not support interface!}
if Intf<>nil then
Intf.DoSomething;
When using either syntax an exception must not be thrown if the class or
object "Base" does not acttually implement any interface whatsoever- the
"as"/"supports" expression should simply return False or nil.
Either syntaxs would make the code more readable. It would be easy for the
compiler to translate the expression "Base supports IDoSomething" into the
function call "Supports(Base, IDoSomething, Intf). So what exactly is the
situation regarding the generation of exceptions when the "as" operator is
used for interface querying?
Any guidance on these questions would be appreciated.
Enquiring Mind.
> 1. I understand that any interface implicitly descends from IUnknown, in the
> same way that any class descends from TObject. Therefore in the above
> example any class implementing interface IDoSomething should also implement
> the QueryInterface, _AddRef, _Release methods of IUnknown, at least
> somewhere along its inheritance path. Hence the class TMyBase in the example
> cannot simply descend directly from TObject, but must descend from
> TInterfacedObject. Is this correct, or will the code work without IUnknown
> and TInterfacedObject?
You can descend directly from TObject, you will have to implement the
methods by yourself though. TInterfacedObject does this work for you,
but it also makes the the object reference counted, sometimes you want
this , sometimes not. The implementation of the methods is not very
difficult:
function TTest._AddRef: Integer;
begin
Result := -1; // no reference counting supported
end;
function TTest._Release: Integer;
begin
Result := -1; // no reference counting supported
end;
function TTest.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := S_OK
else
Result := E_NOINTERFACE;
end;
> 2. If it is true that in order to use interfaces to define selective
> functionality an object/class *must* implement the IUnknown interface
> somewhere along its inheritance path, then we may determine whether or not a
> generic object descending from base class TMyBase implements a given
> interface using the (inherited) QueryInterface method, as in:
>
> if Base.QueryInterface(IDoSomething, Intf) then
> Intf.DoSomething;
>
> Is this correct?
The compiler will check if the methods are all implemented in the class
you declare it.
TTest = class(TObject, ITest)
This means TTest implements the methods of ITest, TTestParent does not
necessarily. When you have an interfaced object, you can check if a
given interface (ITest) is supported, this will call the QueryInterface
method (the same does the as operator).
if (pSomeInterfacedObject.QueryInterface(ITest, pTest) = 0) then
pTest.DoSomething;
> 3. The on-line help states that objects may be queried for compatibility
> with a given interface using the "as" operator, but apparently the object
> *must* implement the interface in the "as" expression, or else an exception
> is thrown. So this is not really a practical technique for querying whether
> an object/class supports an interface. A more effective technique would be
> to use the "is" operator (or perhaps a new operator like "supports"), as in
> the following example:
>
> if Base is IDoSomething then {or: if Base supports IDoSomething then}
> begin
> Intf:= Base as IDoSomething; {Cast object as required interface}
> Intf.DoSomething; {Now use method prescribed by
> interface}
> end;
>
> Alternatively:
>
> Intf:= Base as IDoSomething; {No exception must occur here if Base does
> not support interface!}
> if Intf<>nil then
> Intf.DoSomething;
> ...
I also would welcome an as operator which doesn't throw an exception,
but returns nil, if the interface is not supported, but it's the way it
is. Calling QueryInterface or Supports is the way to go.
http://www.martinstoeckli.ch/delphi/delphi.html#InterfaceWithoutCom
best regards:
Martin
It can, and does.
> The compiler should
> generate an error if the program code attempts to instantiate a class
> containing or inheriting any unimplemented abstract methods.
Instead it generates a warning.
> With such a
> discipline, a generic object may be queried against the base class to
> determine whether or not it implements (directly or indirectly) the base
> class abstract methods.
With such a discipline, any object you have is guaranteed to implement
all abstract methods because it would be impossible to get an object
that didn't. There's little point in asking a question when the answer
is always yes.
> Option 2. Use interfaces instead of abstract methods to define a segment of
> functionality to be possessed by a subset of descendent classes. Then the
> "Supports" function or the "IUnknown.QueryInterface" method may be used to
> determine whether or not a generic object descending from a higher level
> class implements a given interface.
>
> I have some questions about the approach using interfaces.
>
> 1. I understand that any interface implicitly descends from IUnknown, in the
> same way that any class descends from TObject. Therefore in the above
> example any class implementing interface IDoSomething should also implement
> the QueryInterface, _AddRef, _Release methods of IUnknown, at least
> somewhere along its inheritance path.
Correct. The typical way of getting those implementations is to descend
from TInterfacedObject.
> Hence the class TMyBase in the example
> cannot simply descend directly from TObject, but must descend from
> TInterfacedObject. Is this correct, or will the code work without IUnknown
> and TInterfacedObject?
It will not work without IUnknown. It can work just fine without
TInterfacedObject. You're free to implement those three methods on your
own, if you want. You're likely to copy the implementation of
QueryInterface straight from TInterfacedObject. You might forgo counting
references in _AddRef and _Release, but remember that they're still
going to get called all the time, even if you don't make them count
references.
> 2. If it is true that in order to use interfaces to define selective
> functionality an object/class *must* implement the IUnknown interface
> somewhere along its inheritance path, then we may determine whether or not a
> generic object descending from base class TMyBase implements a given
> interface using the (inherited) QueryInterface method, as in:
>
> if Base.QueryInterface(IDoSomething, Intf) then
> Intf.DoSomething;
>
> Is this correct?
Yes. That's what the Supports function does.
But QueryInterface returns an HResult, not a Boolean. On success, it
returns S_OK. On failure, it returns a COM error code. You can turn the
error code into an exception with OleCheck, or you can use the Succeeded
function to turn the value into a Boolean (True for success, False for
error).
> 3. The on-line help states that objects may be queried for compatibility
> with a given interface using the "as" operator, but apparently the object
> *must* implement the interface in the "as" expression, or else an exception
> is thrown.
Right. I wouldn't really use the term "query" to describe it. It's
really a checked type cast, just like using "as" to get a different
class type from an object.
> So this is not really a practical technique for querying whether
> an object/class supports an interface. A more effective technique would be
> to use the "is" operator (or perhaps a new operator like "supports"), as in
> the following example:
>
> if Base is IDoSomething then {or: if Base supports IDoSomething then}
> begin
> Intf:= Base as IDoSomething; {Cast object as required interface}
> Intf.DoSomething; {Now use method prescribed by
> interface}
> end;
Correct. But there's really no need for a new operator since it can
already be done in library code. See SysUtils.Supports.
> Either syntaxs would make the code more readable. It would be easy for the
> compiler to translate the expression "Base supports IDoSomething" into the
> function call "Supports(Base, IDoSomething, Intf).
Easy, but also rather pointless. You're proposing a new reserved word
that overlaps with a well established function name and is nothing but
syntactic sugar for something that isn't really all that different from
what the new syntax would be.
> So what exactly is the
> situation regarding the generation of exceptions when the "as" operator is
> used for interface querying?
I think you've already explained everything there is. If the cast works,
you get the new interface reference. If the cast fails, you get an
exception.
--
Rob
Martin, Thanks for your quick response! Regarding the question of whether
it is better to use a base class with abstract methods or an interface to
prescribe the functionality required of a family of classes, the COM-free
interface option is certainly the more flexible option because it avoids the
no-multiple-inheritance constraint, but on the downside it carries with it
additional complexity and possibly overhead, and inelegant syntax. IMO it
is probably best avoided when it's possible to get the job done simply using
a base class specifying abstract methods.
BTW, I had a look at your web site, and saw that you have posted some good
material there!
Best regards,
Enquiring Mind
Thanks for this comment!
--
Martin Stoeckli
http://www.martinstoeckli.ch/delphi
> With such a discipline, any object you have is guaranteed to implement all
> abstract methods because it would be impossible to get an object that
> didn't. There's little point in asking a question when the answer is
> always yes.
>
The point I am making is that if the class specifying the abstract methods
is not the base class at the root of the class hierarchy, but some
intermediate class introduced just to prescribe an interface for a subset of
classes, and a generic object is declared as being a member of the root base
class, then one may use the "is" operator with the intermediate class to
determine whether or not the object supports the method.
> Yes. That's what the Supports function does.
>
> But QueryInterface returns an HResult, not a Boolean. On success, it
> returns S_OK. On failure, it returns a COM error code. You can turn the
> error code into an exception with OleCheck, or you can use the Succeeded
> function to turn the value into a Boolean (True for success, False for
> error).
>
Although the Supports function and the QueryInterface method both serve the
same purpose, I presume that there are differences in how they behave. The
most obvious seems to be that the QueryInterface method can only be applied
to an object that is a TInterfacedObject or that supports IUnknown by some
other means. Thus if I have a collection of objects descending from a common
root base class, but only a subset implements some specified interface, then
I cannot apply QueryInterface to each and every object in the collection,
because an exception would be generated if the object to which it's applied
is not an interfaced object. On the other hand, I would imagine that
Supports function will not throw an exception if it's passed a
non-interfaced object or a nil interface reference, but would check the
input parameters and simply return a nil interface as the result if the
parameters do not permit otherwise. Or will the same exception still
surfacer by some other route?
>
> Correct. But there's really no need for a new operator since it can
> already be done in library code. See SysUtils.Supports.
> Easy, but also rather pointless. You're proposing a new reserved word
> that overlaps with a well established function name and is nothing but
> syntactic sugar for something that isn't really all that different from
> what the new syntax would be.
>
IMO, it would make the language cleaner, more consistent and more readable
to be able to use the "is" operator with either a class or an interface type
reference on the right hand side, where "is" means "is compatible with".
Clearly the compiler could easily identify the type of the right hand side
identifier, and handle the case accordingly. The use of a SysUtils function
instead of an operator does not seem to me to be in keeping with a unified
programming language. It's rather like having to use a function Sum(X, Y:
double): double to perform an addition of a standard data type rather than
simply using the + operator. Put another way, each Pascal data type has a
set of operators that can be used to manipulate objects of that data type.
The only standard data type that seems deficient in this respect is the
interface type, because it is compatible with only one operator, "as", which
is not sufficient on its own for performing all the operations that are
necessary for interfaces.
Best regards,
Enquiring Mind
Right. There are a few Supports overloads. If you call the one that
takes an IUnknown, then it calls QueryInterface. If you call the TObject
version, then you get the Supports function that checks the object's
interface table as exposed by GetInteface. Nearly any Delphi object that
implements IUnknown.QueryInterface does little more than call
TObject.GetInterface, so the differences between the Supports overloads
is really just the input types.
No overload of Supports cares about TInterfacedObject.
> Thus if I have a collection of objects descending from a common
> root base class, but only a subset implements some specified interface, then
> I cannot apply QueryInterface to each and every object in the collection,
> because an exception would be generated if the object to which it's applied
> is not an interfaced object.
If the compiler lets you call QueryInterface on the objects, then I
wouldn't expect any exceptions. I've never seen any implementation of
that method that raises exceptions.
If you used the "as" operator, then you might get exceptions. If you
don't want exceptions, then don't use that operator. Use QueryInterface
or Supports instead.
> On the other hand, I would imagine that
> Supports function will not throw an exception if it's passed a
> non-interfaced object or a nil interface reference, but would check the
> input parameters and simply return a nil interface as the result if the
> parameters do not permit otherwise. Or will the same exception still
> surfacer by some other route?
The only time Supports would raise an exception on a nil input is the
TClass overload. The TObject and IUnknown overloads return False on nil
inputs. (And you don't have to _imagine_ what Supports would do -- you
have the source code in SysUtils.pas. The implementations are very short.)
When the input object does not support the requested interface, Supports
does not return any interface reference at all. The interface-reference
output parameter is only defined when Supports returns True. When
Supports returns False, there's no interface to look at.
--
Rob
Thanks for detailed explanation - most enlightening.
Just to return to the question of abstract classes versus interfaces, I
created a little application to test the use of interfaces. It lets the user
to create a series of interfaced objects chosen from a variety of interfaced
classes. The classes and interfaces are defined as follows:
type
TObjectBase= class(TInterfacedObject)
private
FID: string;
FDatetimeCreated: TDatetime;
FObjectBaseCount: integer;
public
constructor Create;
property ID: string read FID;
property Datetimecreated: TDatetime read FDatetimecreated;
property ObjectBaseCount: integer read FObjectBaseCount;
end;
IInterface0= interface
['{C366AE51-3826-42AF-A8FA-B567D2E3C7F2}']
function CreatedByControl: string; {Returns control used to issue
command to create
interfaced object}
end;
IInterface1= interface
['{EE3ED09E-2D9F-4309-BA21-02286DAE7947}']
function ObjectInterface1Count: integer; {Returns total number of
objects supporting
IInterface1 at time of creation}
end;
IInterface2= interface
['{800599D6-F195-491E-9915-AAE4B87852D0}']
function ObjectName: string;
end;
TObjectInterface0= class(TObjectBase, IInterface0)
private
FControlName: string;
public
constructor Create(ControlName: string);
function CreatedByControl: string;
end;
TObjectInterface1= class(TObjectBase, IInterface1)
private
FObjectInterface1Count: integer; {No of TObjectInterface1
objects in existence at time of creation}
public
constructor Create;
function ObjectInterface1Count: integer;
end;
TObjectInterface1And2= class(TObjectInterface1, IInterface2)
private
FName: string;
public
constructor Create(Name: string);
function ObjectName: string;
end;
TObjectBaseArray= array of TObjectBase;
TInterfaceArray= array of IUnknown;
TObjectBaseClass= class of TObjectBase;
TObjectBaseCollection= class
private
FObjects: TObjectBaseArray;
function GetObjectCount: integer;
function GetInterfacedObjectByIndex(Index: integer): TObjectBase;
function GetObjectCountByClass(ObjectBaseClass: TObjectBaseClass):
integer;
function GetObjectCountByInterface(InterfaceID: TGUID): integer;
public
destructor Destroy; override;
procedure AddObject(NewObject: TObjectBase);
property ObjectCount: integer read GetObjectCount;
property ObjectByIndex[Index: integer]: TObjectBase read
GetInterfacedObjectByIndex;
property ObjectCountByClass[ObjectClass: TObjectBaseClass]: integer read
GetObjectCountByClass;
property ObjectCountByInterface[InterfaceID: TGUID]: integer read
GetObjectCountByInterface;
end;
Now the objects and objects supporting given interfaces are displayed in
list boxes using the following code:
procedure TForm1.DataToControls;
var
i: integer;
ObjectI: TObjectBase;
LineI: string;
InterfaceI: IUnknown;
Interface0: IInterface0;
Interface1: IInterface1;
Interface2: IInterface2;
begin
{Display all objects in collection in ListBoxObjectBase:}
with ListBoxObjectBase do
begin
Clear;
with FObjectBaseCollection do
begin
for i:= 0 to ObjectCount-1 do
begin
ObjectI:= ObjectByIndex[i];
LineI:= Format('%d %s %s',[i, ObjectI.ClassName,
ObjectI.ID]);
Items.Add(LineI)
end;
end;
end;
{Display all interfaces sorted by type in corresponding list boxes:}
ListBoxInterface0.Clear;
ListBoxInterface1.Clear;
ListBoxInterface2.Clear;
with FObjectBaseCollection do
begin
for i:= 0 to ObjectCount-1 do
begin
ObjectI:= ObjectByIndex[i];
InterfaceI:= ObjectI as IUnknown;
if InterfaceI.QueryInterface(IInterface0, Interface0)= 0 then
begin
LineI:= Format('%d ID:%s Control: %s',[i, ObjectI.ID,
Interface0.CreatedByControl]);
ListBoxInterface0.Items.Add(LineI);
end;
if InterfaceI.QueryInterface(IInterface1, Interface1)= 0 then
begin
LineI:= Format('%d ID: %s Interface 1 count: %d',[i,
ObjectI.ID, Interface1.ObjectInterface1Count]);
ListBoxInterface1.Items.Add(LineI);
end;
if InterfaceI.QueryInterface(IInterface2, Interface2)= 0 then
begin
LineI:= Format('%d ID: %s Name: %s',[i, ObjectI.ID,
Interface2.ObjectName]);
ListBoxInterface2.Items.Add(LineI);
end
end;
end;
Invalidate;
end;
This throws up a problem with interfaces. The problem is this: the use of
a local Interface variable as a return variable in the QueryInterface method
call causes the object to be destroyed when the procedure is exited, due to
reference counting. To avoid the problem I had to add to
TObjectBaseCollection another member FInterfaces:
TObjectBaseCollection= class
private
FObjects: TObjectBaseArray;
FInterfaces: TInterfaceArray;
...
end
and maintain duplicate lists of the objects, the first as TObjectBase object
references, and the second as IUnknown interface references. The second
array is to prevent the reference count going to zero as long as a
TObjectBaseCollection object is still in existence. It appears that although
class TObjectBase descends from TInterfacedObject, and therefore by
definition is also a IUnknown interface, assigning an instance to array
FObjects is not good enough to cause its IUnknown reference count to be
incremented. This to my mind makes the use of interfaces instead of
abstract classes less than satisfactory! Any simpler ways of avoiding he
problem?
Regards,
Enquiring Mind
Because you are using object references and interface references.
If you use interfaces, use *only* interfaces.
Cheers,
Chris
If I dispense with the variable FObjects in the TObjectBaseCollection
class, and only use the FInterfaces array to manage references to the
interfaced objects created, I cannot refer to a member of an object that is
not also a member of the interfaces supported by the object. This is
presumably because although an interface reference may encapsulate a pointer
to the implementing object as well as to the interface method table, the
compiler has no way of knowing the class of the object, given that multiple
classes might be compatible with the interface. Thus an object reference may
be assigned to a compatible interface reference, but the converse does not
apply. For example, the assignment of the interface reference to a
compatible object reference in the following code is rejected by the
compiler:
type
TObjectBase= class(TInterfacedObject, IUnknown);
function TObjectBaseCollection.GetObjectCountByClass(
ObjectClass: TObjectBaseClass): integer;
{Returns number of objects in collection of class ObjectClass}
var
i: integer;
InterfaceI: IUnknown;
ObjectI: TObjectBase;
begin
Result:= 0;
for i:= 0 to High(FInterfaces) do
begin
{Retrieve i'th interface from array of base interfaces:}
InterfaceI:= FInterfaces[i];
{Retrieve reference to object associated with interface:}
{****}ObjectI:= InterfaceI; {*** Although class TObjectBase is
compatible with IUnknown, this assignment gets rejected by the compiler}
if ObjectI.Classtype= ObjectClass then
Inc(Result);
end;
end;
My conclusion is: Class TObjectBaseCollection that manages a collection of
interfaced objects must maintain object references to the interfaced
objects. However since this does not result in the reference counts of the
objects being incremented, it must maintain a separate list of interface
references to the same objects, just to ensure that the object reference
counts are at least 1. The two lists must be synchronised. For example, the
AddObject method is as follows:
procedure TObjectBaseCollection.AddObject(NewObject: TObjectBase);
var
Count: integer;
begin
Count:= Length(FObjects);
SetLength(FObjects, Count+1);
FObjects[Count]:= NewObject;
{At this point NewObject.RefCount is still 0}
SetLength(FInterfaces, Count+1);
FInterfaces[Count]:= NewObject as IUnknown;
{At this point NewObject.RefCount is 1}
end;
Regards,
EM
Correct. Mixing references to interfaces and the objects that implement
them is not recommended with Delphi's current implementation.
If you use interfaces, you must design interfaces which implement
all your functionality. You cannot drop back to the base classes
when you need to get at functionality which is not exposed by an interface.
Cheers,
Chris
> not also a member of the interfaces supported by the object. This is
> presumably because although an interface reference may encapsulate a
> pointer to the implementing object as well as to the interface method
> table, the compiler has no way of knowing the class of the object, given
> that multiple classes might be compatible with the interface.
On reflection this seems to be incorrect! Every method must have an
associated object pointer, and every object pointer must have a pointer to
class data such as Classtype, Classname, etc. Therefore given an interface
method, it should be possible to follow the pointer trail back to the class
of the implementing object. Hence the compiler, or at least the application
code, should be able to check, when an interface is assigned to an object,
whether or not the object reference is compatible with the interface
reference. Can anyone indicate what actually happens, and why the compiler
appears to prohibit an interface reference being assigned to an object
reference?
EM
As a matter of curiosity, is Delphi Win32 the primary programming language
used in your organisation?
Yes it is.
We've been using Delphi since V1, and before that, TP5, or possibly earlier.
We still have active code libraries (very low-level stuff) that have barely
changed since they were written in TP5.
cheers,
Chris
> We've been using Delphi since V1, and before that, TP5, or possibly
> earlier.
> We still have active code libraries (very low-level stuff) that have
> barely
> changed since they were written in TP5.
>
To someone that appreciates the merits of Pascal, that's good news! If the
availability of books on different programming languages in UK bookshops is
anything to go by, one might think that Pascal/Delphi is well on its way to
becoming an obsolete language, so low has its profile sunk. Fortunately, the
activity of the news groups would suggest otherwise!
Regards,
EM
> On reflection this seems to be incorrect! Every method must have an
> associated object pointer, and every object pointer must have a
> pointer to class data such as Classtype, Classname, etc. Therefore
> given an interface method, it should be possible to follow the
> pointer trail back to the class of the implementing object.
Not quite.
An interface reference (in Win32) is a pointer to an offset into the
VMT. With some serious hacking, you *can* get back to the object
reference, but it's not easy and could conceiveably break from one
version to the next.
Even if you "succeed" in doing this you'll break reference counting in
the process unless you're very careful.
--
Craig Stuntz [TeamB] · Vertex Systems Corp. · Columbus, OH
Delphi/InterBase Weblog : http://blogs.teamb.com/craigstuntz
Borland newsgroup denizen Sergio González has a new CD of
Irish music out, and it's good: http://tinyurl.com/7hgfr
Since an interface is, in fact, implemented by a class, that class knows
itself. The issue is that the *user* of an interface should not have direct
knowledge of the implementing class because an interface can be implemented
by *any* class. Thus dependence on the class an interface is acquired from
is a design issue that should be avoided - it defeats one of the main
purposes of using interfaces - decoupling of functionality from class
identity.
Getting the implementing object is actually easy, but is considered a hack
for the above reasons. Just define a base interface that provides a method
to return Self, e.g. you might define it as:
IBase = interface
function ClassImplementor: TObject;
end;
Then any base class inplementing this simply returns Self;
--
Wayne Niddery - Winwright, Inc. (www.winwright.ca)
if ObjectI is IInterface1 then
IInterface1(ObjectI).MethodX;
rather than
if Supports(ObjectI, IInterface1, Intf) then
Intf.MethodX;
In this way we would not run the risk of destroying objects because they
have been temporarily assigned to an interface variable just to access the
interface method table.
I note that C# supports the syntax if ObjectI is IInterface1.
Regards,
Enquiring Mind
> What I seem to be learning from this thread is that in Delphi it's
> not a good idea to mix objects and interfaces in the same OO
> framework.
In Win32 you shouldn't mix object and interface references unless
you're prepared to ensure that you don't mess up reference counting
yourself.
In .NET you can mix them freely.
> In this way we would not run the risk of destroying objects because
> they have been temporarily assigned to an interface variable just to
> access the interface method table.
No, you don't need a new syntax; you just need to make sure you handle
the reference count when you do this.
> I note that C# supports the syntax if ObjectI is IInterface1.
.NET is different. Even in Delphi.
--
Craig Stuntz [TeamB] · Vertex Systems Corp. · Columbus, OH
Delphi/InterBase Weblog : http://blogs.teamb.com/craigstuntz
Everything You Need to Know About InterBase Character Sets:
http://blogs.teamb.com/craigstuntz/articles/403.aspx
> Since an interface is, in fact, implemented by a class, that class knows
> itself. The issue is that the *user* of an interface should not have
> direct knowledge of the implementing class because an interface can be
> implemented by *any* class. Thus dependence on the class an interface is
> acquired from is a design issue that should be avoided - it defeats one of
> the main purposes of using interfaces - decoupling of functionality from
> class identity.
>
The problem is that when you have a collection of objects, when you need to
query an object for whether it supports an interface, so that you can call a
method of the interface, you need to assign the object to an interface
variable, and in so doing the object subsequently gets destroyed if the
interface variable is a local variable of a procedure.
> Getting the implementing object is actually easy, but is considered a hack
> for the above reasons. Just define a base interface that provides a method
> to return Self, e.g. you might define it as:
>
> IBase = interface
> function ClassImplementor: TObject;
> end;
>
> Then any base class inplementing this simply returns Self;
>
That's a useful trick - many thanks for the tip!
Regards,
EM
If you need that object outside of that procedure then it must be assigned
to some non-local variable or returned as a result of the method - and if
that is done, reference counting will prevent it from being destroyed. As
long as it is assigned to a variable defined as an interface type, this
should work fine.
There are significant differences between interfaces in COM and interfaces
in a class framework that make the use of Delphi interfaces ill-suited to
class frameworks. IMO these include:
1. In COM the purpose of an interface is to define a public interface to a
separately compiled module through which external client modules may use its
functionality. A client module would not need to access any members of the
class implementing the interface that are not exposed by an interface, for
the simple reason that they are not known to the compiler. In a class
framework, however, the purpose of an interface is to define some limited
portion of functionality that only a subset of classes needs to provide, as
a technique for overcoming the limitations of abstract classes and single
class inheritance, and to enable an object to be queried for whether or not
it possesses certain methods. In this case the compiler has full knowledge
of the classes implementing interfaces. The application will often need to
retrieve an object or class reference from an interface reference.
2. In COM there is usually only one object on the computer that exposes a
given interface, i.e. there's a one-to-one relationship between an interface
and an object, otherwise the CreateCOMObject method would not know which
implementing object to instantiate. In a class framework, however, there is
a one-to-many relationship between an interface and the classes that expose
the same interface.
3. In COM the interfaced module may be used by several clients concurrently,
therefore the lifetime of the interfaced object cannot be managed by a
individual client module - hence the need for reference counting. In a class
framework, however, there is only one client - the application - so there is
no need for reference counting. When interfaces are being used to define
portions of functionality possessed by a subset of classes, lifetime
management is best handled in the same way as for any other (non-interfaced)
object in the framework. It makes little sense to make the base interface
implement reference counting.
4. In COM, a reference to an interfaced object needs to be stored in a
variable of interface (or variant) type, for the simple reason that the
client program does not know the class of the object that implements the
methods in the interface. In a class framework, on the other hand, it's
usually more convenient and more logical to refer to objects in a collection
using object references, so that the collection may contain both interfaced
and non-interfaced objects. Moreover, given the one-to-many relationship
between interfaces and objects, it makes little sense to refer to the
members of the collection using interface references.
5. In COM an interface object needs a GUID, because interface identifiers do
not offer sufficient guarantee of uniqueness over multiple modules by
different developers. The GUID is language independent and operating system
specific. In a class framework, OTOH, any interface is uniquely identified
by its type identifier, and GUIDs are therefore unnecessary. However several
procedures and methods that take an interface as a parameter define the type
of the parameter as TGUID rather than by interface type identifier, which is
too COM-orientated.
6. In COM once an interfaced object has been created, the only querying
operations that are needed are switching between different interfaces
supported by the object. In such a situation it makes sense to describe the
"as" operator as a practical interface querying mechanism, as the on-line
help does. The client program knows at compile time what interfaces are
exposed by a given COM object resident in the system, so can be sure that an
exception will not be thrown when an interface reference is cast to another
interface known at compile time to be also supported . In a class framework,
OTOH, we need the ability to determine whether or not a given object in the
framework supports a given interface at all - hence the "as" operator is
inappropriate. The Supports and QueryInterface functions would be
satisfactory if they returned a Boolean value rather than an interface
reference. Querying code for interfaces then could be consistent with that
for classes:
class:
if ObjectI is TGraphical then
TGraphical(ObjectI).Plot(Canvas);
interfaced object:
if ObjectI is IGraphical then
IGraphical(ObjectI).Plot(Canvas);
7. The statement that "an interface can be implemented by *any* class" has
different meanings in the contexts of COM and of a class framework. In COM,
since there is only one object in the system providing the interface, if a
code module is replaced by another module exposing the same interface, it is
necessary that the interface does the same things. There can be no choice
for the client between different classes providing the same interface. In a
class framework, OTOH, different classes implementing the same interface may
well produce quite different results, making it sometimes necessary to know
which class is implementing the interface, so that a choice can be made.
IMO all that is needed to make Delphi interfaces suitable for class
frameworks are the following simple extensions:
1. Allow interface type identifiers in "is" statements to provide a better
querying mechanism.
2. Provide a mechanism for retrieving the class of an interfaced object from
the interface reference, when this is possible.
3. Allow an interface to be assigned to an object, and converted to the
class of the object, if the class of the interfaced object and the object
reference variable are compatible. This can be determined by the compiler in
the case of a class framework, by climbing the pointer chain. Clearly a
attempt to do this when the class of the interface is not known, as in COM,
would generate an exception.
With these minor extensions, we would be able to freely mix objects and
interfaces in a class framework using Win32 Delphi, and feel less of an urge
to move to languages like C#!
I am aware that one can avoid some of the problems that I mentioned above by
not using the methods in IInterface, and introducing new methods that
behave in the way required by a class framework (e.g. no reference counting,
initialisation of the reference count to a non zero value, a different
querying function that does not assign an interface reference to a variable,
etc). But a more unified approach would be preferable.
Regards,
EM
> Delphi interfaces do not seem to me to be well-suited to general
> programming of class frameworks. If they were, we would see greater
> use of interfaces in class frameworks like the VCL.
You do see it (e.g., IProviderSupport, IXMLDocument), but it's
limited. IMHO, it's mostly due to lifetime management issues. Using an
interface with VCL components more or less requires mixing object and
interface references. That's "hard," hence it's not done much. I'd like
to see more interfaces myself since I know how to do this, but part of
me dreads supporting it on the newsgroups. :)
--
Craig Stuntz [TeamB] · Vertex Systems Corp. · Columbus, OH
Delphi/InterBase Weblog : http://blogs.teamb.com/craigstuntz
Want to help make Delphi and InterBase better? Use QC!
http://qc.borland.com -- Vote for important issues
Wrong. CreateCOMObject uses the ID of a CoClass, not that of an interfaces.
There can of course be different implementations in different CoClasses
that implement the same interface.
> 6. [...]
> The Supports and QueryInterface functions would be
> satisfactory if they returned a Boolean value rather than an interface
> reference. Querying code for interfaces then could be consistent with that
> for classes:
> class:
> if ObjectI is TGraphical then
> TGraphical(ObjectI).Plot(Canvas);
>
> interfaced object:
> if ObjectI is IGraphical then
> IGraphical(ObjectI).Plot(Canvas);
Sorry, but what is the big difference between "if ObjectI is IGraphical"
and "if supports(ObjectI,IGraphical)"?
IGraphical(ObjectI) is still no good idea, of course.
> 7. The statement that "an interface can be implemented by *any* class" has
> different meanings in the contexts of COM and of a class framework. In COM,
> since there is only one object in the system providing the interface, if a
> code module is replaced by another module exposing the same interface, it is
> necessary that the interface does the same things. There can be no choice
> for the client between different classes providing the same interface. In a
> class framework, OTOH, different classes implementing the same interface may
> well produce quite different results, making it sometimes necessary to know
> which class is implementing the interface, so that a choice can be made.
As I stated above, this is not correct. There can be (and there sometimes
are) multiple COM objects providing the same interface.
Bye, Patrick
The VCL was designed long before interfaces were introduced to the language,
thus to make full use of interfaces in the VCL would require quite a lot of
rewriting. Despite that, if you look, you will see that interfaces have been
added in various areas of the VCL and have definitely been used in code
developed after the introduction of interfaces (e.g. its use in
DataSnap/MIDAS components).
> A client module would not need to access any members of the class
> implementing the interface that are not exposed by an interface, for the
> simple reason that they are not known to the compiler.
Deliberately so, the user of the interface should not need to know the
identity of the implementing class. It is this that provides the ability,
for example, to construct proxy classes on the client side to allow access
to remote server objects.
> In a class framework, however, the purpose of an interface is to define
> some limited portion of functionality that only a subset of classes needs
> to provide, as a technique for overcoming the limitations of abstract
> classes and single class inheritance, and to enable an object to be
> queried for whether or not it possesses certain methods. In this case the
> compiler has full knowledge of the classes implementing interfaces. The
> application will often need to retrieve an object or class reference from
> an interface reference.
The fact that the compiler might have knowledge, within a single
application, of an implementation class does not mean application code
should be designed to depend on that. This is very much a design issue, not
a technical one. If one wants to use interfaces to their full advantage then
one should design all needed functionality around interfaces and not allow
an application to be dependent on the classes that implement them.
> 2. In COM there is usually only one object on the computer that exposes a
> given interface,
As Patrick points out, this is incorrect.
> In a class framework, however, there is only one client - the
> application - so there is no need for reference counting.
This doesn't follow. Even in a standalone application, there can be
concurrent use of an interface and reference counting/automatic destruction
can be very useful.
> When interfaces are being used to define portions of functionality
> possessed by a subset of classes, lifetime management is best handled in
> the same way as for any other (non-interfaced) object in the framework. It
> makes little sense to make the base interface implement reference
> counting.
Reference counting is an advantage depending on what your design needs are,
it is neither good or bad. In addition, Delphi interfaces are *not*
reference counted, that has to be implemented by a class. As a convenience,
Delphi offers TInterfacedObject to be used as a base class for when you want
normal "COM-style" reference counting. You can implement interfaces in any
other object though (i..e. your own base class) and implement the 3
required functions anyway you want.
> In a class framework, on the other hand, it's usually more convenient and
> more logical to refer to objects in a collection using object references,
> so that the collection may contain both interfaced and non-interfaced
> objects.
That is, again, a design issue. If one is going to use interfaces then *all*
functionality to be surfaced to users, via a given implementation class,
should be implemented in interfaces, thus not ever needing access to the
implementing class.
> Moreover, given the one-to-many relationship between interfaces and
> objects, it makes little sense to refer to the members of the collection
> using interface references.
It makes perfect sense - that is the purpose of using interfaces in the
first place. A collection can contain classes of different types but still
be accessed identically via one or more interfaces without the application
code having to know or care about the class identity.
> In a class framework, OTOH, any interface is uniquely identified by its
> type identifier, and GUIDs are therefore unnecessary.
Identifier clashes can happen with interfaces as easily as it can with class
names, but because of GUIDs, it can be *better* handled.
> However several procedures and methods that take an interface as a
> parameter define the type of the parameter as TGUID rather than by
> interface type identifier, which is too COM-orientated.
This makes no sense. The *appearance* of being like COM does not make it
"COM-oriented". It does make it compatible and that was by design, but even
withour that requirement, GUIDs are a perfectly reasonable approach to
provide unique identity. Objections based on it "looking like" COM are,
IMHO, completely irrational.
> In a class framework, OTOH, we need the ability to determine whether or
> not a given object in the framework supports a given interface at all -
> hence the "as" operator is inappropriate. The Supports and QueryInterface
> functions would be satisfactory if they returned a Boolean value rather
> than an interface reference. Querying code for interfaces then could be
> consistent with that for classes:
> class:
> if ObjectI is TGraphical then
> TGraphical(ObjectI).Plot(Canvas);
Again this is based on your belief that you should want or need access to
the underlying object, and again this is a design issue. Your exanple above
takes *no advantage* of interfaces at all and is 100% class-based. If you
wish to write code like this then what advantage are you expecting to get
from mixing in interfaces?
Have you actually looked at the Supports function? It returns a boolean and
you can pass it with just two parameters, an existing interface reference
and the interface you wish to query, meaning it does not return a reference:
Therefore the following:
> interfaced object:
> if ObjectI is IGraphical then
> IGraphical(ObjectI).Plot(Canvas);
... can be written as:
if Supports(ObjectI, IGraphical) then
(ObjectI as IGraphical).Plot(Canvas);
However, there is also no problem with:
if Supports(ObjectI, IGraphical, myintf) then
myintf.Plot(Canvas); // type myintf: IGraphical;
In both cases, reference counting, if implemented, will work correctly. The
first merely avoids the need of a reference variable.
> 7. The statement that "an interface can be implemented by *any* class" has
> different meanings in the contexts of COM and of a class framework. In
> COM, since there is only one object in the system providing the interface,
As indicated this is incorrect.
> OTOH, different classes implementing the same interface may well produce
> quite different results, making it sometimes necessary to know which class
> is implementing the interface, so that a choice can be made.
Another design issue. At some point, the application will be creating
interface references, whether by directly instantiating a class (assigning
to an interface reference, not a class reference) or by using a factory
method provided by the implementing library. So there is no valid reason not
to know the purpose of the interface one is acquiring and expoecting that it
will do the appropriate work.
> 1. Allow interface type identifiers in "is" statements to provide a better
> querying mechanism.
A minor issue since Supports() does what you ask. But I have no objection to
"is" being extended to allow this.
> 2. Provide a mechanism for retrieving the class of an interfaced object
> from the interface reference, when this is possible.
This would be a mistake. In .Net this ability is not necessary, in Delphi
Win32, it is not *wanted*. Partly this is, admitedly, due to the problems it
causes due to the necessary implementation of interfaces contrasted with the
existing design of the class structure - so one can argue it as being a
limitation. But if one actually designs around interfaces fully, then this
"problem" is easily avoided.
> 3. Allow an interface to be assigned to an object, and converted to the
> class of the object, if the class of the interfaced object and the object
> reference variable are compatible.
As above.
> With these minor extensions, we would be able to freely mix objects and
> interfaces in a class framework using Win32 Delphi, and feel less of an
> urge to move to languages like C#!
I just don't see the problem. Just to be clear, the only issue of mixing
objects and interfaces is when it is done in the *same* class - accessing
the underlying class instead of using its interfaces. Other than that, of
course, interfaces and object use *can* be mixed freely. I know I'm
repeating myself, but this is strictly a design issue.
I don't use interfaces very much in my own projects, but do take advantage
of them here and there, and have never had a problem with reference counting
or any other aspect of them (I alway use TInterfacedObject as a base class,
have never found a case where I *don't* want reference counting).
> Wrong. CreateCOMObject uses the ID of a CoClass, not that of an
> interfaces. There can of course be different implementations in different
> CoClasses that implement the same interface.
>
Thanks for the correction. It's true that CreateCOMObject function takes as
input parameter the GUID of the CoClass identifying the server object to be
activated, and that there can be more one object in the system exposing the
same interface. However the COM model is geared towards instantiating a
small number of objects in the client program that are generally different
in character to the often large number of objects instantiated in a class
framework. Because COM objects are normally used as specific libraries or
automation servers, they do not need to be queried in order to determine
whether or not they support a given interface - this is known at compile
time. The querying mechanism is only required to switch between multiple
interfaces exposed by the object that are known at compile time.
>> interfaced object:
>> if ObjectI is IGraphical then
>> IGraphical(ObjectI).Plot(Canvas);
>
> Sorry, but what is the big difference between "if ObjectI is IGraphical"
> and "if supports(ObjectI,IGraphical)"?
> IGraphical(ObjectI) is still no good idea, of course.
>
There isn't a big difference in behaviour - they have the same effect. But
the "is" sysntax is more consistent with the syntax generally used for
manipulation of classes, and is therefore to be preferred. One of the aims
of the original Pascal language was maximum generality, coherence and
consistency.
Regarding the hard cast in the last line, agreed it's not a good idea if
using the present version of the Delphi language for Win32. It would be a
great idea, however, if it could be supported in future versions, along the
lines of what I understand can be done in C#.
Regards,
EM
EM
Have you read Delphi COM Programming by Eric Harmon? I expect that's the
best (don't let "COM" fool you, it covers non-COM use of interfaces).
Disclosure: I was one of the tech editors (along with Danny Thorpe) - my
qualification was exactly opposite of Danny, I didn't have a clue about
using interfaces yet and so peppered Eric with "I don't get that, you need
to explain more" type of comments.
> Following my recent investigation into the option of using interfaces to
> model abstract methods to be implemented by subsets of classes, I came
> away feeling that in their present form they are not an attractive
> alternative to abstract methods, except when multiple inheritance rules
> out the abstract methods option.
In the simple case where a class tree would only ever be implementing a
single interface, I tend to agree that it can be designed as effectively
without (which explains why I don't use them so much myself), but it offers
additional flexibility if desired; the simple rule to follow is, if they are
to be used, then everything that needs to be public needs to be represented
in an interface rather than needing to access the implementing class. That
will avoid all the problems with their implementation in Win32.
> In the simple case where a class tree would only ever be implementing a
> single interface, I tend to agree that it can be designed as effectively
> without (which explains why I don't use them so much myself), but it
> offers additional flexibility if desired; the simple rule to follow is, if
> they are to be used, then everything that needs to be public needs to be
> represented in an interface rather than needing to access the implementing
> class. That will avoid all the problems with their implementation in
> Win32.
>
Let me soften the conclusions I reported from the exercise I have just
undertaken, as on reflection they were a little extreme.
1) I agree that it is preferable, if any class within a class framework
implements as interface type, to make *all* the classes in the framework
implement at least the base interface type TInterface, or better, a custom
base interface, IBaseInterface, to enable any object to be referred to by an
interface reference, and to make all functionality accessible via interface
types. So if starting developing an application based on a class framework
from scratch it would seem sensible to expose all object functionality via
interfaces.
2) However there may be cases where there is a large existing class
framework that doesn't use any interfaces, and one needs to add some limited
functionality to a subset of classes within the framework that cannot
conveniently be achieved by introducing abstract methods. The use of an
interface type would be more convenient, because it would avoid the single
inheritance constraint. Now converting all functionality of the existing
classes to interface methods may require too much work to be considered
worthwhile. Whilst it's clearly easy to make all the classes in the
hierarchy implement the IInterface interface, one still needs to access
existing data and methods not covered by an interface type via an object or
class reference. Hence there's a need to access objects sometimes via an
object or class reference, and sometimes via an interface reference.
3) I have found that by appropriate coding the mixing of object references
and interfaces references *can* be done satisfactorily - all you have to do
is maintain a dual reference to each object, one as an object of the base
class, and one as an interface of the base interface type. The interface
reference ensures that object lifetime works properly, whilst the object
reference allows one to access the object either as an object or as an
interface, depending on which methods and properties one wishes to access.
It's also possible to use the hack you suggested, of course, to retrieve an
object reference from an interface reference.
4) I am still of the opinion that there is scope for improvement in the
operations that can be performed on interface types and object types, given
that an interface reference combines a reference to an interface VMT *and* a
reference to the object to which it applies. However I acknowledge that
it's possible to get by with the existing facilities.
EM
This is clearly the "pain-point" for trying to use interfaces effectively in
Win32 Delphi and what I try to avoid.