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!