4

Delphi allows for interface delegation using the implements keyword.

For example

IIndep1 = interface
  function foo2: integer;
end;

IIndep2 = interface
  function goo2: integer;
end;

TIndep1And2 = class(TInterfacedObject, IIndep1, IIndep2)
private
  FNested : IIndep1; //e.g. passed via constructor or internally created (not shown here)
public
   Constructor Create(AIndep1: IIndep1);
  function goo2: integer;
   property AsIndep1 : IIndep1 read FNested implements IIndep1;
end;

That works well, but not for inherited interfaces. (Error message "Missing implementation of interface method ILev1.foo")

ILev1 = interface
  function foo: Integer;
end;

ILev2 = interface(ILev1)
  function goo: Integer;
end;

TLev2Fails = class(TInterfacedObject, ILev1, ILev2) //Also fails with ILev2 alone (Error: "ILev1 not mentioned in interface list")
private
  FNested : ILev1; //passed via constructor or internally created 
public
   Constructor Create(AILev1: ILev1);
   function goo: Integer;
   property AsLev1 : ILev1 read FNested implements ILev1;
end;

The workaround is to add an extra ancestor class

TLev1Wrapper = class(TInterfacedObject, ILev1)
private
  FNested : ILev1; //passed via constructor or internally created 
public
   Constructor Create(AILev1: ILev1);
   property AsLev1 : ILev1 read FNested implements ILev1;
end;

TLev2Works = class(TLev1Wrapper, ILev2)
public
  function goo: Integer;
end;

Is there a way to avoid the wrapper class ancestor?

[EDIT] Just a note on interface delegation, the purpose of using implements is to avoid satisfying the interface directly, but passing that requirement to an aggregated or composed member. Providing the full interface and manually delegating to a composed member defeats the benefits gained from using implements to direct the interface. In fact, in that case the implements keyword and property may be removed.

Jasper Schellingerhout
  • 1,070
  • 1
  • 6
  • 24

2 Answers2

2

This looks like the compiler is attempting to enforce the expectations (read: requirements) of IUnknown.QueryInterface:

For any one object, a specific query for the IUnknown interface on any of the object's interfaces must always return the same pointer value.

If you were able to delegate the implementation of a base interface whilst implementing a derived interface yourself then:

obj := TLev2Fails.Create(otherLev1);  // Assuming your class could compile

lev1 := obj as ILev1;     // yields reference to otherLev1 implementor
lev2 := obj as ILev2;     // yields reference to TLev2Fails instance

unk1 := lev1 as IUnknown;   // returns IUnknown of otherLev1 implementor
unk2 := lev2 as IUnknown;   // returns IUnknown of obj TLev2fails instance

This would not be the case if your nested object were correctly implemented as a TAggregatedObject derived class, but the compiler has no way of knowing whether this is the case let alone enforcing it so instead it appears it simply requires that if you implement a derived interface then you must also directly implement any interfaces that interface itself inherits.

The compiler error in this situation isn't being very helpful, though it could be read as telling you what you need to do, just not why you need to do it in this case, which is not that unusual for compiler errors.

If you wish to delegate in this case then you must "delegate manually".

Whilst this loses the benefit of the implements facility, it does at least retain the benefits of re-use, just not quite as conveniently.

NOTE: Even if your delegated implementation is based on a TAggregatedObject, the compiler still cannot determine that these implementation details satisfies the requirements of QueryInterface so you will still get this error (even if using a class reference for the delegated interface).

You must still delegate manually.

Having said all that, I cannot currently see how this is any different for the case when interfaces are involved with no inheritance relationship, but it is quite possible that this is valid and I just haven't worked through all the necessary 'thought experiments' to prove it to myself. :)

It may be that the compiler is just being extra cautious in situations when it thinks it can/should be and the documentation simply fails to mention this resulting limitation of implements.

Or it may be a bug in the compiler, though I think there are sufficiently apparent reasons for the behaviour and the behaviour is itself so well established and consistent (having reproduced the exact same behaviour in Delphi 7) that an omission in relevant documentation is the more likely explanation.

Deltics
  • 22,162
  • 2
  • 42
  • 70
  • Can you post a solution using `TAggregatedObject` please? Querying a delegated interface vs. the directly implemented interface will end at different objects, this is expected. Your example changed for the `TIndep1And2` will produce the similar results with `IUnknown` of two different objects. The inherited case the problem is worse. Your example could have had `lev2 as ILev1` then you would have had `ILev1` on two objects. That said, if its not enforced in one case, why the other? – Jasper Schellingerhout Aug 19 '15 at 03:23
  • Using **TAggregatedObject** still does not facilitate the use of **implements**: You still have to delegate manually. **TAggegratedObject** simply takes care of reflecting the IUnknown of the 'container' object. As for why in one case and not the other: the compiler cannot be as certain that independent interfaces should not be implemented independently as it can be that inherited interfaces should not. This explains a potential rationale for the difference in behaviour, but does not alter the fact that the compiler can be wrong in either case. – Deltics Aug 19 '15 at 03:56
  • I agree with the part where you said "I can't see how this is any different from the case where interfaces are involved with no inheritance relationship". Something I do quite often. I deleted my comment but will add it back. This has to be a compiler error. – Graymatter Aug 19 '15 at 04:23
  • What about the situation where you have a single class with 2 `implements`? Wouldn't that run into the same problem. If you look at this question of mine and David's answer (http://stackoverflow.com/questions/22649433/delphi-interface-reference-counting), I think that the "object" as Delphi sees it changes depending on what interface is being accessed and how it is accessed (and also depending on which version of Delphi is being used). So while we see the same object in our code, the object for the interface is the object being reference counted and that is what is used for IUnknown. – Graymatter Aug 19 '15 at 04:35
  • @graymatter - any objects involved are entirely implementation dependent, the compiler cannot do anything to influence that. There is no compiler magic that can change what your implementation of QueryInterface yields. I don't know why you think it depends on the Delphi version being used. As far as I could tell the behaviour is consistent from Delphi 7 thru Delphi XE8. Clearly it is a "compiler error", but that does not necessarily mean it is a bug. Just an error according to the compiler rules in the same way that assigning an integer to a string results in a compiler error. :) – Deltics Aug 19 '15 at 07:05
  • Have a look at the question I linked. The underlying object is different depending on which version of Delphi is being used. For example, in XE2 and earlier, the underlying object for the `Supports` when using `implements` like this `property CC: TObjectC read FObjectC implements IInterfaceY` is the object that implements the interface. In XE3 and above, it's the object that includes the implements declaration. This is different from using a method to return the implementor. – Graymatter Aug 19 '15 at 07:12
  • @graymatter - your question involved other complications which explains the differences seen in Delphi versions. Specifically the explicit involvement of IInterface and class-type delegation. And yes, using two implements clauses does indeed satisfy the compiler leading to the possibility that the compiler has accepted an incorrect implementation. But this is no different than any number of other such devices to silence the compiler's complaints when you are **potentially** doing things wrong that the compiler is otherwise trying to save you from etc. – Deltics Aug 19 '15 at 07:18
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/87406/discussion-between-graymatter-and-deltics). – Graymatter Aug 19 '15 at 17:53
0

Is there a way to avoid the wrapper class ancestor?

No, the inherited interface has to be bound to a an ancestor class. Delphi does not implicitly bind inherited interfaces, because of reasons explained here. Also, it cannot bind to the current class, unless it manually delegates all calls to the composed item.

We are left with only one option. An ancestor wrapper class, to which we can bind ILev1

Community
  • 1
  • 1
Jasper Schellingerhout
  • 1,070
  • 1
  • 6
  • 24