Understanding the message processing mechanism in the VCL by the code in the VCL

xiaoxiao2021-03-06  99

Delphi, a very excellent development tool, has a powerful visual development environment, assemblies fast development model, excellent VCL library, fast code compiler, powerful database and web development capabilities, as well as a number of third-party controls Support ... (here, omitting X thousand words, since everyone knows, do not waste mouth water ^ _ ^) saying that VCL is excellent, it is not possible to mention the more comprehensive and perfect packages for Windows messages and APIs. Because this developer does not even need the details of the Windows message processing in most cases, just write a few lines of event driver code! But if you want to do some understandings as developers, continue, through the VCL code itself, you can experience the message processing mechanism in the VCL. (The following code is taken from Delphi 6) Speaking that the message processing in the VCL does not mention TAPPLICATION, Windows will establish a message queue for each currently running program, which is used to complete the user and program interaction, is completed by Application. Concentration of Windows Messages! First access the message loop through Application.Run, which calls HandleMessage. Procedure; var Msg: TMSG; Begin If Not ProcessMSG; Begin Idle ProcessMessage (MSG) THEN IDLE (MSG); // First call the processMessage process, return value to false call idle, is when it is idle, that is, the message queue is waiting Call the IDle when processed. End; function payplication.processMessage (var Msg: TMSG): Boolean; var handled: boolean; begin result: = false; if peekmessage (msg, 0, 0, 0, pm_remove) THEN // Query message queue has no news waiting Processing, parameter pm_remove will be deleted after the message is processed.

Begin Result: = true; if msg.Message <> wm_quit the// If it is wm_quit, the process is terminated, otherwise the following code becomes: = false; if Assigned (fonmessage) THEN FONMESSAGE (MSG, Handled); if not ishintmsg (Msg) And not handled and not ismdimsg (msg) and not iskeymsg (msg) and not isdlgmsg (msg) THEN BEGIN TRANSLATEMESSAGE (MSG); // Transfer record MSG to Windows to convert DispatchMessage (MSG); // will record MSG back to Windows End; END ELSE FTERMINATE: = true; end; end; then how each VCL object in the program receives Windows messages? This will also start from the creation of the form! First find Windows.RegisterClass (WindowClass) // in TwinControl.CreateWnd (WindowClass) // Call registerClass to register a form class to look up WindowClass.lpfnWndProc: = @initWndProc; // This specifies the message processing function of the window to @initWndProc! To find function InitWndProc (HWindow: HWnd; Message, WParam, LParam: Longint): Longint; found CreationControl.FHandle: = HWindow; SetWindowLong (HWindow, GWL_WNDPROC, Longint (CreationControl.FObjectInstance)); no? It turns out that the initwndproc is initially called, and the window process of the processing message is used to specify the window process for the processing message. Go back to TwinControl.createfObjectInstance: = classes.makeObjectInstance (MainWndProc); find the key, maybe some friends are very familiar with MakeObjectInstance this function, its role is to convert a member process as a standard process. Wave a circle? why? It is very simple, because the form member process includes a hidden parameter to pass the Self pointer, so it needs to be converted to a standard process.

Const instancecount = 313; // Is this not difficult to understand? 314 * 13 10 = 4092, then great, record size TInstanceBlock is more than the PageSize type PObjectInstance defined below = ^ TObjectInstance; TObjectInstance = packed record Code: Byte; Offset: Integer; case Integer of 0: (Next: PObjectInstance ); 1: (Method: TWndMethod); end; type PInstanceBlock = ^ TInstanceBlock; TInstanceBlock = packed record Next: PInstanceBlock; Code: array [1..2] of Byte; WndProcPtr: Pointer; Instances: array [0..InstanceCount ] of TObjectInstance; end; var InstBlockList: PInstanceBlock; InstFreeList: PObjectInstance; 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; passed the recorded TMessage pointer constructed in the stack to EDX MOV EAX, [ECX] .longint [4]; Pass the Self pointer to Eax, the Self pointer in the class is to point to the VMT entry address CALL [ECX ] .Pointer; call mainwndproc method Add ESP, 12 POP Eaxend; Function Calcjmpoffset (SR C, DEST: POINTER: Longint; Begin Result: = longint (DEST) - (longint) (long); end; function makeobjectInstance (Method: twwndmethod): Pointer; Const blockcode: array [1..2] OF byte = ($ 59, {POP ECX} $ E9); {JMP StdWndProc} PageSize = 4096; var Block: PInstanceBlock; Instance: PObjectInstance; begin if InstFreeList = nil then begin Block: = VirtualAlloc (nil, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE) // Assign virtual memory and specify this memory as readable and writable and execute block ^ .next: = InstblockList; Move (Blockcode, Block ^ .code, sizeof (blockcode)); block ^ .wndprocptr: = Pointer Calcjmpoffset (@block ^ .code [2], @

