How to check if a class implements an interface, w

2019-02-24 06:25发布

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.

2条回答
爷、活的狠高调
2楼-- · 2019-02-24 06:56

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:

IA = interface
<guid>
end;
IA1 = interface(IA)
<guid>
end;
IA2 = interface(IA)
<guid>
end;

And a class that aggregates both IA1 and IA2 in which both are delegated:

TAggregate = class(TInterfacedObject, IA1, IA2)
private
  FIA1: IA1;
  FIA2: IA2;
protected
  property ImplIA1: IA1 read FIA1 implements IA1; 
  property ImplIA2: IA2 read FIA2 implements IA2;
end;

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 add IA as an interface on TAggregate, the code would not compile. You'd get the error:

Undeclared indentifier: 'MethodIA'

You would have to either add MethodIA directly onto the class, or choose which of FIA1 or FIA2 to delegate the implementation to. E.g. Choosing FIA2:

  property ImplIA: IA2 read FIA2 implements IA;

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.

TIA1Implementor = class(TInterfacedObject, IA1)
protected
  procedure MethodIA;
end;

var
  LImplObj: TIA1Implementor;
  LA1: IA1;
  LA: IA;
begin
  LImplObj := TIA1Implementor.Create;
  LA1 := LImplObj; //Valid: TIA1Implementor implements IA1
  LA := LImplObj; //Does not compile: TIA1Implementor does not implement IA
  LA := LA1; //Valid: IA1 is an extension of IA
end;

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.

PTypeData = ^TTypeData;
TTypeData = packed record
  case TTypeKind of
    tkInterface: (
      IntfParent : PPTypeInfo; { ancestor }
      IntfFlags : TIntfFlagsBase;
      Guid : TGUID;
      IntfUnit : ShortStringBase;
     {PropData: TPropData});

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.

查看更多
\"骚年 ilove
3楼-- · 2019-02-24 06:58

This is a known quirk of Delphi. Even though IB inherits from IA, TBO must explicitly specify both IA and IB in order for Supports() to retrieve both interfaces.

TBO = class(TInterfacedObject, IA, IB)

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.

查看更多
登录 后发表回答