我正在学习COM和接口,并有以下实验代码:
type
IA = interface(IInterface)
['{C9C5C992-3F67-48C5-B215-7DCE6A61F0E8}']
end;
IB = interface(IA)
['{F1799437-AD12-471B-8716-F1D93D1692FC}']
end;
IC = interface(IB)
['{01780E8C-C47D-468E-8E42-4BFF3F495D51}']
end;
TBO = class(TInterfacedObject, IB)
end;
procedure TForm1.FormCreate(Sender: TObject);
var
x: TBO;
a: IInterface;
begin
x := TBO.Create;
IInterface(x)._AddRef;
if Assigned(TBO.GetInterfaceEntry(IA)) then memo1.lines.add('GetInterfaceEntry IA: OK'); // Why not?
if Assigned(TBO.GetInterfaceEntry(IB)) then memo1.lines.add('GetInterfaceEntry IB: OK');
if Assigned(TBO.GetInterfaceEntry(IC)) then memo1.lines.add('GetInterfaceEntry IC: OK');
if x.QueryInterface(IA, a)=S_OK then memo1.lines.add('QueryInterface TA: OK'); // Why not?
if x.QueryInterface(IB, a)=S_OK then memo1.lines.add('QueryInterface TB: OK');
if x.QueryInterface(IC, a)=S_OK then memo1.lines.add('QueryInterface TC: OK');
if Supports(TBO, IA) then memo1.lines.add('Supports TA: OK'); // Why not?
if Supports(TBO, IB) then memo1.lines.add('Supports TB: OK');
if Supports(TBO, IC) then memo1.lines.add('Supports TC: OK');
if Supports(x, IA, a) then memo1.lines.add('Supports(2) TA: OK'); // Why not?
if Supports(x, IB, a) then memo1.lines.add('Supports(2) TB: OK');
if Supports(x, IC, a) then memo1.lines.add('Supports(2) TC: OK');
end;
输出:
GetInterfaceEntry IB: OK
QueryInterface TB: OK
Supports TB: OK
Supports(2) TB: OK
但我需要:
GetInterfaceEntry IA: OK
GetInterfaceEntry IB: OK
QueryInterface TA: OK
QueryInterface TB: OK
Supports TA: OK
Supports TB: OK
Supports(2) TA: OK
Supports(2) TB: OK
据我所知,由于接口继承,IB
是IA
的超集。据我所知,由于TBO
实现IB
,它会自动实现IA
。但为什么Supports()
,QueryInterface()
,GetInterfaceEntry()
会返回false?
如何直接或间接地查询TBO
是否实现IA
,即通过实施IA
的超集?我需要两者,像GetInterfaceEntry
这样的静态类函数和像QueryInterface
这样的动态对象引用变体。
答案 0 :(得分:5)
这是Delphi的一个已知怪癖。即使IB
继承自IA
,TBO
也必须明确指定IA
和IB
,以便Supports()
检索这两个接口。
TBO = class(TInterfacedObject, IA, IB)
我忘了这个的技术原因。与编译器如何为TBO
生成接口表的限制有关。它不会自动包含继承的接口。
答案 1 :(得分:5)
作为Marco Cantù explains here:接口"继承"与类继承不同,实际上将它称为接口扩展可能更好。
如果我们假设一个类自动实现了一个基接口,那么会出现一些严重的异常。
考虑:
IA = interface
<guid>
end;
IA1 = interface(IA)
<guid>
end;
IA2 = interface(IA)
<guid>
end;
一个聚合IA1和IA2的类,其中两个都被委派:
TAggregate = class(TInterfacedObject, IA1, IA2)
private
FIA1: IA1;
FIA2: IA2;
protected
property ImplIA1: IA1 read FIA1 implements IA1;
property ImplIA2: IA2 read FIA2 implements IA2;
end;
现在,如果你问一个TAggregate实例的IA接口,应该返回哪个实现?
关键是接口&#34;继承&#34;不是真正的继承。因此,该对象并没有实现一个&#34;祖先&#34;界面,除非明确这样做。
还有一些其他事情要注意:
如果您将方法procedure MethodIA;
添加到IA界面并且执行也明确地将IA
添加为TAggregate
上的接口,则代码将无法编译。你得到错误:
未声明的标识符:&#39; MethodIA&#39;
您必须直接在课程中添加MethodIA
,或者选择将FIA1
或FIA2
委托给哪个实施。例如。选择FIA2:
property ImplIA: IA2 read FIA2 implements IA;
此属性声明显示了Delphi识别接口 is-a 关系的方式。即不是通过实现类,而是通过接口引用。
注意:即使在编译时,编译器也不会假定类实现了一个基接口,除非明确说明。
TIA1Implementor = class(TInterfacedObject, IA1)
protected
procedure MethodIA;
end;
var
LImplObj: TIA1Implementor;
LA1: IA1;
LA: IA;
begin
LImplObj := TIA1Implementor.Create;
LA1 := LImplObj; //Valid: TIA1Implementor implements IA1
LA := LImplObj; //Does not compile: TIA1Implementor does not implement IA
LA := LA1; //Valid: IA1 is an extension of IA
end;
在编译时,扩展名是已知的,并且为其基础分配扩展名引用是完全合法的(没有任何强制转换或&#34;支持&#34;检查)。但直接从对象引用中分配不是。
有趣的是,TypInfo
单元显示接口类型确实知道他们的父母&#34;接口
PTypeData = ^TTypeData;
TTypeData = packed record
case TTypeKind of
tkInterface: (
IntfParent : PPTypeInfo; { ancestor }
IntfFlags : TIntfFlagsBase;
Guid : TGUID;
IntfUnit : ShortStringBase;
{PropData: TPropData});
然而,鉴于上述讨论,我不确定这些知识如何在运行时受益。我更倾向于编译时检查。