We know that Gui programs on windows must follow the message response mechanism of windows.
As shown in the following figure, all window controls register their own window functions with the system. messages can be assigned to specific windows during running.
Control Window Function processing. It is not strict to summarize the message-related mechanism. Sorry, I want to change it quickly.
This article focuses on how to put a class
To the system for callback.
When the Registry window class calls the registerclass function, we pass a windowproc class to the system.
Type function pointer
Windowproc is defined as follows:
Lresult callback windowproc (
Hwnd, // handle to window
Uint umsg, // message identifier
Wparam, // first Message Parameter
Lparam // second Message Parameter
);
If we have a control class, it has the member method tmycontrol. windowproc that appears to have the same definition,
However, it cannot pass its first address as the lpfnwndproc parameter to registerclass. The reason is very simple, because
In Delphi, all class member methods have an implicit parameter, that is, self. Therefore, they cannot comply with the standard.
Windowproc definition.
In VCL, what kind of window pointer does the control pass when registering with the system?
How can I adjust the pointer to the Event Response methods of various classes? I want to sell a token first. First, let's see how MFC works.
Before investigating the MFC code, I had two guesses:
1. The function pointer used for registration points to the static method of a class,
Static methods also do not require the implicit parameter This (corresponding to self in Delphi, but Object Pascal does not support
Static Method)
2. The function pointer used for registration points to a global function, which is of course the most traditional and has nothing to say.
After simple tracking, I found that in MFC, the global function afxwndproc is the "root" of messages processed by the entire MFC program.
Node, that is, all messages are assigned to the Message response functions of different controls, that is, all
The Window Function registered by the Windows Control to the system is probably afxwndproc (sorry, I didn't do any further research.
Correct ). How does afxwndproc call wndproc of various window classes?
Haha, MFC uses a very simple mechanism. This mechanism is quite good compared to so many odd macros.
Understanding: A Global Map Data structure is used to maintain all window objects and handle (where handle is the key value ),
Then afxwndproc uses handle to find the unique window object.
(Use the static function cwnd: fromhandlepermanent (hwnd) and then call its wndproc. Note:
Wndproc is a virtual method, so the message can correctly reach the message response function of the specified window class and be processed.
So we have reason to guess that VCL may adopt the same mechanism. After all, this method is easy to implement. I do
I guess so, but the conclusion is that I am wrong ......
The movie show is over and the show is officially staged.
Put a button on form1 (the default name is button1), write some code in its onclick event, and add the breakpoint,
Run F9. When you stop at the breakpoint, open the call Stack window (View-> debug window-> call stack,
Or press Ctrl-alt-s to see the following call sequence (from the bottom up, stack)
(If the stack you see is inconsistent with this one, enable the DCU debugging switch.
Project-> options-> compiler-> use DEBUG dcus. If this switch is not enabled, you cannot debug VCL.
Source code)
Tform1.button1click (???)
Tcontrol. Click
Tbutton. Click
Tbutton. cncommand (48401,388 0, 0, 3880, 0 ))
Tcontrol. wndproc (48401,388 0, 3880, 0, 3880, 0, 3880, 0, 0 ))
Twincontrol. wndproc (48401,388 0, 3880, 0, 3880, 0, 3880, 0 ))
Tbuttoncontrol. wndproc (48401,388 0, 3880, 0, 3880, 0, 3880, 0, 0 ))
Tcontrol. Perform (48401,3880, 3880)
Docontrolmsg (3880, (no value ))
Twincontrol. wmcomman D (273,388 0, 0, 3880, 0 ))
Tcustomform. wmcommand (273,388 0, 0, 3880, 0 ))
Tcontrol. wndproc (273,388 0, 3880, 0, 3880, 0, 3880, 0, 0 ))
Twincontrol. wndproc (273,388 0, 3880, 0, 3880, 0, 3880, 0 ))
Tcustomform. wndproc (273,388 0, 3880, 0, 3880, 0, 3880, 0, 0 ))
Twincontrol. mainwndproc (273,388 0, 3880, 0, 3880, 0, 3880, 0, 0 ))
Stdwndproc (3792,273,388, 3880)
It can be seen that stdwndproc seems to play the afxwndproc role in MFC, but we will not talk about it first,
If you cannot restrain your curiosity, you can check its source code in advance. Have you seen it in forms. Pas? Is it true?
Special ~~~~ Don't be funny.
In fact, the window function pointer passed by VCL in registerclass does not point to stdwndproc. What is that?
What about it?
I followed, and finally in the twindowcontrol implementation code of controls. Pas
(Procedure twincontrol. createwnd;) saw the call of registerclass, Hoho, and finally found the group.
Weaving... Don't be busy. If you find out, the registered window function is initwndproc. Let's take a look at its definition,
Comply with the standards, and then look at what the code has done.
Found this sentence:
Setwindowlong (hwindow, gwl_wndproc, longint (creationcontrol. fobjectinstance ));
I faint, after half a day of initwndproc being called for the first time (for every wincontrol ),
Changed. The new employee is fobjectinstance. There is also a small assembly below, which is followed by the call
The reason for calling fobjectinstance is not surprising, because the system callback will call fobjectinstace later.
But now we have to call initwndproc. The calling method is a bit exquisite, but I will leave it for you to read this article.
After the article, you can think about it yourself.
Next, we can only continue to see what fobjectinstance is, which is defined in the private
Segment, which is a pointer, that is, a common pointer. When everything works, you tell windows that it is a wndproc pointer.
You have no idea about windows.
Where exactly does fobjectinstance point to? The lens moves to the twincontrol constructor. This is
Where fobjectinstance is assigned for the first time. You don't need to look at the extra code. The focus is on this sentence.
Fobjectinstance: = makeobjectinstance (mainwndproc );
You can tell me that makeobjectinstance is the most exciting topic, but now you only need to know
Fobjectinstance "points to" mainwndproc, that is to say, through some way VCL, each mainwndproc
As a window function registration, it is easy to prove that mainwndproc has the window function. Let's look at the Code:
(Exception handling is skipped)
Procedure twincontrol. mainwndproc (VAR message: tmessage );
Begin
Windowproc (Message );
Freedevicecontexts;
Freememorycontexts;
End;
Freedevicecontexts; and freememorycontexts ensure VCL thread security, which is not discussed in this article
, Only read windowproc (Message); originally mainwndproc delegated the message to the method windowproc for processing,
Note that mainwndproc is not a virtual method, while windowproc is a virtual method. Learn about design pattern.
A friend of mine should nod his head. Well, it's a template method. It's also a classic usage.
The message can reach the destination accurately. That is to say, from the functional perspective, mainwndproc can indeed be filled as a window function.
Now you can review the afxwindowproc method of MFC, which also utilizes object polymorphism,
Type is different.
Is it a bit messy? Let's sum up, The VCL registration window function is divided into three steps:
1. [twincontrol. Create]
Fobjectinstance points to mainwndproc
2. [twincontrol. createwnd]
Windowclass. lpfnwndproc: @ initwndproc;
Call windows. registerclass (windowclass) to register with the System
3. [When initwndproc is callback for the first time]
Setwindowlong (hwindow, gwl_wndproc, longint (creationcontrol. fobjectinstance ))
The window function is stolen and removed from the initwndproc
(Note that for each twincontrol control, initwndproc is called only once)
As mentioned above, non-static class methods cannot be registered as window functions, especially in Delphi
There is no static class method at all, so mainwndproc cannot have the privilege (of course, Bao LAN can do this on the Compiler
Hands and feet, if they are not afraid of being vomit ).
Then, you should realize that what is behind the scenes to manipulate everything is ......
Subtitle playing in background
Superstar: mcopbagit intans
(Makeobjectinstance)
There was lightning in the sky. Oh, yeah, the main character was just unveiled.
Code WAITS:
(The original code is in form. Pas, "{}" is the original annotation, and "//" is followed by what I add. You can directly
Then you can comment out the code. You can also read the comments below and chew back on the Code)
// A total of 13 bytes. The variant record is based on the maximum value.
Type
Pobjectinstance = ^ tobjectinstance;
Tobjectinstance = packed record
Code: byte; // 1 bytes
Offset: integer; // 4 bytes
Case INTEGER
0: (next: pobjectinstance); // 4 bytes
1: (method: twndmethod); // 8 bytes
// Twndmethod is a pointer to the object method,
// In fact, it is a pointer pair, containing the method pointer
// And a pointer to an object (that is, self)
End;
// 313 is the maximum value that satisfies the entire tinstanceblock size of up to 4096
Instancecount = 313;
// 4079 bytes in total
Type
Pinstanceblock = ^ tinstanceblock;
Tinstanceblock = packed record
Next: pinstanceblock; // 4 bytes
Code: array [1 .. 2] of byte; // 2 bytes
Wndprocptr: pointer; // 4 bytes
Instances: array [0 .. instancecount] of tobjectinstance; 313*13 = 4069
End;
Function calcjmpoffset (SRC, DEST: pointer): longint;
Begin
Result: = longint (DEST)-(longint (SRC) + 5 );
End;
Function makeobjectinstance (method: twndmethod): pointer;
Const
Blockcode: array [1 .. 2] of byte = (
$59, {Pop ECx}
$ E9); {JMP stdwndproc} // actually only one JMP
Pagesize = 4096;
VaR
Block: pinstanceblock;
Instance: pobjectinstance;
Begin
// Instfreelist = nil indicates that an instance block is fully occupied. Therefore, a new
// The instance block allocates space. Each instance block uses
// The next pointer is connected to form a linked list. Its header pointer is instblocklist.
If instfreelist = nil then
Begin
// Allocate virtual memory to the instance block and specify the memory as read/write and executable
// Pagesize is 4096.
Block: = virtualalloc (nil, pagesize, mem_commit, page_execute_readwrite );
Block ^. Next: = instblocklist;
Move (blockcode, block ^. Code, sizeof (blockcode ));
Block ^. wndprocptr: = pointer (calcjmpoffset (@ block ^. Code [2], @ stdwndproc ));
// The following Code creates an instance linked list
Instance: = @ block ^. instances;
Repeat
Instance ^. Code: = $ E8; {call near PTR offset}
// Calculate the offset relative to the JMP stdwndproc command, which is placed behind $ E8
Instance ^. offset: = calcjmpoffset (instance, @ block ^. Code );
Instance ^. Next: = instfreelist;
Instfreelist: = instance;
// This step is required to move the instance pointer to the bottom of the current instance sub-block.
INC (longint (instance), sizeof (tobjectinstance ));
// Determine whether an instance block has been constructed
Until longint (Instance)-longint (Block)> = sizeof (tinstanceblock );
Instblocklist: = block;
End;
Result: = instfreelist;
Instance: = instfreelist;
Instfreelist: = instance ^. Next;
Instance ^. Method: = method;
End;
Do not underestimate the energy of dozens of lines of code in this area, that is, they manage VCL visual components in pages,
(Two linked lists are operated in the Code. instanceblock contains the linked list of objectinstance, and
An instanceblock also forms a linked list. An instanceblock is one page with 4096 bytes.
Instanceblock actually uses only 4079 bytes, but some padding is added for alignment.
It is filled with 4096. From the Code, each page can contain 313 so-called objectinstances.
It is easy to misunderstand this objectinstance as an object instance. Otherwise, each objectinstance is actually
A small segment of executable code, which is not generated during compilation or as lagging behind as a virtual function
And makeobjectinstance is "created" during running (days )! That is to say,
Makeobjectinstance transforms all the visual VCL components into the executable code area on one page. Is it true?
It's amazing.
Do you not understand what the code for objectinstance is? It doesn't matter. Let's take a look.
Call--> pop ECx // before the call, the address of the next command will be pushed to the stack.
@ Mainwndproc // then execute pop ECx. Why?
@ Object (Self) // mentioned in the previous comment
The answer lies in the stdwndproc Code. It is a compilation, but the infinite scenery is at a high risk.
One time.
Naturally, we found that ECx was used
Function stdwndproc (window: hwnd; message, wparam: longint;
Lparam: longint): longint; stdcall; assembler;
ASM
XOR eax, eax
Push eax
Push lparam
Push wparam
PUSH message
MoV edX, ESP
MoV eax, [ECx]. longint [4] // What is equivalent to mov eax, [ECx + 4] ([ECx + 4? Is self)
Call [ECx]. pointer // call [ECx], that is, call mainwndproc
Add ESP, 12
Pop eax
End;
In this compilation, some parameters are transferred before mainwndproc is called. Because the definition of mainwndproc is as follows:
Below:
Procedure twincontrol... mainwndproc (VAR message: tmessage );
According to Delphi, in this case, the implicit function Self is used as the first parameter and put into eax,
The pointer of the tmessage structure is used as the second parameter and placed in EDX. Where does the message pointer come from? Let's see
After several consecutive pushes, the program has constructed a tmessage structure in the stack, and the ESP
Of course it is the pointer to this structure, so it is assigned to EDX. If you are not familiar with this agreement, refer
Delphi's help Object Pascal refrence-> program control.
Now the truth is clear, Windows messages are transferred to mainwndproc after folds, but this is also a phase
When it comes to perfection, the makeobject function is naturally a hero behind the scenes, and stdwndproc is also a hero behind the scenes. Let's
Connect the code produced by makeobjectinstance with stdwndproc.
To sum up, fobjectinstance is registered as a window function by VCL.
Fobjectinstance does not actually point to a function, but to an objectinstance.
It is known that it is one of a series of connected executable code segments. When the system needs to regard fobjectinstance
When the window function is called back, it actually enters the code segment where the objectinstance is located, and then jumps several times to move (
A call plus a jump) to stdwndproc. The main function of stdwndproc is to set the self pointer
Press the stack and package Windows messages into the tmessage structure of Delphi.
The member method of the twincontrol class is mainwndproc. Once a message enters the mainwndproc, it can be used easily.
Changshou Xiaoqu came to the wndproc of various objects, from which he achieved a complete success.