StdWndProc)); Instance: = @ Block ^ .Instances; repeat Instance ^ .Code: = $ E8; {CALL NEAR PTR Offset} Instance ^ .Offset: = CalcJmpOffset (Instance, @ Block ^ .Code); Instance ^ .Next : = InstFreeList; InstFreeList: = Instance; Inc (Longint (Instance), SizeOf (TObjectInstance)); until Longint (Instance) - Longint (Block)> = SizeOf (TInstanceBlock); InstBlockList: = Block; end; Result: = InstFreeList Instance: = INSTFREELIST; InstfreeElist: = instance ^ .next; instance ^ .method: = method; end; (Note: those 16 of the above 16) is actually some 16-en-machine code $ 59 = Pop ECX $ E8 = Call $ E9 = JMP) The above code looks a bit chaos, but it is also very good to integrate! MakeObjectInstance actually built a block chain list to see the structure of the record TinstanceBlock can know that its structure is as follows: Next // Next Points Code // POP ECX and JMPWndProcptr // and STDWndProc address Interface Instances // Next It is 314 instance linked list instanced linked list through record TOBJECTINSTANCE, it is also very well understood by the content code // calloffset // address Offset // Pointer to the object method (combined with TMETHOD is very well understood) TWndMethod This type of object method pointer points to the structure of data) Well now to review this process, what is Windows callback? In fact, it is turned to and executes a dynamically generated code: first execute the Call Offset, rotate the POP ECX according to the offset, of course, due to the next instruction, the next instruction will be put into the stack, so the pop-up is to point to the object method. pointer.

The next is to execute JMP [STDWndProc], which assumes the recorded TMessage pointer constructed in the stack to the EDX, and the TMETHOD is combined according to the above explanation, it is easy to understand MOV Eax, [ECX] .longint [4]; Passing Self The pointer to the EAX, the Self pointer in the class is to point to the VMT entry address call [ECX] .pointer; call the mainwndproc method is now finally suddenly turned over, the Windows message is passed to TwinControl.MainWndProc, compared to the Power of the callback in the MFC AFXWndProc is high in efficiency of the corresponding object pointer according to the form handle! VCL is more than MFC! ^ _ ^ Now finally found a way to MainWndProc procedure TWinControl.MainWndProc (var Message: TMessage) VCL receive messages; begin try try WindowProc (Message); // create an instance of TControl Since when has FWindowProc point WndProc, so here is real call WndProc finally FreeDeviceContexts; FreeMemoryContexts; // call FreeDeviceContexts and FreeMemoryContexts is to ensure VCL thread-safe end; except Application.HandleException (Self); end; end; this can not be ignored TWinControl.WndProc procedure TControl.WndProc (var Message: TMessage Var form: tkeyboardState; wheyState: tkeyboardState; wheySg: TCMMouseWheel; Begin ... / / The above message related processing code is omitted, and you can view it when you study some specific messages ... Dispatch (Message); // Call DISPATCH Processing END; Next, do not rush to see the corresponding code in Dispatch.

Think about it, what? The above is just inherited on TwinControl's handle control, which is inherited how to get and handle messages inherited to TGRAPHICCONTROL. The following is the following code: Case Message.msg of ... wm_mousefirst..wm_mouselast: // Note 1: The following explains this if iscontrolmousemsg (TWMMouse (Message). . 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); Exit ; end; ... end; inherited WndProc (Message); // WndProc method performed ancestor class function TWinControl.IsControlMouseMsg (var Message: TWMMouse): Boolean; var Control: TControl; P: TPoint; begin if GetCapture = Handle then begin Control: = nil; if (CaptureControl <> nil) and (CaptureControl.Parent = Self) then Control: = CaptureControl; end else Control: = ControlAtPos (SmallPointToPoint (Message.Pos), False); // this is obtained by ControlAtPos The control of the mouse Result: = false; if constral <> nil dam px: = message.xpos - Control.Left; PY : = Message.ypos - Control.top; Message.Result: = Control.Perform (Message.msg, Message.Keys, Longint (P))); // Call Perform method Send message to the corresponding instance Result: = true; end; end; property WindowProc: TWndMethod read FWindowProc write FWindowProc; function TControl.Perform (Msg: Cardinal; WParam, LParam: Longint): Longint; var Message: TMessage; begin Message.Msg: = Msg; Message.WParam: = WPARAM; Message.lParam: = lParam; Message.Result: = 0; if Self <> nil the windowProc (message); // Due to the FWindowProc to WndProc due to TControl creation instance,

So here is actually calling WndProc Result: = message.Result; END; VCL is to distribute messages to those graphic controls that are inherited in TGRAPHICCONTROL.

