Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi: How to implement QueryInterface of IUnknown?

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.

like image 615
Ian Boyd Avatar asked Jul 21 '10 20:07

Ian Boyd


3 Answers

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;
like image 106
Rob Kennedy Avatar answered Oct 09 '22 12:10

Rob Kennedy


Besides of Rob's remarks of breaking the rules here, you can even succeed with this construct:

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;

I didn't investigate this, but you might get some problems with reference counting...

like image 29
Uwe Raabe Avatar answered Oct 09 '22 11:10

Uwe Raabe


Based on the implementation of TObject.GetInterface in System.pas I would suggest this:

  Pointer(Obj) := TFooBar.Create(Self);
like image 25
Ville Krumlinde Avatar answered Oct 09 '22 12:10

Ville Krumlinde