加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

在Delphi中,如何检查IInterface引用是否实现派生但不明确支持的

发布时间:2020-12-15 06:09:53 所属栏目:大数据 来源:网络整理
导读:如果我有以下接口和一个实现它们的类 – IBase = Interface ['{82F1F81A-A408-448B-A194-DCED9A7E4FF7}']End;IDerived = Interface(IBase) ['{A0313EBE-C50D-4857-B324-8C0670C8252A}']End;TImplementation = Class(TInterfacedObject,IDerived)End; 以下代
如果我有以下接口和一个实现它们的类 –
IBase = Interface ['{82F1F81A-A408-448B-A194-DCED9A7E4FF7}']
End;

IDerived = Interface(IBase) ['{A0313EBE-C50D-4857-B324-8C0670C8252A}']
End;

TImplementation = Class(TInterfacedObject,IDerived)
End;

以下代码打印“坏!” –

Procedure Test;
Var
    A : IDerived;
Begin
    A := TImplementation.Create As IDerived;
    If Supports (A,IBase) Then
        WriteLn ('Good!')
    Else
        WriteLn ('Bad!');
End;

这有点恼人但可以理解.支持不能转换为IBase,因为IBase不在TImplementation支持的GUID列表中.可以通过将声明更改为 –

TImplementation = Class(TInterfacedObject,IDerived,IBase)

然而,即使没有这样做,我已经知道A实现了IBase,因为A是一个IDerived,IDerived是一个IBase.所以如果我不用支票我可以投A,一切都会好的 –

Procedure Test;
Var
    A : IDerived;
    B : IBase;
Begin
    A := TImplementation.Create As IDerived;
    B := IBase(A);
    //Can now successfully call any of B's methods
End;

但是,当我们开始将IBases放入通用容器 – 例如TInterfaceList中时,我们遇到了一个问题.它只能保留IInterfaces,所以我们必须做一些投射.

Procedure Test2;
Var
    A : IDerived;
    B : IBase;
    List : TInterfaceList;
Begin
    A := TImplementation.Create As IDerived;
    B := IBase(A);

    List := TInterfaceList.Create;
    List.Add(IInterface(B));
    Assert (Supports (List[0],IBase)); //This assertion fails
    IBase(List[0]).DoWhatever; //Assuming I declared DoWhatever in IBase,this works fine,but it is not type-safe

    List.Free;
End;

我非常希望有一些断言来捕获任何不匹配的类型 – 这种事情可以使用Is操作符的对象来完成,但这对于接口来说不起作用.由于种种原因,我不想明确地将IBase添加到支持的接口列表中.有没有办法我可以写TImplementation和断言这样一个方式,它将评估为true如果硬化IBase(List [0])是一件安全的事情?

编辑:

在其中一个答案中,我补充说,我不想将IBase添加到TImplementation实现的接口列表中的两个主要原因.

首先,实际上并不解决问题.如果,在Test2中,表达式:

Supports (List[0],IBase)

返回true,这并不意味着执行硬执行是安全的. QueryInterface可能会返回一个不同的指针来满足请求的接口.例如,如果TImplementation显式实现了IBase和IDerived(和IInterface),则断言将成功传递:

Assert (Supports (List[0],IBase)); //Passes,List[0] does implement IBase

但是想象一下,有人误将一个项目作为IInterface添加到列表中

List.Add(Item As IInterface);

该断言仍然通过 – 该项目仍然实现IBase,但是添加到列表中的引用仅仅是一个IInterface – 将其硬化到IBase不会产生任何明智的,所以断言是不够的,铸造是安全的.确保工作的唯一方法是使用演播或支持:

(List[0] As IBase).DoWhatever;

但这是一个令人沮丧的性能成本,因为它的目的是将代码添加到列表中的代码的责任,以确保它们是IBase类型 – 我们应该能够承担这一点(因此,如果这个假设是假).这个说法甚至不是必要的,除非有任何人改变某些类型,否则要抓住以后的错误.这个问题来自的原始代码对于性能也是非常关键的,所以性能成本很少(在运行时仍然只能捕获不匹配的类型,但是没有编译更快版本的可能性)是我宁愿避免的.

