如何检查一个类是否实现了一个接口,尊重超集?

时间:2014-07-11 19:23:56

标签: delphi oop interface com

我正在学习COM和接口,并有以下实验代码:

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;

输出:

GetInterfaceEntry IB: OK
QueryInterface TB: OK
Supports TB: OK
Supports(2) TB: OK

但我需要:

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

据我所知,由于接口继承,IBIA的超集。据我所知,由于TBO实现IB,它会自动实现IA。但为什么Supports()QueryInterface()GetInterfaceEntry()会返回false?

如何直接或间接地查询TBO是否实现IA,即通过实施IA的超集?我需要两者,像GetInterfaceEntry这样的静态类函数和像QueryInterface这样的动态对象引用变体。

2 个答案:

答案 0 :(得分:5)

这是Delphi的一个已知怪癖。即使IB继承自IATBO也必须明确指定IAIB,以便Supports()检索这两个接口。

TBO = class(TInterfacedObject, IA, IB)

我忘了这个的技术原因。与编译器如何为TBO生成接口表的限制有关。它不会自动包含继承的接口。

答案 1 :(得分:5)

作为Marco Cantù explains here:接口"继承"与类继承不同,实际上将它称为接口扩展可能更好。

如果我们假设一个类自动实现了一个基接口,那么会出现一些严重的异常。

考虑:

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

一个聚合IA1和IA2的类,其中两个都被委派:

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;

现在,如果你问一个TAggregate实例的IA接口,应该返回哪个实现?

关键是接口&#34;继承&#34;不是真正的继承。因此,该对象并没有实现一个&#34;祖先&#34;界面,除非明确这样做。


还有一些其他事情要注意:

如果您将方法procedure MethodIA;添加到IA界面并且执行也明确地将IA添加为TAggregate上的接口,则代码将无法编译。你得到错误:

  

未声明的标识符:&#39; MethodIA&#39;

您必须直接在课程中添加MethodIA,或者选择将FIA1FIA2委托给哪个实施。例如。选择FIA2:

  property ImplIA: IA2 read FIA2 implements IA;

此属性声明显示了Delphi识别接口 is-a 关系的方式。即不是通过实现类,而是通过接口引用。

注意:即使在编译时,编译器也不会假定类实现了一个基接口,除非明确说明。

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;

在编译时,扩展名是已知的,并且为其基础分配扩展名引用是完全合法的(没有任何强制转换或&#34;支持&#34;检查)。但直接从对象引用中分配不是。


有趣的是,TypInfo单元显示接口类型确实知道他们的父母&#34;接口

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

然而,鉴于上述讨论,我不确定这些知识如何在运行时受益。我更倾向于编译时检查。