When I'm in a TForm
(say, MyForm
) that has a procedure oneProc
(procedure TMyForm.oneProc
), if I do inside any procedure of MyForm
something like TThread.Queue(nil, oneProc);
, how does it end up that when oneProc()
is fired that Self
is well initialized to MyForm
?
Can you explain why this works?
procedure TMyForm.Button1Click(Sender: TObject);
begin
ProcA(ProcB);
end;
procedure TMyForm.ProcA(const Aproc: Tproc);
begin
Aproc;
end;
procedure TMyForm.ProcB;
begin
showmessage(self.className); // << this show TTMyForm (how?)
end;
CodePudding user response:
First off, TThread.Queue()
is overloaded to accept both TThreadMethod
(non-anonymous) and TThreadProcedure
(anonymous) parameters. Your text description is for code that would call the non-anonymous version. Your code example, on the other hand, is doing something a little different than what your described TThread.Queue()
code would be doing. But in any case, to answer your question -
A non-anonymous method pointer actually carries 2 values within it - one is the address of the method, and one is the value passed to the method's Self
parameter (a method pointer is represented by the TMethod
record).
TProc
is a reference to an anonymous method, and a non-anonymous method pointer can be assigned to an anonymous method reference. The documentation even says so, in the section on "Using Anonymous Methods":
Method references can also be assigned to methods as well as anonymous methods. For example:
type TMethRef = reference to procedure(x: Integer); TMyClass = class procedure Method(x: Integer); end; var m: TMethRef; i: TMyClass; begin // ... m := i.Method; //assigning to method reference end;
However, the converse is not true: you cannot assign an anonymous method to a regular method pointer. Method references are managed types, but method pointers are unmanaged types. Thus, for type-safety reasons, assigning method references to method pointers is not supported. For instance, events are method pointer-valued properties, so you cannot use an anonymous method for an event. See the section on variable binding for more information on this restriction.
Internally, an anonymous method reference is implemented as a compiler-generated reference-counted interface with a single Invoke()
method. When you write an anonymous method in code, the compiler generates a hidden class that implements Invoke()
with that code. Any variables that the anonymous method captures are stored as members of that class.
When assigning a non-anonymous method pointer to an anonymous method reference, the compiler generates a class that captures that method pointer and then calls it inside of the generated Invoke()
.
So, given the code you have shown, the compiler would translate it into something roughly like the following (I'm leaving out implementation details that are not relevant):
type
//TProc = reference to procedure;
TProc_Intf = interface
procedure Invoke;
end;
TProc_Generated = class(TInterfacedObject, TProc_Intf)
FProc: procedure of object; // type of TMyForm.ProcB()
procedure Invoke;
end;
procedure TProc_Generated.Invoke;
begin
FProc; // <-- calls FProc.Code with FProc.Data as Self!
end;
procedure TMyForm.Button1Click(Sender: TObject);
var
Intf: TProc_Intf;
begin
//ProcA(ProcB);
Intf := TProc_Generated.Create;
//TProc_Generated(Intf).FProc := @ProcB;
TMethod(TProc_Generated(Intf).FProc).Code := Addr(ProcB);
TMethod(TProc_Generated(Intf).FProc).Data := Self; // <-- Self stored here!
ProcA(Intf);
end;
procedure TMyForm.ProcA(const Aproc: {TProc}TProc_Intf);
begin
//Aproc;
Aproc.Invoke;
end;
procedure TMyForm.ProcB;
begin
ShowMessage(Self.ClassName); // <-- Self valid here!
end;
That is how the Self
gets from Button1Click()
to ProcB()
through ProcA()
.