Disclosure of the Message Mechanism of Delphi

xiaoxiao2021-03-06  49

Disclosure of the Message Mechanism of Delphi

Savetime2k@yahoo.com I started waiting for Li Wei's "Inside VCL" from early December last year. My plan at the time was to learn Delphi in-depth learning under the guidance of this book. At the end of December, the book has not yet come out, I don't want to wait, start reading the VCL source code. After reading the code of Tobject, TPERSIStant, and Tcomponent, I found that it is still unclear how the Delphi object is created. So I viewed the assembly code generated by Delphi, I finally understood the entire process created by the object (here you should be particularly grateful to the help of BOOK523).

Since then I am start learning the messaging mechanism of Delphi VCL. Since I wrote "Delphi's object mechanism", so far a week, I also basically read the Delphi VCL message processing framework. My learning method is to read the source code. At the beginning, the clues gradually became clear. Here, I record my understanding of the Delphi VCL message mechanism, which is convenient for future review, and also gives beginners Delphi or have a friend who has no time to read VCL source code (after all, there are no several programmers like me. Due to the time of learning Short, there will be a mistake, please correct it.

In the process of analyzing the VCL message mechanism, I basically only examined three Tobject, TControl, and TwinControl. Although I didn't read the code of the upper class (such as tform), I think this is the details of the implementation. I believe that the most critical things in the VCL message system are in these three classes. Outlotted, the message processing process of reading other classes after reading the message processing method of the basic class is much easier.

To read this article, the minimum is configured: Understand the Win32 message loop and window procedure Basic understanding of Tobject, TControl, and TwinControl implementation, familiar with Delphi object overload and polymorphism

Recommended configuration is: familiar with Win32 SDK programming familiar with Delphi object mechanism is familiar with Delphi embedded language

Recommended reading: "Delphi's atom world" http://www.codelphi.com/ "VCL window function registration mechanism research" http://www.delphibbs.com/delphibbs/dispq.asp?lid = 584889 "Delphi's object mechanism" http://www.delphibbs.com/delphibbs/dispq.asp?lid=2390131

This paper version is: The body is automatically wrapped by the window; all code is bounded by 80 characters; the Chinese and English characters are separated by space.

(The author retains all rights to this article, do not reprint in any public media without consent.)

Directory ============================================================================================================================================================================== ====================================================================00 ⊙ and create a window ⊙ additional knowledge: TWndMethod overview ⊙ VCL message processing from TWinControl.MainWndProc start ⊙ TWinControl.WndProc ⊙ TControl.WndProc ⊙ TObject.Dispatch ⊙ TWinControl.DefaultHandler ⊙ TControl.Perform and TWinControl.Broadcast ⊙ TWinControl.WMPaint ⊙ to TWinControl Take the path to describe the path transfer of the message =========================================== =====================================

Text ================================================== ======================================================================================================================================= ============================================================================================================================================================================================================= ===================== Usually a Win32 GUI application is running around the message loop. In a standard C language Win32 GUI program, the following code appears: while (GetMessage (& MSG, NULL, 0, 0)) // GetMessage second parameter is null, / / ​​means receiving all applications generation Window Message {TranslateMessage (& MSG); // Convert the character set in the message DISPATCHMESSAGE (& MSG); // Transfer the MSG parameters to lpfnwndproc}

LPfnWndProc is the address of the callback function defined by Win32 API, whose prototype is as follows: int __stdcall WndProc (HWND HWND, UINT UMSG, WPARAM WPARAM, LPARAM LPARAM);

The Windows Pickup Function is also commonly referred to as a window process, which is free to use these two names, which represent the same meaning.

The application uses getMessage to constantly check if there is a message reaching in the message queue. If the message is found, TranslateMessage is called. TranslateMessage is mainly working in character messages, not critical functions. Then call DispatchMessage (& MSG). DispatchMessage (& MSG) uses the MSG to call the callback function (WNDCLASS.LPFNWNDPROC) of the created window for the parameter. LPfnWndProc is a messaging method designed by the user.

When getMessage discovers a WM_QUIT message in the application's message queue, getMessage returns false, the message loop talent ends, and the application is also ended after the application is cleaned up. The execution process of applications written using the most primitive Win32 API is easy to understand, but the Delphi VCL component package message system is not easy. First, Delphi is an object-oriented programming language, not only to encapsulate Win32's message processing procedures in various inheritance classes of the object, allowing the application's user to easily call, but also make the developers of the VCL component have expanded The space for message processing. Secondly, all class methods in Delphi's object model are object-related (that is, a hidden parameter Self), so the method of Delphi object cannot be driven directly by the Windows. Delphi VCL must use other methods to make Windows call back to object messages.

Let us track a standard Delphi Application process, see how Delphi starts a message loop.

Program project1; begin application.initialize; application.createform (TFORM1, FORM1); Application.Run; End.

Before Project1 Application.Initialize, the Delphi compiler will automatically insert a row code: sysinit._initexe. _INITEXE is mainly to initialize Hinstance and module information tables. Then _initexe calls System._startexe. System._startexe calls System.initUnit; System.InitUnit calls the code in all included Initialization segments in the project; where there is a Controls.initialization section, this segment is more critical. Two critical global objects of Mouse, Screen, and Application are established in this code.

Application.create calls Application.createHandle. Application.createHandle creates a window and sets Application.WndProc as a callback function (here the makeObjectInstance method is used, and then talk again). Application.WndProc works primarily to some application level messages.

I didn't discover the creation process of the Application object when I first tracked the execution of the application. It turned out that I was hidden in sysinit._initexe. If you want to track this process, don't set a breakpoint, press F7 directly.

Then I arrived at the first sentence of Project1: Application.initialize; this function has only one code:

IF initproc <> nil dam; initproc;

That is to say, if the user wants to run a specific process before the application execution, you can set the initproc to the process. (Why is the user not running this specific process before or before the application.initialize or in the unit's initliazation segment? A possible answer is: If the component designer wants to execute a process before the application's code is executed, and this process must be Other units of Initialization executes [For example, Application objects must be created], you can only use this process pointer to implement.)

Then it is the second sentence of Project1: Application.createform (TFORM1, FORM1); the main role of this sentence is to create a TFORM1 object, and then set Application.MAINFORM to TFORM1. Finally, the 3 sentence of Project1: Application.Run; TAPPLICATION.RUN calls the TAPPLICATION.HandleMessage processing message. Application.HandleMessage code is only one line:

If not processmessage (msg) THEN IDLE (MSG);

TAPPLICATION.ProcessMessage really starts establishing a message loop. ProcessMessage uses the PeekMessage API instead of get the message in the message queue in GetMessage. The advantage of using PeekMessage is that there is no message in the message queue that will be returned when there is no message in the message queue, which provides a basis for the HandleMessage function.

ProcessMessage also specially handles special messages such as HINTMSG, MDIMSG, Keymsg, DLGMSG when processing the message cycle, so few in which the pure Win32 SDK programming is distinguished in the Delphi, these are Packaged into TForm (in fact, the Dialog in Win32 SDK is also Microsoft specialized in writing a window process and a set of functions to make the user interface design, and its internal operation process is not different from a normal window).

Function Tapplication.ProcessMessage (VAR Msg: TMSG): Boolean; Var Handled: Boolean; Begin Result: = FALSE; if PeekMessage (MSG, 0, 0, 0, PM_Remove) THEN // Get message Begin Result: = true If msg.Message <> wm_quit the beginning: = false; // handled indicates whether Application.OnMessage has handled over // current message. // If the user sets the Application.onMessage event handle, then call Application.onMessage if Assign (FonMessage); if not ishintmsg (msg) and not handled and not ismdimsg (msg) and not IsKeymsg (msg) and not isdlgmsg (msg) THEN // Reflection: NOT HANDLED Why not put it in the forehead? Begin TranslateMessage (// Processing Character Conversion DispatchMessage (MSG); // Call WNDCLASS.LPFNWNDPROC END; END ELSE FTERMINATE: = true; // Application Termination // (here just set a termination tag) END;

From the above code, the Delphi application's message loop mechanism is similar to the standard Win32 C language application. Just Delphi sets a lot of extension spaces for convenience of users, their side effects are that message processing will be lower than pure C Win32 API call efficiency. ============================================================================================================================================================================================================= ============================= ⊙ TwinControl.create, the registration window process and creation window ========== ============================================================================================================================================================================================================= =================== The above simply discusses the process of establishing an Application to form a message loop, and now the problem is how the Delphi control is how to create a window. Because only the window is established, the message loop is meaningful.

Let's take a review of several major classes in Delphi VCL: TOBJECT All Objects Tablets TPERSIStent All Based Categories of Stream Features TibPonent All Objects in Delphi Form Designer TCONTROL All visual Object class TwinControl all object base classes with window handles

Delphi is a component related to the window from TWINControl. The so-called window, for program designers, is a window handle HWnd. TwinControl has a Fhandle private member represents the window handle of the current object, and accesses through the TwinControl.Handle property.

When I first tracked the TwinControl.create process, I didn't find that the CreateWindow API was called, and TWINControl was not created when the object was created, and the Windows window was created. What happens if the user uses the Handle Access window to use the Handle access window?

The answer is in TwinControl.getHandle, Handle is a read-only window handle:

Property TWINCONTROL.HANDLE: HWND READ GETHANDLE;

The content of TwinControl.getHandle code is: Once the user wants to access Fhaandle members, TwinControl.Handleneeded is called. Handleneed is first determined whether the TwinControl.fhandle is equal to 0 (Remember? Any object call constructor is cleared to be cleared). If FHANDLE is not equal to 0, then directly returns fhandle; if fhaandle is equal to 0, the description window has not been created, and the handleneeded is called TwinControl.createHandle to create a handle. But CreateHandle is just a packaging function, which first calls TwinControl.createWnd to create a window, then generate some parameters that maintain VCL Control running (I haven't seen it yet). CREATEWND is an important process that calls TwinControl.createParams Settings to create the parameters of the window. (CreateParams is a virtual method, that is, the programmer can overload this function, define the properties of the window to be built.) CREATEWND and call TwinControl.createWindowHandle. CREATEWINDOWHANDLE is the function that truly calls the CreateWindowEx API creates a window. Come enough, we can complain why Borland makes so complicated, but finally I hope that Borland is designed to have its own truth. The above discussion can summarize TWINControl to delay the establishment window in order to reduce the occupancy of the system resources, only the window is only required to call the control to the control window handle. This usually happens when the window needs to be displayed. Whether a window needs to display often occur when it is assigned to the Parent property (defined in tControl). When setting the Parent property, the TControl.SetParent method calls TwinControl.RemoveControl and TwinControl.InsertControl methods. INSERTCONTROL call TwinControl.UpdateControlState. UpdateControlState checks the TwinControl.Showing property to determine if TwinControl.Updateshowing is to be called. Updateshowing must have a window handle, so call TwinControl.createHandle to create a window.

However, these, just complicated and not hard, there are many key code not to talk.

You may find a key thing is missing, right, that is, the callback function of the window. Since Delphi builds a window's callback process is too complicated (and very delicate design), I have to come out separately.

CHEKA's "VCL Window Function Registration Mechanism Research Instruction, and MFC Comparison" One-in-depth analysis, please refer to: http://www.delphibbs.com/delphibbs/dispq.asp? LID = 584889

I will briefly introduce the implementation of the callback function in the VCL:

In the code of TWINControl.create, the first sentence is inherited, the second sentence is

FOBJECTINSTANCE: = Classes.makeObjectInstance (MainWndProc);

I think this code may be intimidated by many people. If there is no CHEKA analysis, many people are difficult to understand. But you don't necessarily read the implementation process of MakeObjectInstance, you just know:

MakeObjectInstance generates a small assembly code in memory, this code is a standard window process. Two parameters are stored in this assembly code, one is the address of MainWndProc, one is a Self (address of the object). The function of this assembly code is to call the TwinControl.mainWndProc function using the SELF parameter. After MakeObjectInstance returns, the address of this code is stored in the private member of TwinControl.fObjectInstance.

This way, TwinControl.FOBJECTINSTANCE can be used as a standard window process. You may think twicontrol will directly register TwinControl.fObjectInstance as a callback function of the window class (using the registerclass API), but doing this is not right. Because a FOBJECTINSTANCE's assembly code has built-in object-related parameters (object's address Self), it cannot be used as a common callback function. TwinControl.CreateWnd Call CREATEPARAMS to get information on the window class you want to register, and then use the static function inTwndProc in Controls.PAS as the window callback function for the window callback function. The parameters of InitWndProc meet the standard of Windows callback functions. INITWNDPROC first replaced the callback function of the new window (notice the window class) for the first time, INITWINCONTROL.FOBJECTINSTANCE (this is a Windows Subclassing Technology), and the attribute of saving the object's address in the new window using setprop In the table, the auxiliary function of Delphi is read (such as the FindControl function in Controls.PAS).

In short, TWINControl.fObjectInstance is ultimately registered as a window callback function.

Thus, if the window created by the TwinControl object receives the message (the image of the image), it will be called TWINCONTROL.FOBJECTINSTANCE by Windows, and FOBJECTINSTANCE will call the TwinControl.mainWndProc function of the object. In this way, the VCL completes the conversion of the message processing process of the object and the Differences in the callback function format required by Windows. Note that the first parameter hwnd passed in the Windows callback during the conversion process is abandoned. Therefore, Delphi's components must be used to get this parameter using TwinControl.handle (or a WindowHandle in Protected). The return value that the Windows callback function needs to be returned is also replaced with the last field of the TMESSAGE structure.

In order to make everyone clearer the process of being called by the window, I started the assembly code from DispatchMessage to TwinControl.mainWndProc (you can see the code from fObjectInstance.code to the last line as a standard window callback function):

DispatchMessage (& MSG) // Application.run call DispatchMessage Notification // Windows Preparing Tune

Windows callback TWinControl.FObjectInstance ready before setting parameters on the stack: push LPARAM push WPARAM push UINT push HWND push (eip.Next); Windows callback address before the next statement; jmp FObjectInstance.Code stored on the stack; call TWinControl. FOBJECTINSTANCEFOBJECTINSTANCE.CODE only one Call instruction: call objectinstance.offset push Eip.next jmp instanceblock.code; call instanceblock.code

Instanceblock.code: POP ECX; store the value of Eip.next to ECX, used to take @mainwndproc and self jmp stdwndproc; jump to stdwndproc

StdWndProc assembly code: function StdWndProc (Window: HWND; Message, WParam: Longint; LParam: Longint): Longint; stdcall; assembler; asm push ebp mov ebp, esp XOR EAX, EAX xor eax, eax PUSH EAX push eax; Settings Message.Result: = 0 push lparam; why Borland does not directly Push DWORD PTR [EBP $ 14] from the above stack; get these parameters to re-Push once? Push WParam; Because TMessage's Result is PUSH DWORD PTR [EBP $ 10]; recorded last field, and the HWND PUSH MESSAGE of the callback function; is the first parameter, there is no way to compatibility. Push DWORD PTR [EBP $ 0C] MOV EDX, ESP MOV EDX, ESP; Sets Message in the stack in the stack is; MainWndProc parameter MOV Eax, [ECX] .longint [4] MOV EAX, [ECX $ 04]; Setting Self Injiral parameters for MainWndProc Call [ECX] .Pointer Call DWORD PTR [ECX]: Call TwinControl.mainWndProc (Self,; @Message) Add ESP, 12 Add ESP, $ 0c Pop EAX POP END; POP EBP RET $ 0010 MOV Eax, EAX

I don't understand the assembly code above, and I don't affect the understanding of the discussion below. ============================================================================================================================================================================================================= ============================= Supplement knowledge: TwndMethod Overview =============== ============================================================================================================================================================================================================= ============== Write this basics because I don't know what to pass when reading makeObjectInstance (MainWndProc). Find out the meaning of the TwndMethod type or understanding a small trick in the later VCL message system.

Twndmethod = Procedure (Var Message: tMessage) OF Object;

This type of declaration means: twwndMethod is a process type, which pointing to a process of receiving the TMessage type parameter, but it is not a general static process, it is object related. TwnotMethod stored in memory as a pointer to the process and a pointer to an object, so it takes up 8 bytes. The TWndMethod type variable must be assigned using an instantiated object. For example: Var SomeMethod: Twndmethod; Begin SomeMethod: = form1.mainWndProc; // is correct. At this time, SomeMethod contains the MAINWNDPROC / / and FORM1 pointers, which can be performed with SomeMethod (MSG) //. SomeMethod: = TForm.mainWndProc; // Error! Can't use class reference. END;

What is the difference between the Twior Method variable? Example: Var SomeMethod: Twndmethod; Begin SomeMethod: = form1.WndProc; // TForm.WndProc is virtual method END;

At this time, the compiler implements the address of the address of the WndProc process in the Form1 object in the Form1 object to point to SomeMethod, and the address of the FORM1 object. That is, the compiler correctly handles the value of the virtual method. Calling someMethod (Message) is equal to calling Form1.WndProc (Message). In the case where it may be assigned, the object method is best not to be designed to have a function of the return value, and the procedure is designed. The reason is very simple, assigning an object method with the return value to the TwndMethod variable, which will cause erliness when compiling.

============================================================================================================================================================================================================= ============================= ⊙ VCL message processing starts from TwinControl.MainWndProc =========== ============================================================================================================================================================================================================= ============= By discussing Application.Run, TwinControl.create, TwinControl.Handle, and TwinControl.createWnd, we can now turn focus to the message processing process inside the VCL . The source of the VCL control is the TwinControl.mainWndProc function. (If you can't understand this, please re-read the discussion above.)

Let's take a look at the code of the mainwndproc function (the unusually handled statement is deleted by me):

Procedure TWINCONTROL.MAINWNDPROC (Var Message: TMessage); Begin WindowProc (Message); END;

TwinControl.mainWndProc accepts a TMESSAGE type by reference (that is, hidden transmission address), TMessage is defined as follows (where WPARAM, LPARAM, Result has the joint field of HiWord and Loword, is deleted by me, exile for code Too long):

TMESSAGE = Packed Record Msg: Cardinal; WPARAM: Longint; LPARAM: Longint; Result: longint);

There is no window handle in tMessage, as this handle has been saved in TwinControl.Handle after the window is created. TMessage.msg is the ID number of the message, which can be a Windows standard message, a user-defined message or a Control message defined by the VCL. WPARAM and LPARAM are the same as WPARAM and LPARAM in the standard Windows callback function, and Result is equivalent to the return value of the standard Windows callback function. Note that MainWndProc is not a virtual function, so it cannot be overloaded by TWINControl inheritance. (Thinking: Why does Borland does not design the mainwndproc designer as a virtual function?)

Two-layer abnormal processing is established in MainWndProc, which is used to release resource leakage when an abnormality occurs during message processing, and call the default exception handling process. Surrounded by an abnormality is WindowProc (Message). WindowProc is an attribute of TControl (not TwinControl):

Property WindowProc: TwnotMethod Read FwindowProc Write FwindowProc;

The type of WindowProc is TwndMethod, so it is an object-related message processing function pointer (please refer to the introduction of the TWndMethod). FWindowProc is assigned to WndProc in tControl.create.

WndProc is a function of TControl, the parameter is the same as TwinControl.mainWndProc:

Procedure Tcontrol.WndProc (Var Message: TMessage); Virtual;

It turns out that the mainwndproc is just a proxy function, and the final processing message is the TControl.WndProc function.

So why Borland uses a FWindowProc to store this WndProc function, not directly call WNDPROC? I guess may be based on efficiency considerations. Remember the discussion of TWNDMETHOD above? A TwndMethod variable can be assigned a virtual function, the implementation of the compiler to this operation is to access the object's virtual function table through the object pointer, and transfer the function addresses in the virtual function table. Since WndProc is a function that calls a very high call frequency (may be calculated using "100 times / second" or "Thousands / Second"), so if you call WNDPROC to access the virtual function table, you will waste a lot of time, so In the TCONTROL constructor, the virtual address of WndProc is stored in WindowProc, and then the WindowProc will be called to speed up the call to speed up the processing speed.

============================================================================================================================================================================================================= ============================= ⊙ twinControl.WndProc ================== ============================================================================================================================================================================================================= ============ Transfer layer bend, now we just entered the place where the VCL message system processing: WndProc function. As mentioned earlier, TWINControl.mainWndProc has not processed the message after receiving the message, but transmits the message to the WindowProc processing. Since WindowProc always points to the address of the WndProc function of the current object, we can simply think that the WNDPROC function is a function of the first processing message in the VCL, calling WindowProc is just efficient. The WndProc function is a virtual function that begins in TControl, and is overloaded in TwinControl. Borland designs WndProc to virtual functions is to take over the message processing for each inheritance class, and pass unprocessed messages or processed messages to the previous layer class.

Here, the transfer process of the message processing and the constructor of the object is slightly compared:

The constructor of the object usually uses the Inherited statement to call the parent class in the first row code to initialize the member variable defined by the parent class. The parent class will also call the constructor of the grandfather class at the beginning of the constructor, so recursive, so one The creation process of the TwinControl object is Tcomponent.create -> tcontrol.create -> TwinControl.create.

The message processing function WndProc is the message you want first, and then see if it is handled to the WndProc of the parent class. So the process of the message is TwinControl.WndProc -> Tcontrol.Wndproc.

Therefore, if you want to analyze the process of the message, you should start from the WndProc process of the subclass, and then the WndProc process of the parent class. Since TwinControl is the class created by the first support window, its WndProc is important, it implements the most basic VCL message processing.

TwinControl.WndProc is mainly preprocesses some keyboard, mouse, window focus message, for unnequestable messages, TWINControl.WndProc directly returns, otherwise pass messages to TControl.WndProc processing. Check from TwinControl.WndProc to see:

WM_KEYFIRST..WM_KEYLAST: if Dragging Ten exit; // Note: Return directly with EXIT

This code means: If the current component is in drag and drop, discard all keyboard messages.

Look section: WM_MOUSEFIRST..WM_MOUSELAST: if IsControlMouseMsg (TWMMouse (Message)) then begin {Check HandleAllocated because IsControlMouseMsg might have freed the window if user code executed something like Parent:. = Nil} if (Message.Result = 0) and HandleAlLocated Then DefWindowProc (Handle, Message.msg, Message.wParam, Message.lparam); // DEFWINDOWPROC is a function exit; End; end;

The iSControlMouseMsg here is critical. Let us recall: TCONTROL classes do not create a Windows window, how does it receive messages such as mouse and redraw? It turns out that these messages are sent by its Parent window.

In the code above, TwinControl.isControlMouseMsg determines whether the mouse address is on the TControl class control, if not returning no value. TwinControl then call TControl.WndProc, TControl.WndProc calls TOBJECT.DISPATCH methods, which is later.

If the current mouse address falls on the TControl class control on the window, the mouse message is re-generated according to the relative position of the TControl object, and then the TControl.Perform method will send the machined mouse message directly to TControl.WndProc processing. TControl.Perform methods will then talk.

If TwinControl's inheritance class overloads the WndProc mouse message, but does not use Inherited to pass the message to the parent class processing, the object from TControl can not be received by the mouse message. Now let's do a test. The non-window controls such as TspeedButton on the following Form1 do not happen to Mouse events such as OnClick.

procedure TForm1.WndProc (var Message: TMessage); override; begin case Message.Msg of WM_MOUSEFIRST..WM_MOUSELAST: begin DefWindowProc (Handle, Message.Msg, Message.WParam, Message.LParam); Exit; // exit end; Else inherited;

The last line of the TwinControl.WndProc is:

Inherited WNDPROC (Message);

That is to call TControl.Wndproc. Let's take a look at Tcontrol.WndProc. ============================================================================================================================================================================================================= ============================= tControl.WndProc =================== ============================================================================================================================================================================================================= ============ Tcontrol.WndProc The main implementation is: Responding to the interaction with Form Designer (during design), double-click the event to change the mouse without supporting the double-click, click to determine the mouse Whether you need to display a prompt window (HintWindow) to determine whether the control is set to Autodrag, if it is the drag and drop process of the control, call TControl.MousewheelHandler implementation mouse wheel message Using Tobject.dispatch call DMT message processing method

TControl.WndProc is relatively simple, just talk about the second. Have you had such experience: When you quickly double-click the Button of a software, just form a Click event. So if you need to design a button that is also a button that is also clicking on the Click event, you can generate a button for the Click event. The process of processing the mouse message is required to refer to TControl.WndProc.

The last line of TControl.WndProc is Dispatch (Message), that is, if a message is not handled by TControl, the message will be processed by Dispatch.

TOBJECT.DISPATCH is a very critical way in the Delphi VCL message system.

============================================================================================================================================================================================================= ============================= ⊙ TOBJECT.DISPATCH ========================================= ============================================================================================================================================================================================================= ============ Tobject.dispatch is a virtual function, its declaration is as follows: procedure tobject.dispatch (var message); virtual;

Note that its parameters are similar to the parameters of MainWndProc and WndProc, but it does not specify the type of parameters. That is to say, Dispatch can accept any form of parameters.

Delphi's document stating that the first 2 bytes of the Message parameter are the ID (hereinafter referred to as MSGID), and the message processing method of the object is searched by the MSGID.

This paragraph does not understand the Dispatch method to provide more help, it seems that we must analyze the function of this function by reading the source code.

Although TOBJECT.DISPATCH is a virtual method, but is not overloaded by TPERSISTENT, TCOMPONENT, TCONTROL, TWINCONTROL, TFORM, etc. (Tcommondialog calls Tobject.dispatch, but is not important for the entire VCL message system) and is only by tControl. WndProc calls. So simply believe that if the message is not processed in WNDPROC, it is processed by TOBJECT.DISPATCH.

We are easy to explain to a very important question: MSGID is 2 bytes, while tMessage.msg is 4 bytes, if tControl.WndProc passes the TMessage message to the dispatch method, is it a wrong message?

To explain this problem, you must first understand the rules for Windows messages. Because all windows of the Windows operating system use messaging events and information, Microsoft must format the format of the window message. If each programmer defines the message ID value, it will definitely produce confusion. Microsoft divides window messages into five sections:

0x00000000 to WM_USER - 1 Standard Window Message, WM_M_User to WM_APP - 1 User Custom Window Class Message WM_APP to 0x0000BFFF application-level message 0x0000C000 to 0x0000FFFFRSTERWINDOWMESSAGE generated message range 0x00010000 to 0xFffffFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF Microsoft Reserved message, only Use by the system (WM_USER = 0x00000400, WM_APP = 0x00008000)

Did you find a question? The original application really available message is only 0x00000000 to 0x0000FFFF, that is, the message ID is only 20 bytes. (Borland is really cattle, even this can also come out.)

Since the memory storage rule of the Intel CPU is stored in the high address, the low byte is stored at a low address, so the first memory byte of the Dispatch's Message parameter is LOWORD (MSSAGE.MSG). The picture below is a memory storage method of the Message parameter:

| | Memory | ------------------------ | | "-------------------------------- ------- | | | | -------- | - Memory [Illustration: Integer Type of MSGID in memory (see Dispatch assembly code)] (for simple start, I use word For memory units instead of byte, I hope not to understand more.

You can now start reading the assembly code of TOBJECT.DISPATCH (do not understand the compilation, it will introduce the specific function):

Procedure tobject.dispatch (var message); Virtual; ASM PUSH ESI; saving ESI MOV SI, [EDX]; moving MSGID into Si (2 Bytes); if MSGID is Integer type, [EDX] = loword (msgid),; Maps or si, si je @@ default; if si = 0, call defaultHanLder CMP Si, 0C000H jae @@ default; if Si> = $ C000, call defaulthandler (note here) Push Eax; save the object's pointer MOV EAX, [Eax]; find the object's VMT pointer Call getDynamethod; call the dynamic method of the object; if the dynamic method zf = 0 is found, it did not find ZF = 1; Note: getDynamethod is a dynamic method in System.PAS; Compilation function POP EAX; Restore Eax's pointer JE @@ default; if the related dynamic method is found, call DefaultHandler Mov ECX, ESI; save the found dynamic method pointer into ECX POP ESI; restore ESI JMP ECX; call object Dynamic method @@ default: pop ESI; restore ESI MOV ECX, [EAX]; store the object's VMT pointer to ECX to call DEFAULTHANDLER JMP DWORD PTR [ECX] VMTOFFSET TOBJECT.DEFAULTHANDLER END;

The execution process of TOBJECT.DISPATCH is: put the MSGID into Si, as an index value of the dynamic method If Si> = $ C000, call DefaultHandler (that is, all registerWindowMessage generated message ID will be sent directly to DefaultHandler, followed An instance) Check if there is a corresponding dynamic method to find a dynamic method, then do this method is not found, then call DEFAULTHANDLER.

It turns out that the object method defined by the Message keyword is the dynamic method, casually caught several message processing functions from TWINControl:

Procedure WMSIZE (VAR Message: Twmsize; Message WM_SIZE; Procedure Wmmove; Message WM_MOVE); MESSAGE WM_MOVE

Let's finally understand the processing of WM_SIZE and WM_PAINT methods. Not only Windows messages, even the messages defined by Delphi have also handled in the same way:

Procedure CMenableDchanged (Var Message: TMessage); Message CM_ENABLED; Procedure Cmfontchange; Message Cm_FontChanged;

So if you define a message for a control, you can also use the Message keyword to define the function of the method, the VCL message system will automatically call the function you defined. Since the DISPATCH parameters are only in the first two bytes, and from the mainwndproc to WNDPROC to DISPATCH, they can deliver messages in a reference (delivery address), you can set the structure of the message to any structure, or even only MSGID - as long as you correctly access these parameters correctly in the function of the message.

The most critical dispatch method tells a paragraph, let us see what DEFAULTHANDLER has done?

============================================================================================================================================================================================================= ============================= ⊙ TwinControl.defaulthandler ================== ============================================================================================================================================================================================================= ============ DispatchHandler is from TOBJECT to exist, its declaration is as follows:

Procedure Tobject.defaultHandler (var message); virtual;

From the name, you can also see the probably possible purpose of the function: the final message processing function. In the definition of TOBJECT, DEFAULTHANDLER has no code, and DefaultHandler is overloaded after the class (tcontrol) that needs to process messages.

From the above discussion already known that the defaulthandler is called by TOBJECT.DISPATCH, so the parameter type of DEFAULTHANDLER and DISPATCH is the same as a type of Var Message.

Since DefaultHandler is a virtual method, the execution process is from the subclass to the parent class. In TwinControl and TControl's defaulthandler, follow the WndProc's execution rules, that is, TWINControl has not processed messages, and then use Inherited to call TControl.defaultHandler.

The TwinControl.defaultHandler first processes some less important Windows messages such as WM_CONTEXTMENU, WM_CTLCOLORMSGBOX, and so on. Then do two more important work: 1. Handling the RM_GETOBJECTINSTANCE message; 2. Call TwinControl.fdefwndProc for all unprocessed window messages. The following is discussed below. RM_GETOBJECTINSTANCE is the Windows system-level message ID that is automatically registered using the RegisterWindowMessage API when the application starts. This means that this message will pass unconditionally to DEFAULTHANDLER (see DISPATCH analysis) after reaching DISPATCH. TwinControl.defaultHandler discovered that this message set the Self pointer to the return value. There is a function ObjectFromhWnd in Controls.Pas to use the window handle to get the TWINCONTROL handle, which is implemented using this message. However, this message is used inside Delphi and cannot be used by the application. (Reflections: Every time the application starts calling RegisterWindowMessage, if the computer does not stop for a long time, whether the message ID between 0xC000 - 0xFFFF will be exhausted?)

In addition, TWINCONTROL.DEFAULTHANDLER uses the CallWindowProc API to call the TwNDControl.fdefwndProc window process using the CallWindowProc API in the case of TWINControl.fhandle. FDEfWndProc is a pointer, where is it initialized? Track it, find it is set to the following in TWINControl.createWnd:

FDEFWNDPROC: = params.windowclass.lpfnwndproc;

Remember the window creation process discussed earlier? The TwinControl.createWnd function first calls TwinControl.createParams to get the parameters of the window class to be created. CreateParams sets WNDCLASS.LPFNWNDPROC to Windows default callback functions defWindowProc API. However, CreateParams is a virtual function that can be overloaded by TWINControl inheritance, so the programmer can specify a window procedure for its own design.

So TWINCONTROL.DEFAULTHANDLER The intent of the fdefwndproc is obvious, that is, the processing of the message can be supported at the Win32 API level (such as importing window procedures from the DLL written in the C language), providing a sufficient elastic space to the programmer .

The last line of TwinControl.defaultHandler called inherited and passed the message to TControl.

Tcontrol.defaultHandler only processes three messages WM_GETTEXT, WM_GETTEXTLENGTH, WM_SETTEXT. Why do you have to deal with this few seemingly unimportant messages? The reason is that each window in the Windows system has a WindowText property, while the VCL TControl stores a saved in the FText member for analog-to-window, so TControl takes over these messages.

Tcontrol.defaultHandler did not call inherited, but there is no need to call, because the ancestors of TControl have not implemented the defaulthandler function. It can be considered to be DEFAULTHANDLER to do so.

The VCL message flow is so that. ============================================================================================================================================================================================================= ============================= ⊙ tControl.Perform and TwinControl.Broadcast ============== ============================================================================================================================================================================================================= ================ Functions in the VCL message system now in the VCL message system, but call frequencies are high.

TControl.Perform is used to directly send messages to the message processing function WndProc. The Perform method is not a false method. It recomnss the parameters into a TMESSAGE type, then calls WindowProc (Remember the role of WindowProc?) And returns Message.Result to the user. Its call format is as follows:

Function Tcontrol.Perform (msg: cardinal; wparam, lparam: longint): longint

Perform is often used to notify control of certain events, or get the result of message processing, as in the following example:

Perform (cm_enabledchanged, 0, 0); Text: = Perform (WM_GettextLength, 0, 0);

TwinControl.Broadcast is used to broadcast messages to each child control. It calls the WindowsProc process for all objects in the TwinControl.Controls [] array.

Procedure TWINCONTROL.BROADCAST (VAR message);

Note that the parameters of Broadcast are non-type. Noneto, the message is converted to the TMESSAGE type in the Broadcast function body, that is, the parameter of Broadcast must be the TMESSAGE type. So why do you want to design a non-type message? The reason is that TMessage has a lot of variants (MSG and Result fields do not change, WPARAM and LPARAM can be designed for other data types), designing Broadcast as non-type parameters, allowing programmers to force convert parameters before calling, but must be called Know this. For example, variants of the following characters are compatible with TMessage:

TWMKEY = PACKED RECORD MSG: cardinal; charcode: word; unused: word; keydata: longint; result: longint; end; ====================================================== ============================================================================================================================================================================================================= ===== ⊙ TwinControl.wmpaint ========================================= ====================================== The above is mentioned when discussing TwinControl.WndProc, tControl class The control of the control is generated from the Parent TWINControl. But we only discovered the production of mouse messages, then where is the redrawing message? The answer is TwinControl.Wmpaint:

Procedure TWINCONTROL.WMPAINT (VAR Message: Twmpaint); Message WM_Paint;

Two buffered heavy-drawn mechanisms have been established in TwinControl.wmpain, but we don't currently care about this, only to see the most critical code:

IF not (CSCUSTOMPAINT IN ControlState) and (controlcount = 0) THEN INHERITED / / Note Inherited Else Painthandler (Message);

This code means that INHERITED is called if the control does not support self-drawing and does not contain TControl. What is inherited? Since the parent class TControl of TwinControl.Wmpaint does not implement this message handle, the assembly code generated by Delphi is: Call Self.DefaultHandler. (TwinControl.defaultHandler simply call TwinControl.fdefwndproc.)

If the condition is sone, TWINControl.PaintHandler will call TwinControl.PaintHandler. PaintHandler calls the BeginPaint API to get a window device environment, and then use the device environment handle to call TwinControl.PaintWindow. PainTwindow in TwinControl simply passes the message to the defaulthandler. PainTwindow is a virtual function that can be rewritten in the inheritance class to achieve the drawing content you need. PaintHandler also calls the TwinControl.PaintControls method. PaintControls uses Perform to send WM_PAINT messages to all TControl controls included in the TwinControl control. In this way, the TCONTROL control has received the redrawn message.

Let us design a TWINControl inheritance as an exercise:

TMYWINCONTROL = Class (TWINCONTROL) Protected Procedure PainTwindow (DC: HDC); OVERRIDE; PUBLIC Constructor Create (Aowner: tComponent); OVERRIDE; END;

Constructor TMYWINCONTROL.CREATE (Aowner: Tcomponent); Begin inherited Create (Aowner); controlState: = controlState [cscustompaint]; // must notify WMPaint needs to draw your own END;

procedure TMyWinControl.PaintWindow (DC: HDC); var Rect: TRect; begin Windows.GetClientRect (Handle, Rect); FillRect (DC, Rect, COLOR_BTNSHADOW 1); SetBkMode (DC, TRANSPARENT); DrawText (DC, 'Hello, TMYWINCONTROL ', -1, RECT, DT_SINGLINE OR DT_VCENTER OR DT_CENTER); END;

The TMYWINCONTROL implemented above simply overloads the PaintWindow message, which can include TControl objects and draw them correctly. If you determine that the control does not need to include TControl objects, you can also overload the WMPAINT message directly, just like writing a normal WM_PAINT processing function with a C language.

============================================================================================================================================================================================================= ============================== ⊙ Take the path to describe the path transfer from TWINCONTROL ===================================================================================================================================================================== ============================================================================================================================================================================================================= =================== The following figure describes the call path of the message processing function after the message reaches the message. Each layer indicates that the function is called by the upper function. TWinControl.FObjectInstance | -TWinControl.MainWndProc | -TWinControl.WindowProc | -TWinControl.WndProc | -TControl.WndProc | -TObject.Dispatch | -Call DMT messages | -TWinControl.DefaultHandler | -TControl.DefaultHandler

Note: As mentioned above, WindowProc in the above figure is a pointer, so it is actually equal to WNDPROC in the compiler level instead of calling WNDPROC, in order to prevent itself from dividing the design branch confusing to divide two layers. TOBJECT.DISPATCH has two paths. If the current control implements a message processing function with the Message keyword, call the function, otherwise the defaulthandler is called. Some message processing functions may have returned in the middle, and some messaging functions may be recursively called.

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

New Post(0)