The above is Windows Messages, which seems to have to talk about two frequently used VCLs: CM_MOUSEENTER, CM_MOUSELEAVE (cm = short of control message how they are processed? Still look at the above (if not processmessage (msg);), these two are not Windows messages, so Idle Procedure Tapplication.Idle (Const MSG: TMSG); var Control: tControl; Done: boolean; begin Control: = DoMouseIdle; // call the method DoMouseIdle ... end; function TApplication.DoMouseIdle: TControl; var CaptureControl: TControl; P: TPoint; begin GetCursorPos (P); Result: = FindDragTarget (P, True); // Gets the current mouse stayed in the control IF (CSDesigning in result.componentState ". = Nil; captureControl: = getcapturecontrol; if fmousecontrol <> result the previous recorded mouse pointer points to and current control is the same as the control points to begin if ((FMouseControl <> nil) and (CaptureControl = nil)) or ((CaptureControl <> nil) and (FMouseControl = CaptureControl)) then FMouseControl.Perform (CM_MOUSELEAVE, 0, 0); // Send message cm_mouseeleave to the control fmousecontrol: = result; // record the control IF pointed to by the current mouse pointer (((CaptureControl <> nil)) or (CaptureControl <> nil) or (CaptureControl <> NIL)) or (CaptureControl <> nil) and (fmousecontrol = capturecontrol) The FmouseControl.Perform (cm_ MOUSEENTER, 0, 0); // send message CM_MOUSEENTER to control the mouse pointer is now located end; end; function FindDragTarget (const Pos: TPoint; AllowDisabled: Boolean): TControl; var Window: TWinControl; Control: TControl; begin Result: = NIL; Window: = FindvCLWindow (POS); // Returning this is TwinControl, is a handle of control if Window <> nil dam results: = window; control: = window.controlatpos (Window.ScreenToClient (POS) AllowDisabled;

// There may also be a graphic control in TGRAPHICCONTROL at the point pointing to the mouse, while the above return is just its container control if constrole <> nil the result: = control; // If there is a control END obtained with ControlatPos; End; then turned to the above TCONTROL.PERFORM now all the questions are also concentrated on the Dispatch's body, how is the message triggered the process of processing? First, the declaration of the message processing method: Procedure cmmouseenter (var message: tMemage); Message cm_mouseenter; this is actually considered to have a dynamic method, calling Dispatch actually found corresponding to the DMT (Dynamic Method) The dynamic method pointer, then execute / / above, the register EAX is the class Self pointer, that is, the VMT entry address, the register EDX pointing to the record message, the pointer, the record message, the pointer procedure TOBJECT.DISPATCH (VAR message); ASM PUSH ESI MOV Si, [EDX]; message number, that is, the value of MSG in TMESSAGE, corresponds to CM_MOUSEENTER $ B013 (45075) or Si, Si Je @@ default Cmp Si, 0C000H jae @@ default push Eax Mov Eax, [EAX] ; VMT entrance address CALL GETDYNAMETHOD; call getDynamethod Find POP EAX JE @@ default; if you find the value of the flag bit register is 0 in getDynamethod, if it is 1, it means not found, execute the jump MOV ECX, ESI; The pointer gives the ECX POP ESI JMP ECX; jumps to the position pointed to the ECX, the process of calling CMMouseEnter through the message number is completed @@ default: pop esi mov ECX, [EAX] JMP DWORD PTR [ECX] .VMTDEFAULTHANDLER; if This control and its ancestral class have no processing method corresponding to this message number, call defaultHandlerend; procedure getdynamethod; {function getDynamethod (VMT: Tclass; selector: smallint): Pointer;} ASM {-> EAX VMT of Class} {si Dynamic Method Index} {<- ESI Pointer To Routine} {zf = 0 if Found} {TRASHES: EAX, ECX} Push EDI XCHG EX, ESI; Switch EX and ESI's value, this is VMT in ESI The entrance address, EAX is the message number, that is, the code JMP @@ Havemt @@, corresponding to the dynamic method.

OuterLoop: MOV ESI, [ESI] @@ Havevmt: MOV EDI, [ESI] .vmtdynamictable; Try to pass DMT's entry address to EDI TEST EDI, EDI; whether it is 0 to determine if there is DMT JE @@ Parent There is no way to jump to the parent class Continue MOVZX ECX, Word PTR [EDI]; Take [EDI], the value of DMT's head two bytes passed to ECX, namely the number of dynamic methods PUSH ECX Add EDI, 2; Address plus 2, that is, some of the number of DMTs to store dynamic methods in the DMT, the data of EAX and EDI points to data, until it is found (zf = 1) or ECX = 0 so JE @@fact Pop ECX @ @Parent: MOV ESI, [ESI] .Vmtparent; try to get the parent class Test ESI, ESI; if it is 0 to determine if there is a parent class JNE @@ outerloop; there is a jump to @@ outerloop for search JMP @@@ Exit @@fact: POP Eax Add Eax, Eax Sub Eax, Ecx {this will always clear the z-flag!}; this sentence is to set the sign bit Zf 0 MOV ESI, [EDI] EAX * 2-4]; Pass the obtained method pointer to ESI, understand this sentence first to learn about the content of the DMT structure; in the VCL, the structure of the DMT is like this, the first 2 The number of dynamic methods in the DMT is stored in the DMT, then the method code, a total of 4 * n bytes, and finally the method pointer is also 4 * n bytes! This is very well understood, EDI-4 is the address of the current method code, EDI-4 4 * n = EDI EAX * 2-4 (because of the ADD EAX, EAX, EAX = 2 * n) Therefore, [EDI EAX * 2-4] is the corresponding method pointer found.

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

New Post(0)