VCL Hardcore - VCL window function registration mechanism research memory, comparison with MFC

zhaozj2021-02-08  266

VCL Hardcore - VCL Window Function Registration Mechanism Research Book, Comparison with MFC

By Cheka Cheka@yeah.net (Reprinting please keep this information)

Some of the names of this name, no intention, just to attract eye, if you are interested in the following keywords, I hope not to miss this article:

1. Paging mode management in the memory of VCL visual components;

2. Member method for system callback class

3. Use of discourse instructions in Delphi

4. Hardcore

5. Article 4 is lie to you.

We know that the GUI program on the Windows platform must follow the Windows message response mechanism, which can be simply summarized, all of the window controls register their own window functions, running messages during run, can be assigned to a window function processing of a particular window control. This summary is a strict, please forgive me, I want to hurry to this article, that is, how to use Object Pascali or C , how to register a class of member methods to the system For callback.

When the registration window is called, when the registerclass function is called, we pass a WindowProc type function pointer to the system.

WindowProc defines as follows

LResult Callback WindowProc

HWND HWND, // Handle to Window

UINT UMSG, // Message Identifier

WPARAM WPARAM, // First Message Parameter

LParam lParam // Second Message Parameter

);

If we have a control class, it has a seemingly definition member method TMYCONTROL.WINDOWPROC, but it is not possible to pass its first address as the LPfnWndProc parameter to RegisterClass, which is simple, because all class members in Delphi have A implied parameter, that is, Self, so it cannot meet the definition of standard WindowProc.

Then, in the VCL, what kind of window pointer is passed when registering the system, and how is this pointer to how to adjust the event response method of each class? I first sell a Cat, first look at how the MFC is doing.

Before I investigate the MFC code, I have made two conjectures:

First, the registered function pointer points to a static method of a class,

The static method also does not need to imply the parameter this (corresponding to the self in Delphi, but Object Pascal does not support static methods)

Second, the registered function pointer points to a global function, which is of course the most traditional, nothing to say.

After simple tracking, I found that the global function AFXWndProc is the "root node" of the entire MFC program to process the message, that is, all messages are assigned to the message response functions of different controls, that is, all The window function registered with the system is like AfxWndProc (sorry, if you don't correct it). How do AfxWndProc call WNDPROC in each window class?

Haha, MFC uses a very simple mechanism, which compares to so much quirky macro, this mechanism is quite well understood: use a global MAP data structure to maintain all window objects and Handle (where Handle is Key value), then AFXWndProc, find the unique corresponding window object according to Handle (using a static function cWnd :: fromHandlePermanent (hwnd)), then call its WNDPROC, pay attention to WndProc, so the message can correctly arrive at the designated window class The message response function is processed. So we have reason to guess the VCL may also use the same mechanism. After all, this way is very simple. I am indeed so guess, but the conclusion is that I am wrong ...

The opening show is over, the priest is officially staged.

Put a button on Form1 (default name button1), write some code in its onclick event, plus breakpoints, F9 run, open the Call Stack window when staying on the breakpoint (view-> debug window > Call Stack, or press Ctrl-alt-s) It can be seen that the call sequence is as follows (from the bottom, STACK)

(If you see the Stack and this inconsistency, open the DCU debugging switch Project-> options-> compiler-> use debug dcus, if this switch does not open, it is not possible to debug VCL source code)

TFORM1.BUTTON1CLICK (???)

Tcontrol.click

TButton.click

TButton.cnCommand ((48401, 3880, 0, 3880, 0))

TControl.WndProc ((48401, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))

TwinControl.WndProc (48401, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))

TButtonControl.WndProc ((48401, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))

TControl.Perform (48401, 3880, 3880)

DOCONTROLMSG (3880, (no value)

TwinControl.Wmcomman D ((273, 3880, 0, 3880, 0))

TcustomMM.WMCommand (273, 3880, 0, 3880, 0))

Tcontrol.WndProc ((273, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))

TwinControl.WndProc ((273, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))

Tcustomform.WndProc ((273, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))

TwinControl.mainWndProc ((273, 3880, 3880, 0, 3880, 0, 3880, 0, 0, 0))

STDWndProc (3792, 273, 3880, 3880)

It can be seen that STDWndProc looks like a role that plays the AFXWndProc, but we don't talk about it, if you can't rest, you can go to see its source code in Forms.Pas, is it? ~~~~ Don't be interesting.

In fact, the window function pointer that VCL passes when registerclass is not pointing to StdWndProc. what is that?

I follow, I follow, I will finally see the call of RegisterClass in the implementation code of Controls.PAS's TwinDowControl (Procedure TWINCONTROL.CREATEW;), Hoho, finally found the organization ... don't be busy, I found out, at this time, the window function of the registered is initwndproc, look at its definition, um, in line with the standard, and go to the code.