第二个原因是我希望能够比较引用的相等性,但是如果同一个实现对象由具有不同VMT偏移量的不同引用持有,则不能完成此操作.

编辑2:以示例展开上述编辑.

编辑3:注意:问题是我如何制定断言,以便硬判决是安全的,如果断言通过,而不是如何避免强硬.有一些方法可以不同地进行强硬步骤,或者完全避免这种情况,但是如果运行时性能成本,我就不能使用它们.我想要在断言中检查所有的成本,以便以后可以编译.

话虽如此,如果有人可以完全避免这个问题,没有性能成本,没有类型检查的危险将是巨大的!

解决方法

你可以做的一件事是停止类型转换界面.您不需要从IDerived到IBase,也不需要它从IBase到IUnknown.任何对IDerived的引用都是IBase,所以即使没有类型转换,也可以调用IBase方法.如果你做更少的类型转换,你让编译器为你做更多的工作,并抓住那些不健全的东西.

您所述的目标是能够检查您从列表中获取的内容是否真的是IBase参考.添加IBase作为实现的接口将允许您轻松实现该目标.在这方面,你没有这样做的“两个主要原因”就没有水.

>“我想能够比较引用的平等”:没问题. COM需要如果您在同一对象上使用相同的GUID调用QueryInterface两次,那么您将获得两次相同的接口指针.如果您有两个任意的接口引用,并且将它们两者都转换为IBase,那么当且仅当它们由相同对象支持时,结果将具有相同的指针值.

由于您似乎希望您的列表只包含IBase值,并且您没有Delphi 2009中通用的TInterfaceList< IBase>这将是有帮助的,你可以纪律自己,始终明确地添加IBase值到列表,从来没有任何后裔类型的值.每当你添加一个项目到列表,使用这样的代码:

List.Add(Item as IBase);

这样,列表中的任何重复项都很容易被检测出来,您的“硬盘”就可以放心工作.
>“实际上并不解决问题”:但是,如上所述,

Assert(Supports(List[i],IBase));

当对象明确地实现其所有接口时,可以检查这样的事情.如果您已将项目添加到列表中,可以禁用该断言.启用断言可以检测何时有人更改程序中其他位置的代码,以将错误的项添加到列表中.经常运行您的单元测试也将让您在介绍之后很快就会发现问题.

考虑到以上几点,您可以使用以下代码检查添加到列表中的任何内容是否正确添加:

var
  AssertionItem: IBase;

Assert(Supports(List[i],IBase,AssertionItem)
       and (AssertionItem = List[i]));
// I don't recall whether the compiler accepts comparing an IBase
// value (AssertionItem) to an IUnknown value (List[i]). If the
// compiler complains,then simply change the declaration to
// IUnknown instead; the Supports function won't notice.

如果断言失败,则您添加了一些不支持IBase的列表,或者为某个对象添加的特定接口引用不能作为IBase引用.如果断言通过,那么你知道List [i]会给你一个有效的IBase值.

请注意,添加到列表中的值不需要显式为IBase值.鉴于上面的类型声明,这是安全的:

var
  A: IDerived;
begin
  A := TImplementation.Create;
  List.Add(A);
end;

这是安全的,因为TImplementation实现的接口形成一个简并为简单列表的继承树.没有分支,其中两个接口不相互继承但具有共同的祖先.如果IBase有两个提示,并且TI实现都实现了这两个,上述代码将无效,因为在A中保存的IBase引用不一定是该对象的“规范”IBase引用.该断言会检测到该问题,您需要将其添加到List.Add(A as IBase)中.

当您禁用断言时,只有在添加到列表中时才会收到正确的类型的费用,而不是从列表中读取.我命名变量AssertionItem以阻止您在该过程的其他地方使用该变量;它只是支持断言,一旦断言被禁用,它就不会有一个有效的值.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读