Wednesday, November 23, 2011

Making Integer 4 bytes for x64 target comes with price

A whole generation of programmers grew in assumption that Integer and Pointer will be the same size forever.

function EventToVariant(const AMethod: TNotifyEvent): Variant;
begin
  Result := 'vgmet' + IntToHex(Integer(TMethod(AMethod).Data), 8) + IntToHex(Integer(TMethod(AMethod).Code), 8);
end;
The generic integer types are Integer and Cardinal, use these whenever possible, since they result in the best performance for the underlying CPU and operating system.
So they use them whenever possible.
function ObjectToVariant(const AObject: TObject): Variant;
begin
  Result := 'vgobj' + IntToStr(Integer(Pointer(AObject)));
end;
Sometime its hard to see any problem at all.
    TMethod(Result).Data := Pointer(StrToInt('$' + Copy(S, 6, 8)));
    TMethod(Result).Code := Pointer(StrToInt('$' + Copy(S, 14, 8)));

(Source code snippets taken from FMX.Types unit).

Yes, probably most of the pointers are way below 4GB. Methods will probably always be there (unless you manage to set the image base above that range). But the larger your project grow, the sooner it will start producing pointers above 4GB, and its the time when FireMonkeys' DataBinding (which relies heavily on the code above) may fail.

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!