5

There is an example:

type
  TDelegate = procedure of object;

  I1 = interface
  ['{31D4A1C7-668B-4969-B043-0EC93B673569}']
    procedure P1;
  end;

  TC1 = class(TInterfacedObject, I1)
    procedure P1;
  end;

...

var
  obj: TC1;
  int: I1;
  d: TDelegate;
begin
  obj := TC1.Create;
  ...
  int := obj; // "int" may contains TSomeAnotherObjectWhichImplementsI1
  d := obj.P1; // <- that's fine
  d := int.P1; // <- compiler error
end;

So how can I make last operation? I don't know which type of object will be present at "int" variable so I can't use a typecast. But i know what it will be present anyway (because if you implement an interface, you must implement all its methods). So why i can't just get a pointer to this method? Maybe there is another way? Thanks.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • You can't get a pointer to the method because there is no implementation in the interface! There's no way to get that pointer at compile time. btw, its called `procedure of object` so it's even in the name that it needs an object to work. – jpfollenius Jul 05 '11 at 10:16
  • @Smasher `int.P1` has an implementation – David Heffernan Jul 05 '11 at 10:29
  • `obj := TC1.Create` is very dangerous, I assume you know that. When you do that you're mixing two methods of life-cycle management, and that's bound to get you in trouble. – Cosmin Prund Jul 05 '11 at 11:16
  • @Cosmin I'd assumed that was for the purpose of explaining the problem. – David Heffernan Jul 05 '11 at 12:04
  • @Cosmin and @David: it is something I did at instantiation so I could use some (initialization) methods on the class that aren't available through the interfaces. As of D2010 I now create "into" the interface reference and use the initialization methods through a cast back to the class. – Marjan Venema Jul 05 '11 at 16:19

3 Answers3

4

Maybe there is another way?

The best way would be to change the code that expects the TDelegate to also accept an i1. If you wrote the code, the change is trivial, and it's basically the best you can do. If you can't change the code expecting TDelegate, and you absolutely need to call the procedure from the interface, you might want to create an adapter object, something like this:

TDelegateAdapter = class
private
  Fi1: i1;
public
  constructor Create(Ani1: i1);

  procedure P;
end;

constructor TDelegateAdapter.Create(Ani1: i1);
begin
  Fi1 := Ani1;
end;

procedure TDelegateAdapter.P;
begin
  Fi1.P1;
end;

Then in the code where you need to assign the TDelegate, do something like this:

var Adapter: TDelegateAdapter;
    Intf: i1; // assumed assigned
    ObjectExpectingDelegate: TXObject; // assumed assigned
begin
  Adapter := TDelegateAdapter.Create(Intf);
  try
    ObjectExpectingDelegate.OnSomething := Adapter.P;
    try
      ObjectExpectingDelegate.PerformWork;
    finally ObjectExpectingDelegate.OnSomething := nil;
    end;
  finally Adapter.Free;
  end;
end;

Edit

If you're on a Delphi version that supports anonymous methods you can implement the Delegate adapter using such anonymous methods, those only requiring one "adapter" per procedure signature. Delphi implements anonymous methods behind the scenes using Interfaces, so runtime performance would be good, no need to worry.

The code below is a demonstrative console implementation of the anonymous delegate adapter. Take a look straight at the final begin - end block to see the magic.

program Project29;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type

  // This is the type of the anonymous method I want to use
  TNoParamsProc = reference to procedure;

  // This implements the "delegate" adapter using an anonymous method
  TAnonymousDelegateAdapter = class
  private
    NoParamsProc: TNoParamsProc;
  public
    constructor Create(aNoParamsProc: TNoParamsProc);

    procedure AdaptedDelegate;
  end;

  { TAnonymousDelegateAdapter }

  procedure TAnonymousDelegateAdapter.AdaptedDelegate;
  begin
    NoParamsProc;
  end;

  constructor TAnonymousDelegateAdapter.Create(aNoParamsProc: TNoParamsProc);
  begin
    NoParamsProc := aNoParamsProc;
  end;

  // --------- test code follows ----------

type

  // Interface defining a single method.
  ISomething = interface
    procedure Test;
  end;

  // Implementation of the interface above
  TSomethingImp = class(TInterfacedObject, ISomething)
  public
    procedure Test;
  end;

  // Definition of delegate
  TNoParamsDelegate = procedure of object;

  { TSomethingImp }

  procedure TSomethingImp.Test;
  begin
    WriteLn('Test');
  end;

// ---- Test program to see it all in action. ---

var intf: ISomething;
    Dlg: TNoParamsDelegate;

begin
  intf := TSomethingImp.Create;
  // Here I'll create the anonymous delegate adapter, notice the "begin - end"
  // in the constructor call; That's the anonymous method. Runtime performance
  // of anonymous methods is very good, so you can use this with no warries.
  // My anonymous method uses the "intf" variable and calls the method "Test"
  // on it. Because of that the "intf" variable is "captured", so it doesn't run
  // out of scope as long as the anonymous method itself doesn't run out of scope.
  // In other words, you don't risk having your interface freed because it's reference
  // count reaches zero. If you want to use an other interface, replace the code
  // in the begin-end.
  with TAnonymousDelegateAdapter.Create(procedure begin intf.Test; end) do
  try
    Dlg := AdaptedDelegate;
    Dlg;
  finally Free;
  end;

  Readln;
end.
Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104
  • Thanks, but that is not an universal solution. Anytime when I want to delegate some method of interface I'll need to make a new adapter, which invokes interface's method. But if there is no another solution then I have no choice... – stack-overflower Jul 06 '11 at 05:08
  • The problem's that Interface method and "of object" procedures are really not compatible: a `"procedure of object"` is a pair of pointers, a pointer to the method itself plus a method to the `Self` of the object the method operates on. With interface you can't even get the address of a method, the compiler doesn't let you, and the `Self` of the interface is actually a pointer to the `VMT`. [And here's my implementation of interface not backed by object!](http://stackoverflow.com/questions/6278381/delphi-rtti-for-interfaces-in-a-generic-context/6278822#6278822). You absolutely need an adapter. – Cosmin Prund Jul 06 '11 at 06:29
  • [...] that being said, see my edit, if you're on Delphi 2010+ you've got Anonymous Methods, you can use that to simplify the implementation of the adapters. – Cosmin Prund Jul 06 '11 at 06:30
2

I'd imagine that at least one reason why the compiler is blocking this is that procedure of object is not a managed type and so you would be bypassing interface reference counting.

Another reason why this would be disallowed is that the invoking mechanism for an interface method is different from the invoking mechanism for procedure of object.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
0

I will suggest probably unlikely option for you, but it's switching to freepascal/lazarus

I checked, your code fragments compile and work there.

Maksee
  • 2,311
  • 2
  • 24
  • 34