gpt4 book ai didi

delphi - 在 Delphi 中,如何检查 IInterface 引用是否实现派生但未显式支持的接口(interface)?

转载 作者:行者123 更新时间:2023-12-03 14:38:33 27 4
gpt4 key购买 nike

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

IBase = Interface ['{82F1F81A-A408-448B-A194-DCED9A7E4FF7}']
End;

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

TImplementation = Class(TInterfacedObject, IDerived)
End;

以下代码打印“Bad!” -

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;

但是当我们开始将 IBase 放入通用容器(例如 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 运算符的对象来完成,但这不适用于接口(interface)。由于各种原因,我不想将 IBase 显式添加到支持的接口(interface)列表中。有什么方法可以编写 TImplementation 和断言,使其计算结果为 true iff 硬转换 IBase(List[0]) 是安全的事情吗?

编辑:

正如答案之一中所提到的,我添加了我不想将 IBase 添加到 TImplementation 实现的接口(interface)列表中的两个主要原因。

首先,它并没有真正解决问题。如果在 Test2 中,表达式:

Supports (List[0], IBase)

返回 true,这并不意味着执行硬强制转换是安全的。 QueryInterface 可能会返回不同的指针来满足所请求的接口(interface)。例如,如果 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 类型 - 我们应该能够假设这一点(因此断言要捕获 if这个假设是错误的)。断言甚至不是必要的,除非有人更改某些类型时捕获以后的错误。这个问题所来自的原始代码对性能也相当关键,因此我宁愿避免获得很少的性能成本(它仍然只能在运行时捕获不匹配的类型,但无法编译更快的发布版本) .

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

编辑 2:通过示例扩展了上述编辑。

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

话虽如此,如果有人可以完全避免这个问题,而不需要性能成本,也没有类型检查的危险,那就太好了!

最佳答案

您可以做的一件事是停止类型转换接口(interface)。您无需执行此操作即可从 IDerived 开始至IBase ,并且您不需要它从 IBase 开始至IUnknown , 任何一个。任何对 IDerived 的引用 IBase已经,所以您可以调用IBase即使没有类型转换的方法。如果您减少类型转换,就可以让编译器为您做更多工作并捕获不合理的内容。

您规定的目标是能够检查您从列表中取出的内容是否确实是 IBase引用。添加IBase因为实现的接口(interface)可以让您轻松实现该目标。从这个角度来看,你不这样做的“两个主要原因”是站不住脚的。

  1. “我希望能够比较引用是否相等”:没问题。 COM 要求,如果您调用 QueryInterface在同一个对象上两次使用相同的 GUID,两次都会获得相同的接口(interface)指针。如果您有两个任意接口(interface)引用,并且您 as -将它们都转换到 IBase ,那么当且仅当它们由同一对象支持时,结果才会具有相同的指针值。

    因为您似乎希望列表仅包含 IBase值,并且您没有 Delphi 2009,其中通用 TInterfaceList<IBase>会有帮助的,您可以约束自己始终明确添加 IBase列表中的值,绝不是任何后代类型的值。每当您将项目添加到列表中时,请使用如下代码:

    List.Add(Item as IBase);

    这样,列表中的任何重复项都可以轻松检测到,并且您的“硬转换”一定会起作用。

  2. “它实际上并没有解决问题”:但根据上述规则,它确实解决了问题。

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

    当对象显式实现其所有接口(interface)时,您可以检查类似的事情。如果您已经像我上面描述的那样将项目添加到列表中,则可以安全地禁用断言。启用断言可以让您检测到有人更改了程序中其他位置的代码以错误地将项目添加到列表中。频繁运行单元测试也可以让您在问题出现后很快发现问题。

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

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 的内容。根本没有,或者您为某些对象添加的特定接口(interface)引用不能充当 IBase引用。如果断言通过,那么您就知道 List[i]会给你一个有效的 IBase值。

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

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

这是安全的,因为 TImplementation 实现的接口(interface)形成一个退化为简单列表的继承树。不存在两个接口(interface)不互相继承但具有共同祖先的分支。如果IBase两个后代,和TImplementation实现它们两者,上面的代码将无效,因为 IBase引用文献 A不一定是“规范”IBase该对象的引用。断言将检测到该问题,您需要将其添加为 List.Add(A as IBase)相反。

当您禁用断言时,只有在添加到列表时才会支付获得正确类型的成本,而不是在从列表中读取时支付。我将变量命名为AssertionItem阻止您在过程的其他地方使用该变量;它只是为了支持断言,一旦禁用断言,它就不再具有有效值。

关于delphi - 在 Delphi 中,如何检查 IInterface 引用是否实现派生但未显式支持的接口(interface)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/755044/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com