Dav*_*vid 12 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;
Run Code Online (Sandbox Code Playgroud)
以下代码打印'Bad!' -
Procedure Test;
Var
A : IDerived;
Begin
A := TImplementation.Create As IDerived;
If Supports (A, IBase) Then
WriteLn ('Good!')
Else
WriteLn ('Bad!');
End;
Run Code Online (Sandbox Code Playgroud)
这有点烦人但可以理解.支持无法转换为IBase,因为IBase不在TImplementation支持的GUID列表中.可以通过将声明更改为 - 来修复
TImplementation = Class(TInterfacedObject, IDerived, IBase)
Run Code Online (Sandbox Code Playgroud)
然而,即使没有这样做,我已经知道 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;
Run Code Online (Sandbox Code Playgroud)
但是当我们开始将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;
Run Code Online (Sandbox Code Playgroud)
我非常想要某种断言来捕获任何不匹配的类型 - 这种事情可以使用Is运算符来完成,但这对接口不起作用.由于各种原因,我不希望将IBase显式添加到支持的接口列表中.有没有什么方法可以用这样的方式编写TImplementation和断言,如果硬编译IBase(List [0])是安全的事情,它会评估为真?
编辑:
正如其中一个答案所示,我添加了两个主要原因,我不想将IBase添加到TImplementation实现的接口列表中.
首先,它实际上并没有解决问题.如果,在Test2中,表达式:
Supports (List[0], IBase)
Run Code Online (Sandbox Code Playgroud)
返回true,这并不意味着执行强制转换是安全的.QueryInterface可以返回不同的指针以满足所请求的接口.例如,如果TImplementation显式实现了IBase和IDerived(以及IInterface),则断言将成功传递:
Assert (Supports (List[0], IBase)); //Passes, List[0] does implement IBase
Run Code Online (Sandbox Code Playgroud)
但想象有人错误地将一个项目添加到列表中作为IInterface
List.Add(Item As IInterface);
Run Code Online (Sandbox Code Playgroud)
断言仍然通过 - 该项仍然实现了IBase,但添加到列表中的引用只是一个IInterface - 将其强制转换为IBase不会产生任何合理的,因此断言不足以检查以下是否演员是安全的.保证工作的唯一方法是使用as-cast或支持:
(List[0] As IBase).DoWhatever;
Run Code Online (Sandbox Code Playgroud)
但这是一个令人沮丧的性能成本,因为它的目的是将代码添加到列表中以确保它们属于IBase类型 - 我们应该能够假设这一点(因此,如果这个假设是假).断言甚至不是必要的,除非如果有人改变某些类型,以便捕获后来的错误.这个问题来自的原始代码也具有相当的性能关键性,因此性能成本很低(它在运行时仍然只捕获不匹配的类型,但没有编译更快版本构建的可能性)是我宁愿避免的.
第二个原因是我希望能够比较引用的相等性,但是如果相同的实现对象由具有不同VMT偏移的不同引用保持,则无法完成.
编辑2:通过示例扩展了上述编辑.
编辑3:注意:问题是我如何制定断言,以便如果断言通过,则强制转换是安全的,而不是如何避免强制转换.有很多方法可以不同地执行硬编译步骤,或者完全避免它,但如果存在运行时性能成本,我就无法使用它们.我想要在断言中检查所有成本,以便以后可以编译出来.
话虽如此,如果有人可以完全避免这个问题,没有性能成本,也没有类型检查的危险,那将是巨大的!
Rob*_*edy 12
你可以做的一件事是停止类型转换接口.你并不需要做的是从去IDerived到IBase了,你不需要它,从去IBase到IUnknown,无论是.对a的任何引用IDerived 都 IBase已经存在,因此IBase即使没有类型转换也可以调用方法.如果你减少了类型转换,你可以让编译器为你做更多工作并捕捉不合理的东西.
您声明的目标是能够检查您从列表中获取的内容是否真的是一个IBase参考.添加IBase为已实现的界面可以让您轻松实现该目标.从那个角度来看,你不这样做的"两个主要原因"就是没有水.
"我希望能够比较平等的参考":没问题.COM要求如果QueryInterface在同一对象上使用相同的GUID 调用两次,则两次都会获得相同的接口指针.如果你有两个任意的接口引用,并且你as将它们都转换为IBase,那么当且仅当它们由同一个对象支持时,结果将具有相同的指针值.
由于您似乎希望您的列表仅包含IBase值,并且您没有通用TInterfaceList<IBase>对其有用的Delphi 2009 ,因此您可以自我约束以始终IBase向列表中显式添加值,而不是任何后代类型的值.每当您向列表中添加项目时,请使用以下代码:
List.Add(Item as IBase);
Run Code Online (Sandbox Code Playgroud)
这样,列表中的任何重复项都很容易被发现,并且您的"强硬演员"可以确保工作.
"它实际上并没有解决问题":但鉴于上述规则,它确实如此.
Assert(Supports(List[i], IBase));
Run Code Online (Sandbox Code Playgroud)
当对象显式实现其所有接口时,您可以检查这样的事情.如果你像上面所描述的那样将项目添加到列表中,则可以安全地禁用断言.通过启用断言,您可以检测有人在程序中的其他位置更改了代码,从而错误地将项目添加到列表中.经常运行您的单元测试也可以让您在问题出现后立即检测到它.
考虑到上述要点,您可以检查添加到列表中的任何内容是否已使用以下代码正确添加:
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.
Run Code Online (Sandbox Code Playgroud)
如果断言失败,那么您在列表中添加了一些根本不支持IBase的内容,或者您为某个对象添加的特定接口引用不能作为IBase引用.如果断言通过,那么你知道这List[i]会给你一个有效的IBase值.
请注意,添加到列表中的值不需要是IBase显式值.鉴于上面的类型声明,这是安全的:
var
A: IDerived;
begin
A := TImplementation.Create;
List.Add(A);
end;
Run Code Online (Sandbox Code Playgroud)
这是安全的,因为通过TImplementation形成一个继承树实现的接口退化为一个简单的列表.没有分支,其中两个接口不相互继承但具有共同的祖先.如果有两个后代IBase,并且TImplementation两者都实现了它们,则上述代码将无效,因为IBase保留的引用A不一定是该IBase对象的"规范" 引用.断言会检测到该问题,而您需要添加它List.Add(A as IBase).
当您禁用断言时,只有在添加到列表时才支付获得类型权限的成本,而不是在从列表中读取时.我将变量命名AssertionItem为阻止您在过程中的其他位置使用该变量; 它仅用于支持断言,一旦断言被禁用,它就没有有效值.
你的考试是正确的,据我所知,你遇到的问题确实没有直接的解决办法.原因在于接口之间的继承性质,它们之间的继承只有模糊的相似之处.继承的接口是一个全新的接口,它有一些与它继承的接口相同的方法,但没有直接的连接.因此,通过选择不实现基类接口,您将特定假设已编译的程序将遵循:TImplementation不实现IBase.我认为"界面继承"有点用词不当,界面扩展更有意义!通常的做法是使基类实现基接口,而不是实现扩展接口的派生类,但是如果你想要一个实现它们的单独的类,只需列出这些接口.它有一个特定的原因,你想避免使用:
TImplementation = Class(TInterfacedObject, IDerived, IBase)
Run Code Online (Sandbox Code Playgroud)
或者只是你不喜欢它?
进一步评论
你永远不应该,甚至硬类型转换界面.当你在界面上执行"as"时,它会以正确的方式调整对象vtable指针...如果你进行硬转换(并且有方法可以调用),你的代码很容易崩溃.我的印象是你正在处理像对象这样的接口(使用继承和转换以相同的方式),而它们的内部工作确实不同!
| 归档时间: |
|
| 查看次数: |
4504 次 |
| 最近记录: |