Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I store an interface method in a method pointer?

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.

like image 585
stack-overflower Avatar asked Jul 05 '11 10:07

stack-overflower


2 Answers

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.
like image 164
Cosmin Prund Avatar answered Sep 30 '22 00:09

Cosmin Prund


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.

like image 27
David Heffernan Avatar answered Sep 30 '22 02:09

David Heffernan