I am learning about COM and Interfaces and have following experimental code:
type
IA = interface(IInterface)
['{C9C5C992-3F67-48C5-B215-7DCE6A61F0E8}']
end;
IB = interface(IA)
['{F1799437-AD12-471B-8716-F1D93D1692FC}']
end;
IC = interface(IB)
['{01780E8C-C47D-468E-8E42-4BFF3F495D51}']
end;
TBO = class(TInterfacedObject, IB)
end;
procedure TForm1.FormCreate(Sender: TObject);
var
x: TBO;
a: IInterface;
begin
x := TBO.Create;
IInterface(x)._AddRef;
if Assigned(TBO.GetInterfaceEntry(IA)) then memo1.lines.add('GetInterfaceEntry IA: OK'); // Why not?
if Assigned(TBO.GetInterfaceEntry(IB)) then memo1.lines.add('GetInterfaceEntry IB: OK');
if Assigned(TBO.GetInterfaceEntry(IC)) then memo1.lines.add('GetInterfaceEntry IC: OK');
if x.QueryInterface(IA, a)=S_OK then memo1.lines.add('QueryInterface TA: OK'); // Why not?
if x.QueryInterface(IB, a)=S_OK then memo1.lines.add('QueryInterface TB: OK');
if x.QueryInterface(IC, a)=S_OK then memo1.lines.add('QueryInterface TC: OK');
if Supports(TBO, IA) then memo1.lines.add('Supports TA: OK'); // Why not?
if Supports(TBO, IB) then memo1.lines.add('Supports TB: OK');
if Supports(TBO, IC) then memo1.lines.add('Supports TC: OK');
if Supports(x, IA, a) then memo1.lines.add('Supports(2) TA: OK'); // Why not?
if Supports(x, IB, a) then memo1.lines.add('Supports(2) TB: OK');
if Supports(x, IC, a) then memo1.lines.add('Supports(2) TC: OK');
end;
Output:
GetInterfaceEntry IB: OK
QueryInterface TB: OK
Supports TB: OK
Supports(2) TB: OK
But I need:
GetInterfaceEntry IA: OK
GetInterfaceEntry IB: OK
QueryInterface TA: OK
QueryInterface TB: OK
Supports TA: OK
Supports TB: OK
Supports(2) TA: OK
Supports(2) TB: OK
I understand that IB
is a superset of IA
due to the Interface inheritance. In my understanding, since TBO
implements IB
, it automatically implements IA
. But why does Supports()
, QueryInterface()
, GetInterfaceEntry()
return false?
How do I query if TBO
implements IA
directly OR indirectly, i.e. by implementing a superset of IA
? I need both, a static class function like GetInterfaceEntry
and a dynamic object reference variant like QueryInterface
.
As Marco Cantù explains here: Interface "inheritance" is not like class inheritance, and in fact it would probably be better to refer to it as interface extension.
There are some serious anomalies that arise if we were to assume a class automatically implemented a base interface.
Consider:
And a class that aggregates both IA1 and IA2 in which both are delegated:
Now if you ask an instance of TAggregate for its IA interface, which implementation should it return?
The point is interface "inheritance" is not really inheritance. So the object doesn't implement an "ancestor" interface unless it does so explicitly.
There are a few other things to observe:
If you add a method
procedure MethodIA;
to the IA interface and do also explicitly addIA
as an interface onTAggregate
, the code would not compile. You'd get the error:You would have to either add
MethodIA
directly onto the class, or choose which ofFIA1
orFIA2
to delegate the implementation to. E.g. Choosing FIA2:This property declaration shows the way in which Delphi does recognise interface is-a relationships. I.e. not through an implementation class, but rather an interface reference.
NOTE: Even at compile-time, the compiler will not assume a class implements a base interface unless explicitly stated.
At compile-time the extension is known, and assigning an extension reference to its base is perfectly legal (without any casting or "supports" checks). But assigning directly from the object reference is not.
Interestingly, the
TypInfo
unit shows that interface types do know about their "parent" interface.However given the discussion above I'm not sure how this knowledge could be of benefit at run-time. I'd far rather favour compile-time checking.
This is a known quirk of Delphi. Even though
IB
inherits fromIA
,TBO
must explicitly specify bothIA
andIB
in order forSupports()
to retrieve both interfaces.I forget the technical reason for this. Something to do with a limitation in how the compiler generates the interface table for
TBO
. It does not automatically include inherited interfaces.