It’s probably not news anymore but Delphi 2009 supports anonymous methods out of the box. To be able to support them, CodeGear developers added a new type of a “pointer to a method” called a managed method reference:
type TManagedProc = reference to procedure; TManagedIFunc = reference to function(const A : Byte) : Byte;
Many of you would say “oh no! another type of pointer to a function!”. Yes, that’s another one. But it’s much so more than a simple pointer!
But first let’s remember about the good old days when we had 2 types of pointers to functions in Delphi:
type TSimpleProc = procedure; TMethodProc = procedure of object;
The difference between these 2 is that TSimpleProc is a simple pointer to a procedure and that TMethodProc is actually a structure defined as follows (in System unit):
type TMethod = record Code: Pointer; Data: Pointer; end;
This means that TMethodProc actually contains 2 pointers: one for the method itself and one for the object it will be called upon. It should be pretty obvious why the distinction, but in any case let me “draw” an example:
procedure TForm1.Button1Click(Sender : TObject); var Method : TMethod; MethodPtr : TNotifyEvent; begin { Obtain the pointer to the function in TForm1 (assuming that Button2Click exists and is of right format)} Method.Code := MethodAddress('Button2Click'); { Set the pointer to TForm1 instance } Method.Data := Self; { Typecast this structure to a pointer to a method } MethodPtr := TNotifyEvent(Method); { Call the Button2Click(Sender). } MethodPtr(Sender); end;
OK, so what’s with this “reference to procedure” stuff I’m talking about?
This new type is actually a “managed” object in Delphi — it’s an object which is reference counted and all this funkiness.
— Oh no! says the person in the back row; I say — Oh Yes! Because managed method references are objects they are instantiated and must be destroyed so Delphi MUST keep track of all references to those – otherwise it would be your job, and that would make the whole idea useless.
Some interesting facts about this new type:
- You can assign a normal function pointer to a managed method reference.
- You can assign a normal method pointer to a managed method reference.
- Variables that are captured by anonymous methods are located in the heap and not local routine’s stack.
- Because captured variables live long, one must be careful how to manage them properly.
A call to a managed method reference is expensive – and I will show you why. Let’s take the following code as an example:
type TMyProc = reference to procedure(); TCarry = record P : TMyProc end; var C : TCarry; procedure FX; begin end; procedure Y(const MyProc : TMyProc); begin MyProc(); end; procedure TForm1.Button1Click(Sender: TObject); begin C.P(); end; procedure TForm1.FormCreate(Sender: TObject); var x : integer; begin { Case 1: Local anonymous method } Y(procedure begin x:= 10; end); { Case 2: Simple function pointer } Y(FX); { Case 3: Simple method pointer } Y(IFX); { Case 4: Local anonymous method as a reference that will be called later } C.P := procedure begin x:=x+1; end; end; procedure TForm1.IFX; begin end;
Each case (call to Y()) behaves differently:
- Local anonymous method — will call Y with a reference to the “auto-generated” method in TForm1 class (with a pretty name like TForm1.FormCreate$ActRec.$0$Body) that will perform the assignment operation directly on variable X (in heap – no copy). Still, it produces 1 call, 1 jump and a few additional instructions until the control gets there. And no, I’m not counting the additional call made to function Y; with this one it would be 2 calls.
- Simple function pointer — This one is interesting because the compiler will actually auto-generate a new anonymous method and in that method it will perform a call to FX function. That’s very expensive because you get 2 calls, one jump and a few additional instructions just to make a simple call.
- Simple method pointer — This call performs as the previous one except the fact that the anonymous method that was generated, will first copy the “address of the object” into EAX register and then call the method – that is how a standard (register) call performs.
- Local anonymous method as a reference that will be called later — in this case the same as in point 1 happens, but the “frame” in which this anonymous method exists will not be destroyed on exit from FormCreate because it’s referenced in global variable C and called later in a button press event. The interesting part is that variable X will live long after FormCreate method terminates.
The conclusion is simple: even if this new fancy reference type finally promises to “unify” all method pointers types under one wing – try to avoid it when you can. Of course there are cases when you would want to write one single function that accepts all 3 types of references — but you can just make overloads for that.
P.S. At first wanted to paste assembler code here but then I realized that would be too much 🙂 You can always use the CPU mode in Delphi’s debugger to follow the code yourselves.