在Delphi中,如何检查IIinterface引用是否实现了派生的但不受显式支持的接口?

在Delphi中,如何检查IIinterface引用是否实现了派生的但不受显式支持的接口?,delphi,winapi,types,interface,delphi-2007,Delphi,Winapi,Types,Interface,Delphi 2007,如果我有以下接口和实现它们的类- IBase = Interface ['{82F1F81A-A408-448B-A194-DCED9A7E4FF7}'] End; IDerived = Interface(IBase) ['{A0313EBE-C50D-4857-B324-8C0670C8252A}'] End; TImplementation = Class(TInterfacedObject, IDerived) End; 下面的代码打印“坏!”- Procedure Test; V

如果我有以下接口和实现它们的类-

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是iderive,iderive是IBase。因此,如果我不付支票,我可以投一张支票,一切都会好起来的-

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;
但是,当我们开始将iBASE放入一个通用容器时,我们遇到了一个问题——例如,TInterfaceList。它只能容纳界面,所以我们必须做一些铸造

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和断言,使其评估为真正的iff硬转换IBase(列表[0])是一件安全的事情

编辑:

正如在其中一个答案中提到的,我添加了两个主要原因,即我不想将IBase添加到TImplementation实现的接口列表中

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

Supports (List[0], IBase)
返回true,这并不意味着执行强制转换是安全的。QueryInterface可以返回不同的指针以满足请求的接口。例如,如果TImplementation显式实现了IBase和iderive(以及IInterface),那么断言将成功通过:

Assert (Supports (List[0], IBase)); //Passes, List[0] does implement IBase
但是想象一下,有人错误地将一项添加到列表中作为界面

List.Add(Item As IInterface);
断言仍然通过-项仍然实现IBase,但是添加到列表中的引用只是一个接口-将其硬转换到IBase不会产生任何合理的结果,因此断言不足以检查以下硬转换是否安全。保证工作的唯一方法是使用铸态或支架:

(List[0] As IBase).DoWhatever;
但是,这是一个令人沮丧的性能代价,因为它是代码向列表中添加项以确保它们属于IBase类型的责任——我们应该能够假设这一点(因此,如果该假设是错误的,则捕获断言)。断言甚至不是必需的,只是为了在以后有人更改某些类型时发现错误。这个问题产生的原始代码也是相当关键的性能,因此我宁愿避免性能成本很少(它仍然只在运行时捕获不匹配的类型,但不可能编译更快的发布版本)

第二个原因是我希望能够比较引用的相等性,但是如果同一个实现对象由具有不同VMT偏移量的不同引用持有,则无法做到这一点

编辑2:用一个例子展开了上面的编辑

编辑3:注意:问题是如何制定断言,以便在断言通过时硬转换是安全的,而不是如何避免硬转换。有不同的方法来执行硬转换步骤,或者完全避免硬转换步骤,但是如果存在运行时性能成本,我不能使用它们。我想要在断言中检查的所有成本,以便以后可以编译出来

话虽如此,如果有人能够完全避免问题,而没有性能成本和类型检查的危险,那就太好了

试验2中

您不应通过IBase(A)将IDerived重新键入为IBase,但应使用:

Supports(A, IBase, B);
添加到列表中可以是:

List.Add(B);

你的考试是对的,据我所知,你遇到的问题确实没有直接的解决办法。原因在于接口之间的继承性,而类之间的继承性只是一种模糊的相似性。 继承的接口是一个全新的接口,它与继承的接口有一些相同的方法,但没有直接连接。因此,通过选择不实现基类接口,您可以做出编译程序将遵循的特定假设:TImplementation不实现IBase。 我认为“接口继承”有点用词不当,接口扩展更有意义!一种常见的做法是让一个基类实现基类接口,而不是派生类实现扩展接口,但如果需要一个单独的类来实现这两个接口,只需列出这些接口即可。您不想使用的具体原因如下:

TImplementation = Class(TInterfacedObject, IDerived, IBase)
还是你不喜欢

进一步评论

即使是硬类型,也不应强制转换接口。当你在一个界面上执行“as”时,它会以正确的方式调整对象vtable指针。。。如果您进行硬转换(并有方法调用),代码很容易崩溃。我的印象是,您将接口视为对象(以相同的方式使用继承和强制转换),而它们的内部工作方式却完全不同

您可以做的一件事是停止类型转换接口。从
iderive
IBase
,您不需要这样做,也不需要从
IBase
IUnknown
。对
IDerived
的任何引用都已经是
IBase
,因此即使不进行类型转换,也可以调用
IBase
方法。如果你减少类型转换,你会让编译器为你做更多的工作,并捕捉到一些不合理的东西

你的目标是能够检查你从列表中得到的东西是否真的是一个
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.
var
  A: IDerived;
begin
  A := TImplementation.Create;
  List.Add(A);
end;