Re: IcompleteInterface = interface(IsubInterface1, IsubInterface2, IsubInterface3) not possible ?
- From: "Skybuck Flying" <nospam@xxxxxxxxxxx>
- Date: Fri, 30 Sep 2005 11:05:48 +0200
> > Since now I have to have 3 different variables to access the complete
object
> > for example:
> >
> > var
> > SubInterface1 : IsubInterface1;
> > SubInterface2 : IsubInterface2;
> > SubInterface3 : IsubInterface3;
> >
> > SubInterface1 := TcompleteClass.Create;
> > SubInterface2 := ???;
> > SubInterface3 := ???;
>
> Subinterface2 := Subinterface1 as ISubinterface2;
> Subinterface3 := Subinterface1 as ISubinterface3;
Ok, at first I tried this it gave a compiler error:
"[Error] Unit1.pas(41): Operator not applicable to this operand type"
After reading your post I thought I'd give typecasts a go to see what would
happen.
Ofcourse the type cast worked but the result was wrong, the wrong functions
would be called.
Then I read the documentation for the "as" operator.
"
Classes that implement interfaces can use the as operator for dynamic
binding on the interface. In the following example,
procedure PaintObjects(P: TInterfacedObject)
var
X: IPaint;
begin
X := P as IPaint;
{ statements }
end;
"
It starts with an example. Notice P which is a TinterfacedObject which is a
class. So seeing this example I thought: "hmmm maybe the "as" operator only
works for classess and not for interfaces etc".
Finaly the documentation reads:
"
When you use the as operator for dynamic binding on an interface, you should
be aware of the following requirements:
1. Explicitly declaring IInterface: Although all interfaces derive from
IInterface, it is not sufficient, if you want to use the as operator, for a
class to simply implement the methods of IInterface. This is true even if it
also implements the interfaces it explicitly declares. The class must
explicitly declare IInterface in its interface list.
2. Using an IID: Interfaces can use an identifier that is based on a GUID
(globally unique identifier). GUIDs that are used to identify interfaces are
referred to as interface identifiers (IIDs). If you are using the as
operator with an interface, it must have an associated IID. To create a new
GUID in your source code you can use the Ctrl+Shift+G editor shortcut key.
"
I do not understand point 1 though I tried it and that doesn't matter.
I also do not completely understand point 2 but again I tried it and this
time it works. The code will compile/execute and the result will be correct
etc.
I have some basic questions/remarks:
1. I thought that GUID's were only necessary for type libraries, windows
registry stuff and COM etc...
In this example I am not really using COM etc ? (I'll post the complete code
later on ;) )
2. Why does it suddenly work when the GUID's are included ? That's kinda
strange... Can't delphi do this automatically ? Why do I the programmer need
to create a GUID etc when I am not even using COM or any other stuff outside
my program etc, etc, etc, can't the compiler simply generate a GUID for each
interface... when pressing compile button ?
3. Are these GUID's registrated into the windows registry ??? (I don't want
to polute my registry ;))
Here is the complete code:
*** BEGIN OF UNIT2.PAS ***
unit Unit2;
interface
type
IsubInterface1 = interface {(IInterface)}
['{79599B4F-C8A9-4CFB-BDF5-B5CB135A9F8B}']
function Blue : string;
end;
IsubInterface2 = interface {(IInterface)}
['{8E5EDD9C-683A-4AA8-BA2B-48A9F48F3769}']
function Red : string;
end;
IsubInterface3 = interface {(IInterface)}
['{D9757B2D-82E5-44F7-9C4D-551DF5781ADF}']
function Green : string;
end;
{
IcompleteInterface = interface(IsubInterface1, IsubInterface2,
IsubInterface3)
function Tata : string;
end;
}
type
// TcompleteClass = class( TinterfacedObject, IcompleteInterface)
TcompleteClass = class( TinterfacedObject, IsubInterface1, IsubInterface2,
IsubInterface3 )
private
public
function Red : string;
function Blue : string;
function Green : string;
function Tata : string;
end;
implementation
function TcompleteClass.Red : string;
begin
result := 'Red';
end;
function TcompleteClass.Blue : string;
begin
result := 'Blue';
end;
function TcompleteClass.Green : string;
begin
result := 'Green';
end;
function TcompleteClass.Tata : string;
begin
result := 'Tata';
end;
end.
*** END OF UNIT2.PAS ***
*** BEGIN OF UNIT1.PAS ***
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Unit2;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
vSubInterface1 : ISubInterface1;
vSubInterface2 : ISubInterface2;
vSubInterface3 : ISubInterface3;
begin
vSubInterface1 := TCompleteClass.Create;
ShowMessage( vSubInterface1.Blue );
// typecasting in this case is very bad and produces a wrong result
// the wrong functions will be called. Use the "as" operator.
// The "as" operator only works on interfaces if they have a GUID
// press control-shift-g to create a unique guid for each interface.
// I accidently press control-g and found a nifty/handy tool.
// The "procedure list" gui/view for quickly finding and navigating to
// a procedure... ;)
// vSubInterface2 := ISubInterface2(vSubInterface1); // very dangerous dont
use this.
vSubInterface2 := vSubInterface1 as ISubInterface2; // guid need for both
interfaces
ShowMessage( vSubInterface2.Red );
vSubInterface3 := vSubInterface1 as ISubInterface3; // guid need for both
interfaces
ShowMessage( vSubInterface3.Green );
end;
end.
*** END OF UNIT1.PAS ***
>
> > That's the first problem.
> >
> > I would like to solve this problem simply by creating a "complete
interface"
> > like so:
> >
> > IcompleteInterface = interface(IsubInterface1, IsubInterface2,
> > IsubInterface3)
> > function Tata : string;
> > end;
>
> Doesn't seem like there's much point in having separate interfaces if
> you're just going to do that.
There is a point in doing that...
The functionality of each interface can be re-used seperately from each
other.
The functionality of each interface can be combined together and expanded
upon to create a larger component.
Code from libraries which act upon the little interfaces can now also act
upon the larger components etc since they have the same little interface ;)
Thus the code from the libraries can be re-used to work with larger
components as well :)
>
> > And then simply define/declare the class as follows:
> >
> > TcompleteClass = class( TinterfacedObject, IcompleteInterface)
>
> Supposing it were possible to define ICompleteInterface the way you want
> to, the the class above still wouldn't implement the other three
> interfaces.
Lol, no.
Since it's not possible to define it in the first place we are free to
create it the way we want too ;)
What I want is that when a class would implement ICompleteInterface it would
simply behave the same as if it implemented each interface seperately.
> It only implements two: IUnknown, which it inherited from
> TInterfacedObject, and ICompleteInterface. It will contain no record of
> any of ICompleteInterface's ancestors.
Ofcourse not since this idea is not implemented.
I guess if it was implemented the records or whatever information it needs
would be there ;)
* Borland simply has to make it happen * :)
> A class declaration needs to
> mention *every* interface it implements
Sounds kinda boring and labor intensive ;)
Can't we simply group those little interfaces together in a bigger interface
etc ;)
> (except the ones it inherits from its ancestor class).
Logical captain =D
>
> type
> TComplete = class(TInterfacedObject, ISubinterface1, ISubinterface2,
> ISubinterface3, ICompleteInterface);
>
> The order of the interfaces doesn't matter, but I like to list them in
> order of least specific to most specific.
>
> > And then simply access the object as follows:
> >
> > var
> > CompleteInterface : ICompleteInterface;
> >
> > CompleteInterace := TcompleteClass.Create;
> > etc...
> >
> > However delphi does not allow me to define
> >
> > IcompleteInterface = interface(IsubInterface1, IsubInterface2,
> > IsubInterface3)
> >
> > ????
> >
> > Why is that ?
>
> An interface reference is just a pointer to a list of function pointers.
> That list has to be in a well-defined order.
>
> When one interface descends from another, the descendant's list of
> methods must consist of the parent's full list followed by any new
> methods introduced by the descendant.
>
> IUnknown has three methods (_AddRef, _Release, and QueryInterface), so
> its list has three function pointers. It looks a little like this:
>
> type
> PIUnknownMT = ^TIUnknownMT;
> TIUnknownMT = record
> QueryInterface: TQueryInterface;
> _AddRef: TAddRef;
> _Release: TRelease;
> end;
>
> (The exact definitions of TAddRef et al aren't necessary for this
> discussion, but they're not hard to guess if you really want to know
> what they are.)
>
> Now consider the IPersist interface, which is a direct descendant of
> IUnknown and has one method of its own. Its layout would look like this:
>
> type
> PIPersistMT = ^TIPersistMT;
> TIPersistMT = record
> UnknownMT: TIUnknownMT;
> GetClassID: TGetClassID;
> end;
>
> That is, an IPersist method table is an IUnknown method table with
> another pointer tacked onto the end. For this reason, when we talk about
> one interface inherited from another, it's really more accurate to say
> that the one is _extending_ the other.
>
> As I said above, an interface reference is a pointer to such a method
> table. (It's actually a pointer to a pointer, but that's not important
> for this. I include a link below to my Web site where I explain
> interfaces a little more.) Notice that a pointer to an IPersist
> interface *is* a pointer to an IUnknown interface, too. The pointer
> points to the entire record. A pointer to a record is the same as a
> pointer to the first field of the record, which in this case is the
> UnknownMT field. This sort of "polymorphism" is important toward
> answering your question of why an interface can't descend from multiple
> interfaces.
>
> If we have a reference to an ICompleteInterface, then we must also have
> a reference to an ISubinterface1 because the former descends from the
> latter. We also have a reference to an IUnknown for the same reason.
> (Having a reference to IUnknown is critical since that's where the
> QueryInterface method is, and QueryInterface is how we cast from one
> interface to another when they're not direct descendants of one another.)
>
> If ICompleteInterface is also a descendant of ISubinterface2, then
> having a reference to an ICompleteInterface must also mean that we have
> a reference to an ISubinterface2. We must also have a reference to an
> ISubinterface3. Obviously, we can't have a reference to all three of
> those separate, unrelated types at once. The function-pointer lists
> can't overlap.
Maybe delphi needs a special type of interface which is ment to allow
multiple interfaces to be embedded into one interface for easy use etc.
Then it would be possible to have multiple interface references if that's
really necessary and the function points lists don't have to overlap.. they
can simply be appended after each other ? ;)
Typecasting interfaces is bad anyway since the pointer list has to be
retrieved. So just typecasting itself doesn't work since the pointer values
still point to the function of the real type etc... and not the type in the
typecast etc...
So the as operator could have special code to handle this special type ;)
Maybe a good name for this special interface type would be
IAggregatedInterface just like TAggregatedObject ;)
>
> http://www.cs.wisc.edu/~rkennedy/interface-object
Ok, good link for more detailed information ;)
> > How am I suppose to work with/access objects which have multiple
interfaces
> > ??? without messing up the reference counting etc...
>
> Use Delphi's "as" casting. I provided a demonstration above.
Hmmm, I tried that ofcourse I didn't work because of the missing GUID's ;)
> It's a wrapper around a call to the interface's QueryInterface method.
> If QueryInterface returns something other than S_OK, then the "as" cast
> will raise an exception. But calling QueyrInterface directly can be
> dangerous. Using "as" will protect you against calling QueryInterface on
> a nil reference.
Ok, good to know that ;)
>
> > One solution might be to use inheritance like this:
> >
> > A = Interface
> > B = Interface(A)
> > C = Interface(B)
> > CompleteInterface = Interface(C)
> >
> > But this more or less defeats the whole purpose of interfaces ?
>
> No more than your original attempt. Either way, you wind up with
> ICompleteInterface, and it has all the methods of its three ancestors.
>
> Interface inheritence is generally frowned upon.
>
> > Having completely seperate interfaces which can all be inherited via
> > multiple inheritance seems kinda nice ;)
>
> What I've described here is how interfaces work in Win32.
Euhm... There is a difference between COM interfaces and simple Delphi
interfaces...
> Interfaces in
> Java and .Net are different, and I think what you describe might be
> possible. I don't know exactly _how_ they're different since I haven't
> studied them. It might be nice if Win32 interfaces were a little more
> flexible, but there's really no use complaining about it since it's not
> going to change. All we can do is be aware of the limits, and learn to
> work within them.
No I dont think this is a true. Interfaces are also something borland
specific so borland is free to change there interface implementation etc ;)
Ofcourse it should also remain compatible with COM... but some extensions
could be made ;)
Bye,
Skybuck =D
.
- References:
- Prev by Date: Re: Re: strange anomalies when using an iteration variable twice in 1 function
- Next by Date: Tsomething(nil).SomeMethod and Tsomething(garbage).SomeMethod should halt the debugger.
- Previous by thread: IcompleteInterface = interface(IsubInterface1, IsubInterface2, IsubInterface3) not possible ?
- Next by thread: FFT class or routines
- Index(es):