【问题标题】:How to check if a class implements an interface, with respecting supersets?如何检查一个类是否实现了一个接口,尊重超集?
【发布时间】:2014-09-02 12:16:59
【问题描述】:

我正在学习 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

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

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

【问题讨论】:

  • 考虑IA = interface、IA1 = interface(IA) 和IA2 = interface(IA)。现在TAggregate = class(TInterfacedObject, IA1, IA2) IA1 和IA2 都被委托:property ImplIA1: IA1 read FIA1 implements IA1; 和property ImplIA2: IA2 read FIA2 implements IA2; 现在,如果您询问TAggregate 的实例以获取其IA 接口,它应该返回哪个实现?关键是接口“继承”并不是真正的继承。所以这个对象不会实现一个“祖先”接口,除非它明确地这样做。
  • @CraigYoung 对此进行扩展的答案会很有用。
  • @CraigYoung 非常感谢这个例子。理解为什么需要显式命名非常有帮助。

标签: delphi oop interface com


【解决方案1】:

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

TBO = class(TInterfacedObject, IA, IB)

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

【讨论】:

  • 这是否意味着 Delphi 认为 IB 包含 IA 包含的所有内容,或者这仅仅是 Delphi 中反射工作方式的一个奇怪之处?
  • 是否可以编写一个替代函数来解决这个问题而不改变类定义,即GetInterfaceEntry 的替代方法?该算法可能如下:(1)我可以通过查询GetInterfaceTable找到TBO的所有接口,(2)然后我需要检测IA是IB的父级最后(3)如果IA是请求的IID,则间接支持。 (4) 这将重复,直到不再有父接口。问题是#2:如何找出IB 的父接口(如接口的TObject.ClassParent)?
  • @rinntech 简单来说TBO不支持IA
  • 这不是德尔福的怪癖。这反映了 Delphi 接口实际上是 COM 接口。
  • 你找错树了,Rinntech。只需修复 TBO 的声明,使其提及 all 它支持的接口。接口继承不像类继承。它不是 is-a 关系,而是 has-all-the-same-members-in-the-same-order-as 关系。
【解决方案2】:

正如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 接口,它应该返回哪个实现?

关键是接口“继承”并不是真正的继承。所以对象不会实现“祖先”接口,除非它明确地这样做。


还有其他一些需要注意的事项:

如果您向 IA 接口添加方法 procedure MethodIA; 并且 do 还显式添加 IA 作为 TAggregate 上的接口,则代码将无法编译。你会得到错误:

未声明的标识符:'MethodIA'

您必须将MethodIA 直接添加到类中,或者选择FIA1 或FIA2 中的哪一个来委托实现。例如。选择 FIA2:

  property ImplIA: IA2 read FIA2 implements IA;

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

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

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;

在编译时扩展是已知的,并且将 扩展引用 分配给它的基是完全合法的(没有任何强制转换或“支持”检查)。但是直接从对象引用中赋值不是。


有趣的是,TypInfo 单元表明接口类型确实知道它们的“父”接口。

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

但是,鉴于上面的讨论,我不确定这些知识如何在运行时受益。我更喜欢编译时检查。

【讨论】:

  • +1 我认为值得一提的是 COM 在这一切中所扮演的角色。
猜你喜欢
  • 1970-01-01
  • 2018-11-16
  • 1970-01-01
  • 2011-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-29
  • 1970-01-01
相关资源
最近更新 更多