Delphi接口继承:为什么我不能访问祖先接口的成员?

时间:2010-12-07 18:06:20

标签: delphi inheritance interface delphi-2009

假设您有以下内容:

//Note the original example I posted didn't reproduce the problem so
//I created an clean example  
  type
    IParent = interface(IInterface)
    ['{85A340FA-D5E5-4F37-ABDD-A75A7B3B494C}']
      procedure DoSomething;
    end;

    IChild = interface(IParent)
    ['{15927C56-8CDA-4122-8ECB-920948027015}']
      procedure DoSomethingElse;
    end;

    TGrandParent = class(TInterfacedObject)
    end;

    TParent = class(TGrandParent)
    end;

    TChild = class(TParent, IChild)
    private
      FChildDelegate: IChild;
    public
      property ChildDelegate:IChild read FChildDelegate implements IChild;
    end;

    TChildDelegate = class(TInterfacedObject, IChild)
    public
      procedure DoSomething;
      procedure DoSomethingElse;
    end;

我认为这可以让你拨打DoSomething,但情况似乎并非如此:

procedure CallDoSomething(Parent: TParent);
begin
  if Parent is TChild then
    TChild(Parent).DoSomething;
end;

很明显,编译器正在强制执行接口继承,因为除非实现了IParent的成员,否则这两个类都不会编译。尽管如此,当实例化和使用类时,编译器仍无法解析IParent的成员。

我可以通过在类声明中明确包含IParent来解决这个问题 TMyClass

TMyClass = class(TInterfacedObject, IChild, IParent)

没关系,这无效。

4 个答案:

答案 0 :(得分:14)

如果实现类没有声明它支持继承的接口,那么该类将不会与继承接口的变量兼容。您发布的代码示例应该可以正常工作(使用IChild接口),但是如果您尝试从TMyClass实例分配给IParent变量,那么您将遇到麻烦。

原因是因为COM和ActiveX允许实现实现后代接口(您的IChild)但拒绝该接口的祖先(IParent)。由于Delphi接口旨在与COM兼容,因此这就是这个愚蠢的工件来源。

我很确定我在大约10年或12年前写过一篇关于此事的文章,但我的Borland博客并没有幸免于向Embarcadero服务器过渡。

可能有一个编译器指令来改变这种行为,我不记得了。

答案 1 :(得分:8)

问题不在于接口声明或类实现,而在于消费者代码:

procedure CallDoSomething(Parent: TParent);
begin
  if Parent is TChild then
    TChild(Parent).DoSomething;  // << This is wrong
end;

不能正常工作,因为 TChild 没有方法“ DoSomething ”。如果 TChild 直接实施 IChild ,那么通常可以,因为 TChild 会直接将 AND 作为 IChild 界面的一部分实施。

但请注意,如果 TChild PRIVATE 范围内实施了 DoSomething ,它仍然可以通过界面访问,但正常的范围规则意味着你仍然无法使用 TChild 引用来调用它(来自class / uni之外)。

在您的情况下,您只需要获取适当的接口,然后通过界面调用您需要的方法:

  if Parent is TChild then
    (Parent as IChild).DoSomething;

但是,您正在使用类类型测试来确定(推断)接口的存在,依赖于实现细节(知识 TChild 实现 IChild )。我建议您应该直接使用接口测试,将这种依赖性与这些实现细节隔离开来:

  var
    parentAsChild: IChild;

  begin
    if Parent.GetInterface(IChild, parentAsChild) then
      parentAsChild.DoSomething;
  end;

答案 2 :(得分:0)

编辑:此答案不再相关,因为它是在原始问题被修改之前发布的。


这在Delphi 2010中编译:

type
  IParent = interface(IInterface)
    function DoSomething: String;
  end;

  IChild = interface(IParent)
    function DoSomethingElse: string;
  end;

  TMyClass = class(TInterfacedObject, IChild)
  private
  public
    function DoSomething: String;
    function DoSomethingElse: String;
  end;

// ... 

procedure Test;
var
  MyObject : IChild;
begin
  MyObject := TMyClass.Create;
  MyObject.DoSomething;
end;

答案 3 :(得分:-1)

IShellView *psv = some object; IOleView *pow; psv->QueryInterface(IID_IOleView, (void**)&pow); 的Delphi实现不符合标准。在标题为的博客文章中 The ways people mess up IUnknown::QueryInterface Raymond Chen列举了实施中常见的失败。最值得注意的是第三点

  

忘记响应基接口。实现派生接口时,隐式实现基接口,所以   不要忘记回应他们。

var text = "";//Holds the text of current line being looped.
var startindex = 0;//The position where selection starts.
var endindex = 0;//The length of selection.

for (int i = 0; i < richtextbox1.Lines.Length; i++)//Loops through each line of text in RichTextBox
{
    text = richtextbox1.Lines[i];  //Stores current line of text.

    startindex = richtextbox1.GetFirstCharIndexFromLine(i);
    endindex = text.Length;
    richtextbox1.Select(startindex, endindex);
    MessageBox.Show(richtextbox1.SelectedText);
    richtextbox1.SelectedText = "";              
}
     

有些对象忘了,而且QueryInterface因E_NOINTERFACE而失败。

除非将继承的接口显式附加到某个类或其祖先之一,否则Delphi无法找到它。它只是遍历对象的接口表及其继承的类型,并检查接口ID的匹配,它不检查基接口。