Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cast a TInterfacedObject to an interface

According to the Delphi docs, I can cast a TInterfacedObject to an interface using the as operator.

But it doesn't work for me. The cast gives a compile error: "Operator not applicable to this operand type".

I'm using Delphi 2007.

Here is some code (a console app). The line that contains the error is marked.

program Project6;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  IMyInterface = interface
    procedure Foo;
  end;

  TMyInterfacedObject = class(TInterfacedObject, IMyInterface)
  public
    procedure Foo;
  end;

procedure TMyInterfacedObject.Foo;
begin
  ;
end;

var
  o: TInterfacedObject;
  i: IMyInterface;
begin
  try
    o := TMyInterfacedObject.Create;
    i := o as IMyInterface;  // <--- [DCC Error] Project6.dpr(30): E2015 Operator not applicable to this operand type
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.
like image 807
dan-gph Avatar asked Mar 27 '11 08:03

dan-gph


1 Answers

Quick answer

Your interface needs to have a GUID for the as operator to work. Go to the first line after IMyInterface = interface, before any method definitions and hit Ctrl+G to generate a new GUID.

Longer comment

The as operator for interfaces requires a GUID because it calls IUnknown.QueryInterface, and that in turn requires a GUID. That's all right if you run into this problem when casting an INTERFACE to an other kind of INTERFACE.

You're not supposed to cast an TInterfacedObject to an interface in the first place, because that means you're holding both a reference to the implementing object (TInterfacedObject) and a reference to the implemented interface (IMyInterface). That's problematic because you're mixing two lifecycle management concepts: TObject live until something calls .Free on them; You're reasonably sure nothing calls .Free on your objects without your knowledge. But interfaces are reference-counted: when you assign your interface to a variable the reference counter increases, when that instance runs out of scope (or is assigned something else) the reference counter is decreases. When the reference counter hits ZERO the object is disposed of (.Free)!

Here's some innocent-looking code that's going to get into a real lot of trouble fast:

procedure DoSomething(If: IMyInterface);
begin
end;

procedure Test;
var O: TMyObjectImplementingTheInterface;
begin
  O := TMyObjectImplementingTheInterface.Create;
  DoSomething(O); // Works, becuase TMyObject[...] is implementing the given interface
  O.Free; // Will likely AV because O has been disposed of when returning from `DoSomething`!
end;

The fix is very simple: Change the O's type from TMyObject[...] to IMyInterface, like this:

procedure DoSomething(If: IMyInterface);
begin
end;

procedure Test;
var O: IMyInterface;
begin
  O := TMyObjectImplementingTheInterface.Create;
  DoSomething(O); // Works, becuase TMyObject[...] is implementing the given interface
end; // `O` gets freed here, no need to do it manually, because `O` runs out of scope, decreases the ref count and hits zero.
like image 78
Cosmin Prund Avatar answered Oct 04 '22 03:10

Cosmin Prund