Delphi: How to implement QueryInterface of IUnknown?
Asked Answered
R

3

12

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.

Rhu answered 21/7, 2010 at 20:7 Comment(2)
I encourage you to post a question about your "real problem" as well. It sounds interesting, and I have a vague idea of how to do it (something about aggregates and containers). You might find that you're writing more code than you need to.Eichmann
@Rob Kennedy If you can think of a useful title for it, that would generate interest, i'd definitely do it. i find that without a good hook, question goes unanswered. This question was good, because the question, as phrased, sounded easy - so i was sucking in people who thought they'd be able to give me an easy schooling. On the other hand, you and Uwe seem to patrol for Delphi questions :)Rhu
E
6

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;
Eichmann answered 21/7, 2010 at 20:39 Comment(1)
i can solve the one problem (always return the same IUnknown) by using the implements keyword, and delegate the new interface to an adapter class created in the property getter. But yes, that still causes the other problem of new objects each time. i would have to cache n-objects, one for each of the n-supported interfaces. sigh Updated the original question with the actual problem i'm trying to solve.Rhu
O
3

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...

Oberstone answered 21/7, 2010 at 20:46 Comment(4)
That certainly is a slick idea. But i think you're right, i would have to add a call to _AddRef somewhere in there.Rhu
I don't think you'll have reference-counting issues here. The reference count will be 1 while the constructor runs. When the constructor returns, the count goes to zero, but the object does not delete itself (see TInterfacedObject.AfterConstruction). Then the new object's QueryInterface method is called, and it sets the reference count for you.Eichmann
The QueryInterface() call on the TFooBar object handles the needed _AddRef() for you. QueryInterface() always increments the reference count of the interface it returns.Essa
As I said, I didn't investigate. I knew there are wiser people around already knowing the answer ;)Oberstone
A
2

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

  Pointer(Obj) := TFooBar.Create(Self);
Allot answered 21/7, 2010 at 20:37 Comment(2)
You'll need to call _AddRef afterward. To call that, you'll need to type-cast Obj to an interface type. Better to just cast to an interface type in the first place, as in my answer.Eichmann
You are right _AddRef is needed, I should have compiled and tried it before posting.Allot

© 2022 - 2024 — McMap. All rights reserved.