Wednesday, April 13, 2011

Anonymous Methods as Events in Delphi

Since Delphi 2009 we have anonymous methods and since Delphi 1.0 we have events.

Combining those two together would be probably a trivial task for CodeGear, but that was never done. The excuse is that anonymous method references are managed types, and events are just method pointers, thus "it would be easy to create ill-typed programs with dangling pointers or memory leaks."

While that is a truth, its only one part of it. Another part is that compiler is already smart enough to:

1) create an instance object from IInterface, similar to TInterfacedObject but without constructor/destructor;

2) add anonymous method as a virtual method to this instance;

3) manage reference counting for this instance.

Now calling an anonymous method is a matter of picking up its address from instance's VMT. The offset is $0C, because $00, $04 and $08 are taken by QueryInterface(), _AddRef() and _Release().

About parameters passing. Interface methods expects first parameter to be a pointer to instance implementing this interface, while event handlers, being a method pointers, expects first parameter to be a pointer to an object instance, aka self.

That means that a) our anonymous method already have a placeholder for implicit self parameter, and b) it is useless. Since every event has at least a sender parameter, self could be totally avoided.

Finally, a few words about the reference counting. Since we are not going to store a valid reference to anonymous interface instance, it would be deallocated once out of scope. But since we got a grip on method's address, and self is not used, that should not be a big problem as long as method's body stays in memory.

Putting all together, here is an example of anonymous method being used as an event handler for a Button:

@Button1.OnClick := pPointer(Cardinal(pPointer( procedure (sender: tObject) begin ((sender as TButton).Owner as TForm).Caption := 'Freedom to anonymous methods!' end )^ ) + $0C)^;

Nothing new if you follow the discussion above. The code was tested in Delphi 2010.

Don't do it at home, use at own risk!

2 comments:

  1. The reason events (method pointers) were not changed to method references is because it would be a huge change with massive backward compatibility ramifications, breaking our customers' code, reducing their incentive to upgrade, reducing our revenues, etc. In other words, it was not "an excuse" - it was the only responsible thing to do.

    If you really want to squeeze an anonymous method into a method pointer, I already wrote an article about it - http://blog.barrkel.com/2010/01/using-anonymous-methods-in-method.html

    The way you are currently doing it - typecasting to Cardinal - is ill advised with the move to 64-bit. Better to use a typecast to PByte instead, which supports pointer arithmetic.

    You're also wrong about Self not being important; Self and Sender serve two very different purposes. Sender references the control to which this particular triggering of the event relates; it is defined on the sending side. Self is provided by the subscriber of the event; it is defined on the receiving side. This division, between the sender of events and the receiver of events, cannot be simply split up, because it is normal for two different teams - often two different companies - to be responsible for each half of the code.

    ReplyDelete
  2. Thanks for clarifications, Barry.

    Yes, I was speculating a little saying it would be trivial. Still a pretty nice feature to have.

    Regarding Self, sure it is important. But it points to wrong instance, so something like this:

    Caption := 'Hello There!'

    will not work. That's why we look for Form instance via Owner.

    ReplyDelete