【问题标题】:Delphi: How to implement QueryInterface of IUnknown?Delphi:如何实现 IUnknown 的 QueryInterface?
【发布时间】:2011-03-19 05:12:34
【问题描述】:

在 Delphi 中,IUnknown 被声明为:

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

注意:输出参数无类型

在我的TInterfacedObject 后代中,我需要处理QueryInterface,所以我可以返回一个支持请求接口的对象:

function TFoo.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
   if IsEqualGUID(IID, IFooBar) then
   begin
      Obj := (TFooBar.Create(Self) as IFooBar);
      Result := S_OK;
   end
   else
      Result := inherited QueryInterface(IID, {out}Obj);
end;

问题来了:

Obj := (TFooBar.Create(Self) as IFooBar);

德尔福抱怨:

运算符不适用于此操作数类型

显然我不知道如何或将什么分配给 untyped out 参数。我可以随机尝试一些东西,希望编译器不会抱怨:

Obj := TFooBar.Create(Self);

Obj := Pointer(TFooBar.Create(Self));

Obj := Pointer(TFooBar.Create(Self) as IFooBar);

忽略我编写的所有代码(如果需要):如何在 TInterfacedObject 的后代对象中实现 QueryInterface


我一直试图解决的真正问题可以归结为我想要:

我想覆盖接口中的方法

同理:

TList = class(TObject)
...
   function GetItem(Index: Integer): Pointer; 
   procedure SetItem(Index: Integer; Value: Pointer);
   property Items[Index: Integer]: Pointer read GetItem write SetItem;
end;

可以在后代类中被覆盖:

TStudentList = class(TList)
...
   function GetItem(Index: Integer): TStudent; 
   procedure SetItem(Index: Integer; Value: TStudent);
   property Items[Index: Integer]: TStudent read GetItem write SetItem;
end;

我想对接口也一样:

IFoo = interface(IUnknown)
...
   function GetItem(Index: Variant): Variant; 
   procedure SetItem(Index: Variant; Value: Variant);
   property Items[Index: Variant]: Variant read GetItem write SetItem;
end;

IFooGuidString = interface(IFoo)
...
   function GetItem(Index: TGUID): string ; 
   procedure SetItem(Index: TGUID; Value: string );
   property Items[Index: TGUID]: string read GetItem write SetItem;
end;

问题是我必须如何开始加载我的实现对象:

TFoo = class(TInterfacedObject, IFoo, IFooGuidString)
public
   function IFoo.GetItem = FooGetItem;
   procedure IFoo.SetItem = FooSetItem;
   function FooGetItem(Index: Variant): Variant; 
   procedure FooSetItem(Index: Variant; Value: Variant);

   function IFooGuidString.GetItem = FooGuidStringGetItem;
   procedure IFooGuidString.SetItem = FooGuidStringSetItem;
   function FooGuidStringGetItem(Index: TGUID): string ; 
   procedure FooGuidStringSetItem(Index: TGUID; Value: string );
end;

IFoo 中不仅有两个方法,还有 6 个。然后如果我想添加另一个支持的接口:

IFooInt64String = interface(IFoo)
...
   function GetItem(Index: Int64): string ; 
   procedure SetItem(Index: Int64; Value: string );
   property Items[Index: Int64]: string read GetItem write SetItem;
end;


TFoo = class(TInterfacedObject, IFoo, IFooGuidString)
public
   function IFoo.GetItem = FooGetItem;
   procedure IFoo.SetItem = FooSetItem;
   function FooGetItem(Index: Variant): Variant; 
   procedure FooSetItem(Index: Variant; Value: Variant);

   function IFooGuidString.GetItem = FooGuidStringGetItem;
   procedure IFooGuidString.SetItem = FooGuidStringSetItem;
   function FooGuidStringGetItem(Index: TGUID): string ; 
   procedure FooGuidStringSetItem(Index: TGUID; Value: string );

   function IFooInt64String.GetItem = FooInt64StringGetItem;
   procedure IFooInt64String.SetItem = FooInt64StringSetItem;
   function FooInt64StringGetItem(Index: Int64): string ; 
   procedure FooInt64StringSetItem(Index: Int64; Value: string );
