views:

178

answers:

3

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.

+2  A: 

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

  Pointer(Obj) := TFooBar.Create(Self);
Ville Krumlinde
That does not invoke correct interface reference counting.
Remy Lebeau - TeamB
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.
Rob Kennedy
You are right _AddRef is needed, I should have compiled and tried it before posting.
Ville Krumlinde
+5  A: 

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;
Rob Kennedy
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.
Ian Boyd
+2  A: 

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

Uwe Raabe
That certainly is a slick idea. But i think you're right, i would have to add a call to `_AddRef` somewhere in there.
Ian Boyd
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.
Rob Kennedy
The QueryInterface() call on the TFooBar object handles the needed _AddRef() for you. QueryInterface() always increments the reference count of the interface it returns.
Remy Lebeau - TeamB
As I said, I didn't investigate. I knew there are wiser people around already knowing the answer ;)
Uwe Raabe