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 TSomeAnotherObjectWhichImplements开发者_C百科I1
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.
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.
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
.
I will suggest probably unlikely option for you, but it's switching to freepascal/lazarus
I checked, your code fragments compile and work there.
精彩评论