end;

而且事情变得非常笨拙。

【问题讨论】:

  • 我鼓励你也发布一个关于你的“真正问题”的问题。这听起来很有趣,而且我对如何做有一个模糊的想法(关于聚合容器)。您可能会发现自己编写的代码超出了您的需要。
  • @Rob Kennedy 如果您能想到一个有用的标题,这会引起人们的兴趣,我肯定会这样做。我发现如果没有一个好的hook,问题就没有答案。这个问题很好,因为这个问题,用措辞来说,听起来很简单——所以我在吸引那些认为他们能够给我一个轻松的教育的人。另一方面,你和 Uwe 似乎在为 Delphi 问题巡逻 :)

标签: delphi interface iunknown queryinterface


【解决方案1】:

您需要在赋值语句的 left 侧进行类型转换。这样,无类型参数就有了类型,编译器知道如何给它赋值:

IFooBar(Obj) := TFooBar.Create(Self) as IFooBar;

请注意,您正在破坏requirements of COM 之一。如果你查询一个接口,你应该能够查询 IUnknown 的结果并且总是得到相同的值:

Foo.QueryInterface(IUnknown, I1);
I1.QueryInterface(IFooBar, B);
B.QueryInterface(IUnknown, I2);
Assert(I1 = I2);

如果你只想生成 TFooBar 类型的新对象,那么给你的接口一个生成这些对象的方法:

function TFoo.NewFooBar: IFooBar;
begin
  Result := TFooBar.Create(Self) as IFooBar;
end;

【讨论】:

  • 我可以通过使用implements 关键字来解决一个问题(总是返回相同的IUnknown),并将新接口委托给在属性getter 中创建的适配器类。但是,是的,这仍然会导致每次出现新对象的另一个问题。我将不得不缓存n-objects,每个n-supported 接口一个。 sigh 用我要解决的实际问题更新了原始问题。
【解决方案2】:

除了 Rob 在这里打破规则的言论之外,您甚至可以通过以下构造获得成功:

function TFoo.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
   if IsEqualGUID(IID, IFooBar) then
      Result := TFooBar.Create(Self).QueryInterface(IID, obj)
   else
      Result := inherited QueryInterface(IID, {out}Obj);
end;

我没有对此进行调查,但您可能会在引用计数方面遇到一些问题...

【讨论】:

  • 这当然是一个巧妙的想法。但我认为你是对的,我必须在其中某处添加对 _AddRef 的调用。
  • 我认为您不会在这里遇到引用计数问题。构造函数运行时,引用计数将为 1。当构造函数返回时,计数变为零,但对象不会自行删除(请参阅TInterfacedObject.AfterConstruction)。然后调用新对象的QueryInterface 方法,并为您设置引用计数。
  • 对 TFooBar 对象的 QueryInterface() 调用会为您处理所需的 _AddRef()。 QueryInterface() 总是增加它返回的接口的引用计数。
  • 正如我所说,我没有调查。我知道周围有更聪明的人已经知道答案了;)
【解决方案3】:

基于 System.pas 中 TObject.GetInterface 的实现,我建议这样做:

  Pointer(Obj) := TFooBar.Create(Self);

【讨论】:

  • 之后您需要致电_AddRef。要调用它,您需要将Obj 类型转换为接口类型。就像我的回答一样,最好首先转换为接口类型。
  • 你说得对 _AddRef 是需要的,我应该在发布之前编译并尝试过。
猜你喜欢
  • 2019-06-18
  • 2013-05-18
  • 1970-01-01
  • 1970-01-01
  • 2012-04-10
  • 1970-01-01
  • 2011-07-23
  • 2011-06-19
  • 1970-01-01
相关资源
最近更新 更多