Delphi: How to implement QueryInterface of IUnknown?

Ian Boyd picture Ian Boyd · Jul 21, 2010 · Viewed 9k times · Source

In Delphi, IUnknown is declared as:

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

Note: The output parameter is untyped

In my TInterfacedObject descendant i need to handle QueryInterface, so i can return an object that supports the requested interface:

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;

The problem comes on the line:

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

Delphi complains:

Operator not applicable to this operand type

Obviously i don't know how or what to assign to an untyped out parameter. i can randomly try things, in hopes that the compiler will stop complaining:

Obj := TFooBar.Create(Self);

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

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

Ignoring all the code i've written (if required): how do i implement QueryInterface in an object descendant from TInterfacedObject?


The real problem i've been trying to solve can be boiled down to i want to:

i want to override methods in an interface

In the same way:

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

can be overridden in a descendant class:

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

i want to so the same with interfaces:

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;

Problem is that how i have to begin loading up my implementing object with:

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;

And there isn't just the two methods in IFoo, there's 6. And then if i want to add another supported interface:

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;

And things get really unwieldy very fast.

Answer

Rob Kennedy picture Rob Kennedy · Jul 21, 2010

You need to type-cast the left side of the assignment statement. That way, the untyped parameter has a type, and the compiler knows how to assign it a value:

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

Please note that you're breaking one of the requirements of COM. If you query for an interface, you should be able to query the result for IUnknown and always get the same value:

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

If you just want to generate new objects of type TFooBar, then give your interface a method that generates those:

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