Discover this sentence:

Setwindowlong (HWindow, GWL_WndProc, Longint (CreationControl.fObjectInstance);

I faint, I have been called for the first time INitWndProc (for every WinControl), I will change myself, new job is FOBJECTINSTANCE. There is also a small compilation below, which is called FOBJECTINSTANCE, and the reason for calling is not surprising, because the FOBJECTINSTACE is called by the system Callback, but now it is still rooted in initwdproc to Call. The way to call is somewhat, but leave it to you after reading this article.

Next, I can only continue to see what fOBJECTINSTANCE is Dongdong. It defines the private paragraph in TwinControl. It is a Pointer is a normal pointer. When you make a line, you said that it is WndProc type pointer Windows to take you.

What is FOBJECTINSTANCE pointing to the Shot to TWINCONTROL constructor, which is where FOBJECTINSTANCE is initially assigned. Unnecessary code is not looking, the focus is on this sentence

FOBJECTINSTANCE: = MAKEOBJECTINSTANCE (MainWndProc);

You can tell you first, makeobjectInstance is the most exciting thing in this topic, but you only need to know FOBJECTINSTANCE "" MainWndProc, that is, to register each MainWndProc as a window function through some way VCL, first prove easy, That is, mainWndProc has the function of the window function, to see the code:

(Province to handle abnormalities)

Procedure TWINCONTROL.MAINWNDPROC (Var Message: TMESSAGE);

Begin

WINDOWPROC (Message);

FreedeviceContexts;

FreeMemoryContexts;

END;

FreeDeviceContexts; and FreeMEMORYCONTEXTS are guaranteed VCL threads, not in this article discussions, only WINDOWPROC (Message); original MainWndProc will delegate the message to method WindowProc processing, notice that MainWndProc is not a virtual method, and WindowProc is virtual, understanding Design Pattern's friend should nod, um, it is a template method, naturally very classic usage, so that all messages can be accurately arrived, that is to say that mainwndProc can act as a window function. You can now review the approach of the MFC's AFXWindowProc, which is also used to use the polymorphism of the object, but there are differences in two ways.

Is it a bit messy, let us summary, VCL registration window function is divided into three steps: 1. [TwinControl.create]

FOBJECTINSTANCE points to mainwndproc

2. [TwinControl.createWnd]

The WindowClass.lpfnWndProc value is @initwndproc;

Call Windows.RegisterClass (WindowClass) Register to System

3. [InitWndProc is initially called Callback]

Setwindowlong (Hwindow, GWL_WndProc, Longint (CreationControl.fObjectInstance))

The window function is stolen and replaced, since then InitWndProc retreats

(Note that INitWndProc is only called once for each TwinControl control.

As mentioned earlier, non-static class methods cannot be registered as a window function, especially in delphi, there is no static class method, then MainWndProc can not have privileges (of course, Balan can make a hand feet on the compiler, if they Not afraid of being a vomiting.

Then, then, you should realize that everything is manipulated behind the scenes, it is ...

Background

Super Superstar: Mc. Tsinsanas

(MakeObjectInstance)

The sky shows lightning, oh yeah, the protagonist just appeared.

Do not say nonsense, code is serving:

(Original code in form.pas, "{}" is the original annotation, and after "file: //" is what I added, you can directly note the code, you can first look at my comments below, and then Looking back to Code)

// Total 13 Bytes

Type

POBJECTINSTANCE = ^ TOBJECTINSTANCE;

TOBJECTINSTANCE = Packed Record

Code: Byte; // 1 byte

OFFSET: Integer; // 4 Byte

Case Integer of

0: (Next: POBJECTINSTANCE); // 4 Byte

1: (Method: TwndMethod); // 8 Byte

// TwndMethod is a pointer to the object method,

// In fact, it is a pointer, including method pointer

// and a pointer of an object (ie Self)

END;

// 313 is the maximum value that satisfies the entire TINSTANCEBLOCK size does not exceed 4096

InstanceCount = 313;

// Total 4079 bytes

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.StanceCount] of tobjectInstance; 313 * 13 = 4069

END;

Function Calcjmpoffset (SRC, DEST: POINTER): Longint

Begin

Result: = longint (DEST) - (Longint (SRC) 5);

Function MakeObjectInstance (Method: Twnotmethod): 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

// instfreeelist = NIL indicates that an instance block has been fulfilled, so it needs to be a new

// Instance Block Assignment Space, one by all instance block in the PinstanceBlock

// NEXT pointer is connected to form a linked list whose head pointer is instablocklist

IF instfreeelist = nil dam

Begin

/ / Assign virtual memory for Instance Block and specify this memory as readable and writable and executable

// pageSize is 4096.

Block: = Virtualalloc (NIL, PageSize, Mem_Commit, Page_execute_Readwrite);

Block ^ .next: = instablocklist;

Move (Blockcode, Block ^ .code, sizeof (blockcode));

Block ^ .wndprocptr: = Pointer (Calcjmpoffset); @stdwndproc);

// The following code creates an instance linked list

Instance: = @ block ^ .instances;

Repeat

Instance ^ .code: = $ e8; {call near ptr offset}

File: // Calculate the offset of the relative to JMP STDWndProc command, put it behind $ E8

Instance ^ .offset: = Calcjmpoffset (Instance, @block ^ .code);

Instance ^ .next: = instfreeelist;

Instfreeelist: = instance;

/ / Must have this step, let the Instance pointer move to the bottom of the current instance sub-block

INC (LONGINT (INSTANCE), SIZEOF (TOBJECTINSTANCE);

/ / Decades if an instance block has been constructed

Until Longint (Instance) - Longint (Block)> = Sizeof (TinstanceBlock);

InstablockList: = block;

END;

RESULT: = instfreeelist;

= Instfreeelist;

Instfreeelist: = instance ^ .next;

Instance ^ .method: = method;

END;

Don't underestimate the energy of dozens of lines of code in this area, they are panelively managed by VCL's visual components, and two linies are operated in the code. There are ObjectInstance linked in InstanceBlock, and one instanceblock constitutes one Link list) A page, with 4096 bytes, although INSTANCEBLOCK actually uses only 4079 bytes, but for Alignment, add some padding to 4096. From the code, you can accommodate 313 so-called ObjectInstance. If you want to misunderstand this objectInstance, it is actually a small executable code, and these executable code is not generated during compilation. It is not like the virtual function is lagging behind, and it is simply "creation" during the operation of MakeObjectInstance (where to create "! Area, is it very great? What does I don't understand what the code corresponding to ObjectInstance is? It doesn't matter, come together

Call - - - - - - -> Pop Ecx // Before Call, the next instruction address will be stack

@MainWndProc // Implement POP ECX, why do you do this?

@Object (ie self) // mentioned previous comments

The answer is in the code of STDWndProc, and it is a matter of interest, but the unlimited scenery is in the peak, hard scalp.

Sure enough, we found that ECX is used.

Function StdwndProc (Window: hwnd; message, wparam: longint;

LPARAM: longint; long;; assembler;

ASM

XOR EAX, EAX

Push EAX

Push LParam

Push WPARAM

Push Message

MOV EDX, ESP

Mov Eax, [ECX] .longint [4] // is equivalent to MOV EAX, [ECX 4] ([ECX 4]? Just Self)

Call [ECX] .pointer // is equivalent to Call [ECX], that is, call mainwndproc

Add ESP, 12

POP EAX

END;

This assembly works in front of MAINWNDPROC, because the definition of MainWndProc is as follows

Procedure TWINCONTROL..mainWndProc (var message: tMessage);

According to Delphi's convention, in this case, the hidden function SELF is used as the first parameter, put it in Eax, the pointer of the TMESSAGE structure as the second parameter, put it in the EDX, and where does the Message pointer come? After we see a few pushs, the program has already constructed a TMessage structure in the stack, and this time ESP is of course the pointer of this structure, so it assigns it to the EDX. If you are not familiar with this agreement, you can refer to Delphi to help Object Pascal Refrence -> Program Control. Now the truth is big and white, and Windows news will finally pass mainwndproc. However, this way can be described as quite wonderful. MakeObject This function is naturally a powerful, and STDWndproc is also the behind-the-scenes hero, let us make the MakeObjectInstance and the STDWndProc connection. Get up, oh, it is a ghosta. (The picture cannot be displayed, please download the full text)

He summarizes this, and FOBJECTINSTANCE is registered as a window function, and actually fObjectInstance does not actually point a function, but pointing to an ObjectInstance, and the latter we already know a series of connected executable code segments. When the system needs to make FOBJECTINSTANCE as a callback, the code segment is located, then several jumping times (a CALL plus a jump) comes to StdWndProc, the main function of StdWndProc is to put the Self Pointer Punch and put it Windows's message is packaged into a Delphi's TMESSAGE structure, so that the member method of the TwinControl class is successful. Postscript:

Personally, in this technology, the VCL is more efficient than the MFC efficiency, the latter is quite a time based on the window handle according to the window handle, and the code of the MakeObject is also quite reference value. Let your own program open a bunch of executable code in memory?

All code is based on Delphi5, which may be invested from the rest of the version, but I believe it will not be big.

I spent the whole Saturday and Sunday I spent in this article (continuous test strip writing), but the horizontal limit, it is inevitable that there is no mistake and expression, but I will not be fainted, welcome to the letter. Advice cheka@yeah.net, Thanx

转载请注明原文地址:https://www.9cbs.com/read-1855.html

New Post(0)