Core tips: This experiment demonstrates that when calling a method in a class, all methods imply a self parameter, and this parameter is passed as the first parameter of the object method...
First, create an empty form and put a button.
The following two methods are declared under implementation:
// The external method declares only one parameter. In this case, the standard Object internal event Method tpolicyevent is declared. In this declaration, the sender corresponds to the object pointer that generates the event.
Procedure extclick1 (Sender: tobject );
Begin
{ASM
MoV eax, [edX + 8]
Call showmessage
End ;}
Showmessage (tcomponent (sender). Name );
End;
// The external method declares two parameters to prove that the object will pass a self pointer during the call. In this case, we assume that frm is a self pointer passed through the class object, the sender is the object pointer that generates the event.
Procedure extclick (FRM: tobject; Sender: tobject );
Begin
{ASM
MoV eax, [edX + 8]
Call showmessage
End ;}
Showmessage (tcomponent (sender). Name );
If frm is tform then
Tform (FRM). Close
End;
// Write in the 'specify call' button eventCode:
Procedure tform1.button1click (Sender: tobject );
Begin
Showmessage (tcomponent (sender). Name );
End;
// Obviously, when running, click this button to return a dialog box with the message content 'button1'. This is the method to call the object event of the form1 class.
// Write in the 'call form class external method trigger event' Click Event
Procedure tform1.button2click (Sender: tobject );
VaR
Extclickevent: tpolicyevent;
Begin
INTEGER (@ extclickevent): = INTEGER (@ extclick1 );
// Point the extclickevent address pointer to the address of the extclick1 method of the external function.
Button1.onclick: = extclickevent;
// Assign this address to the onclick event of button1 to replace the previous onclick event
End;
// The other code is as follows:
Procedure tform1.button3click (Sender: tobject );
Begin
Button1.onclick: = button1click; // restores it to the event function triggered in the object.
End;
After running
Click 'call form class external method trigger event', and then click 'specify call,
Showmessage (tcomponent (sender). Name); The returned value is 'form1 '. Does this indicate whether the first parameter is a Passed Self pointer. Therefore, when the button. Click Event is called, the first parameter passed is the self pointer inside form1, which points to form1. In this case
Place a breakpoint at the begin position,ProgramWhen running, the breakpoint here is not available, for example:
It indicates that the program does not process any other code at the beginning. At this time, the breakpoint is adjusted
Showmessage (tcomponent (sender). Name); then click the button to stop the program running at the breakpoint
The code for calling out the CPU View window is as follows:
Note that the values of eax, EBX, EDX, and ECx are
MoV eax, [eax + $08] // This command transfers the object's name attribute value to eax
Call showmessage // This function requires a parameter. The rules for passing Delphi parameters are eax, EDX, and ECx.
It can be seen that there is no extra processing, but it cannot be proved that the self pointer of the class object passed by eax is
In this case, replace the function of the Code in the 'call form external method trigger event' click event
Extclick
Both INTEGER (@ extclickevent): = INTEGER (@ extclick1 );
Replace it with integer (@ extclickevent): = INTEGER (@ extclick );
Repeat the preceding steps to break the breakpoint at the extin part of extclick and stop the program running at the breakpoint.
The program has code execution at begin. Open the CPU view and view the Code as follows:
It can be seen that after begin, there are two sections of code before the showmessage function:
Push EBX // Save the value of EBX
MoV EBX, eax // Save the eax value to EBX temporarily
Next, let's look at the following showmessage (tcomponent (sender). Name );
The Assembly Code is as follows:
MoV eax, [edX + $08]
Call showmessage
Compared with mov eax, [eax + $08] is changed to mov eax, [edX + $08]
Run the command. The value of tcomponent (sender). Name is button1.
The following code
If frm is tform then
Tform (FRM). close;
This fully proves that the value of eax is form1, indicating that the object method will pass an implicit self pointer during the call, and the value of this pointer is in eax.
Because the parameter passing in Delphi is
The first parameter of eax
EdX second parameter
ECX third parameter
So you can know that the real trigger event's button object is stored in EDX.
So we can draw the following conclusion:
In a click event,
Tpolicyevent = procedure (Sender: tobject) of object;
The real entity is procedure (the object self, Sender: tobject caused by the current Declaration)
Therefore, the transfer method for button. onclick is as follows:
Button1.onclick (self, Sender );
Other event methods, and so on.
Then, based on this conclusion, we may not
When you specify an event Method for a control object in form, it is restricted by the object. You can specify the event Method anywhere. As long as you note that the parameter corresponding to this method must be one more parameter declaration than the method specified by its event Method (of object), you can
For example, in this case, we take the form close event asArticle:
Create a new button and write code
Procedure tform1.button4click (Sender: tobject );
VaR
Closeevent: tcloseevent;
Begin
INTEGER (@ closeevent): = INTEGER (@ mycloseevent );
Self. onclose: = closeevent;
End;
The method for closing a form event is
Tcloseevent = procedure (Sender: tobject; var action: tcloseaction) of object;
From the above conclusion, we know that we can declare an external function. The parameter of this external function has a self pointer more than the parameter of tcloseevent, So we declare the following:
Procedure mycloseevent (FRM: tform; Sender: tobject; var action: tcloseaction );
FRM is the implicit pointer self passed when the form is closed.
The overall code of this function is as follows:
Procedure mycloseevent (FRM: tform; Sender: tobject; var action: tcloseaction );
Begin
Showmessage (FRM. Name + 'form external method call, the form cannot be closed! ');
Action: = canone;
End;
Click it. After the new button is created, check whether the form can be closed !!
Process through Assembly
Procedure tform1.setevent (Event: pointer );
ASM
Push EBX // protect EBX
MoV EBX, eax // Save the current eax value first with EBX, and save in eax as the start location of Form
MoV eax, EDX // assign the event pointer value to eax
MoV [EBX + $2d8], eax // write the values of eax into their high and low positions respectively.
MoV eax, [edX + 4]
MoV [EBX + $2d4], eax
Pop EBX
End;
// As we have proved above, there will be an implicit parameter self when passing methods in the class. Therefore, in this assembly code, we know that the event parameter corresponds to the edX register rather than the eax register. Then there is content like [EBX + $2d8], which is the address of the onclose event of the form. You can view it in the cpuview window. You have no idea how to specify an event name to get the address of the event in the memory. If so, you can write a function.
Resetobjevent (eventname: string; eventvalue: pointer );
First, find the event address through eventname, and then use the above Code to write a simple and easy-to-understand public function.
Otherwise, you can modify the point of the event function by passing the address and changing the value in the address. As follows:
Write a function specifically used to reset the event method as follows:
Procedure resetobjevent (oldeventaddress: pointer; neweventvalue: pointer );
VaR
GG: integer;
SD: pinteger;
Begin
SD: = oldevent;
GG: = INTEGER (newevent );
SD ^: = GG;
End;
In fact, it is to change the data value of the memory block storing the event Method pointer to another value.
Note that the callback parameter is the memory address for storing the old event Method pointer, so it should be a pointer.
Parameter 2 indicates the event Method pointer value.
The call method is as follows:
For example, the onclose event Method pointer of the specified form is a function defined outside the Form class.
Resetobjevent (@ (INTEGER (@ form1.onclose), @ mycloseevent)
For example:
Procedure frmclose (FRM: tform; Sender: tobject; var action: tcloseaction );
Begin
Showmessage ('Call an external method. Do not close it! ');
Action: = canone;
End;
Procedure tform1.bitbtn1click (Sender: tobject );
Begin
Resetobjevent (@ (INTEGER (@ self. onclose), @ frmclose );
End;
Statement:
The above test passed in Delphi7. As for the 2007 test, an implicit parameter is also passed, but this implicit parameter is not self
Further discussion:
After a notification from cnpack's Liu Yu, he found that Delphi7 passed the test. The reason why 2007 failed was that the following statement in D7:
Procedure tform1.button4click (Sender: tobject );
VaR
Closeevent: tcloseevent;
Begin
INTEGER (@ closeevent): = INTEGER (@ mycloseevent );
Self. onclose: = closeevent;
End;
At this time, the program running in section 2007 cannot be passed, while the D7 compilation and running can be passed, which is indeed a coincidence.
The prompt shows that tcloseevent is called the object method in Delphi, and the object Method
In Delphi, procedure (Sender: tobject) of object; the event type declared in this format actually contains records of objects and functions at the same time. We can forcibly convert a variable of tpolicyevent to tmethod:
Tmethod = record
Code, data: pointer;
End;
For example, we declare a method mainform. btnclick and assign it to the btn1.onclick event. In fact, the mainform object and the btnclick method address are assigned to the btn1.onclick event attribute as the data and code members of the tmethod structure respectively. When the btn1 button calls this btnclick event, the data in the tmethod structure is actually used as the first parameter to call the code function.
You can write the following code:
Procedure myclick (SELF: tobject; Sender: tobject );
Begin
// The first parameter is virtual.
Showmessage (format ('self: % d, Sender: % s', [INTEGER (Self), Sender. classname]);
End;
procedure tform1.formcreate (Sender: tobject);
var
M: tmethod;
begin
M. code: = @ myclick;
M. data: = pointer (325); // random number
btn1.onclick: = tpolicyevent (m);
end;
in this way, you can assign a common function to the object event attribute.
let's take a look at tlanguages. create Code:
constructor tlanguages. create;
type
tcallbackthunk = packed record
popedx: byte;
moveax: byte;
selfptr: pointer;
pusheax: byte;
pushedx: byte;
JMP: byte;
jmpoffset: integer;
end;
var
callback: tcallbackthunk;
begin
inherited create;
callback. popedx: = $ 5A;
callback. moveax: = $ B8;
callback. selfptr: = self;
callback. pusheax: = $50;
callback. pushedx: = $52;
callback. JMP: = $ E9;
callback. jmpoffset: = INTEGER (@ tlanguages. localescallback)-INTEGER (@ callback. JMP)-5;
enumsystemlocales (tfnlocaleenumproc (@ callback), lcid_supported);
end;
The callback format required by enumsystemlocales can be found in Win32 SDK:
Bool callback enumlocalesproc (
Lptstr lplocalestring // pointer to locale identifier string
);
The method declaration in sysutils:
Tlanguages = Class
...
Function localescallback (localeid: pchar): integer; stdcall;
...
End;
Obviously, we cannot pass the localescallback method directly to enumsystemlocales, because the function declaration of localescallback is actually:
Function localescallback (SELF: tlanguages; localeid: pchar): integer; stdcall;
This parameter is more than enumlocalesproc.
Therefore, the callback structure variable is used in tlanguages. Create to generate a short dynamic code. This code is constructed in the stack (local variable) and converted to assembly:
Prcoedure callbackthunk;
ASM
// Extract the lplocalestring parameter to the edx register.
// Callback enumlocalesproc is called by stdcall, and the parameter is in the stack
Pop edX
// Pass the self object to the eax register
MoV eax self
// Call stdcall and use self as the first parameter to press the stack
Push eax
// Use lplocalestring as the second parameter to press the stack
Push edX
// Use the relative jump command to jump to the tlanguages. localescallback entry address
JMP tlanguages. localescallback
End;
It is valid to pass callbackthunk to enumsystemlocales as a temporary callback function. When the callback is executed, the code in the previous section dynamically modifies the stack content and changes the call with only one parameter to two parameters, thus implementing the conversion between the callback and the object method.
However, as passion mentioned earlier, because this small temporary code is put in the stack, and the DEP of win2003 restricts the code execution in the stack, as a result, the callback function is not called correctly.
Borland programmers also saw this problem, so in BDS 2006, the implementation of this part of Code was changed:
VaR
Ftemplanguages: tlanguages;
Function enumlocalescallback (localeid: pchar): integer; stdcall;
Begin
Result: = ftemplanguages. localescallback (localeid );
End;
Constructor tlanguages. Create;
Begin
Inherited create;
Ftemplanguages: = self;
Enumsystemlocales (@ enumlocalescallback, lcid_supported );
End;
By declaring a temporary variable and a conversion function to replace the original method, there will be no Dep conflict.
Let's take a look at makeobjectinstance in the forms unit. This function is used to generate a piece of dynamic code and convert the form message processing process of windows to the object method call of Delphi. It is used in scenarios such as twincontrol that require message processing support. This function also uses a similar method, but the difference is that since these conversion calls are long-term, so the dynamically generated code is put into the dynamic space marked as executable, so it can still work normally in the DEP of win2003:
Function makeobjectinstance (method: twndmethod): pointer;
VaR
...
Begin
If instfreelist = nil then
Begin
Block: = virtualalloc (nil, pagesize, mem_commit, page_execute_readwrite );
...
End;
Liu Yu
For example, we declare a method mainform. btnclick and assign it to the btn1.onclick event. In fact, the mainform object and the btnclick method address are assigned to the btn1.onclick event attribute as the data and code members of the tmethod structure respectively. "When the btn1 button calls this btnclick event, the data in the tmethod structure is actually used as the first parameter to call the code function ."
The call here seems worth discussing. Remember that The onclick event was written as follows when it was called:
If assigned (fonclick) then
Fonclick (Self );
The first parameter is passed into the button itself during the call, that is, the self of the button, rather than the data in the original method, right?
In my understanding, the method data is only used to indicate the object instance of the method, but it does not seem to play a role when it is called. Therefore, you can create a tmethod data part and assign values to onclick and then call the data.
Zhou Jinyu
If assigned (fonclick) then
Fonclick (Self );
The self passed in here is the sender: tobject parameter in tpolicyevent, and The onclick parameter as the object method actually requires two parameters. The first hidden self is the object subordinate to the onclick method, the second is sender.
For example, when a button calls fonclick, this fonclick may point to onbtnclick of a form. Class itself does not save the object instance, directly call form. when onbtnclick is an instance of form. fonclick calls form. when onbtnclick is used, where does onbtnclick self come from? Of course, tmethod. Data is passed in. The tmethod. Data is the form object when the value is button. onclick: = form. onbtnclick.
The self passed in during fonclick is used as the sender, while the self referenced in the btnonclick method is a form instance, and the self of the latter should come from data.
A general function can be obtained to dynamically set object events:
Procedure resetobjevent (oldeventaddr: pointer; neweventvalue: pointer; resetobject: tobject );
Begin
Tmethod (oldeventaddr ^). Code: = neweventvalue;
Tmethod (oldeventaddr ^). Data: = resetobject;
End;
Parameter 1: Specify the address pointer to store the memory address value of the event pointer, so it is a pointer
Parameter 2: Specify the new event function address pointer.
Parameter 3: Specify the modifier for resetting the event to hide the implicit parameter self of the object method.
Call method:
Resetobjevent (@ INTEGER (@ self. onclose), @ mycloseevent, self );
Example:
Procedure mycloseevent (classsend: tobject; Sender: tobject; var action: tcloseaction );
Begin
Action: = canone;
Showmessage (tcomponent (sender). Name + 'trigger, do not close ');
Showmessage (tcomponent (classsend). Name );
End;
Procedure tform1.button1click (Sender: tobject );
Begin
Resetobjevent (@ INTEGER (@ self. onclose), @ mycloseevent, self );
End;