In-depth discussion of MFC messages loops and message pumps

zhaozj2021-02-16  56

In-depth discussion of MFC messages loops and message pumps

First, you should clear the MFC's message loop (:: getMessage, :: PeekMessage), message pump (CWINTHREAD :: PumpMessage) and MFC's message between the windows is two different things. In the MFC application (application class based on CWINTHREAD inheritance), there must be a message loop, his role is to read the message from the application's message queue and send it (:: DispatchMessage). The message route is that the system (user32.dll) is delivered to which window is delivered to the message, and how the message is passed in the window.

The message is divided into queue messages (message queue from the thread) and non-queue messages (not entering the thread). For queue messages, the most common messages triggered by mouse and keyboard, such as WM_Mousermove, WM_CHAR, etc., for example: WM_Paint, WM_TIMER, and WM_QUIT. When the mouse, the keyboard event is triggered, the corresponding mouse or keyboard driver converts these events into the corresponding message, then transports to the system message queue, and the Windows system is responsible for adding the message to the message queue of the corresponding thread, so There is a message loop (read and send a message from the message queue). Another is a non-queue message, he is bypassed the system queue and message queue, and the message is sent directly to the window process. For example, when a user activates a window system to send WM_Activate, WM_SETFOCUS, and WM_SETCURSOR. Send a WM_CREATE message when you create a window. Behind you will see that MS is very reasonable, as well as his entire implementation mechanism.

Here, the Message cycle of the MFC is described here. First look at the program startup, how to enter the message loop: _twinmain -> AFXWinMain -> AFXWininit -> Cwinthread :: InitApplication -> Cwinthread :: InitInstance -> Cwinthread :: run

The message loop of the non-dialog box program starts from a RUN of this cwinthread ...

Part 1: Message loop mechanism for non-dialog box program.

//thrdcore.cpp//main running routine unstert_valid (this);

// for tracking the iDLE Time State Bool Bidle = true; long LidleCount = 0;

. // acquire and dispatch messages until a WM_QUIT message is received for (;;) {// phase1: check to see if we can do idle work while (bIdle && :: PeekMessage (& m_msgCur, NULL, NULL, NULL, PM_NOREMOVE! )) {// call onidle while in bidle statiff (! OnIdle (LidleCount )) BIDLE = false; // Assume "no idle" state}

// Phase2: Pump Messages While Available Do {// Pump Message, But Quit On WM_QUIT IF (! PumpMessage ()) Return EXITINSTANCE ();

// reset "no idle" state after pumping "normal" message if (IsIdleMessage (& m_msgCur)) {bIdle = TRUE; lIdleCount = 0;}} while (:: PeekMessage (& m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)); } // Unlimited loop, the exit condition is to receive a WM_Quit message.

Assert (false); // not reachable}

This is an infinite loop, his exit condition is to receive a WM_Quit message:

IF (! pumpMessage ()) Return ExitInstance ();

In PumpMessage, if you receive a WM_QUIT message, return false, so the exitInstance () function executes, jump out of the loop, return the exit code of the program. So, a program is to exit, only use the function in the code.

Void PostquitMessage (Int nexitcode). Specifies that the quit code Nexitcode can exit the program.

Let's discuss the process of this function Run, divided into two steps:

1, the first internal cycle PHASE1. The BIdle represents whether the program is idle. What he means is that if the program is idle and there is no message to be processed in the message queue, then the virtual function OnIdle is invalid. In this process, the UI interface (such as the Enable and Disable status of the toolbar button), delete temporary objects (such as object pointers you get with fromhandle. For this reason, the object pointer to the from "to send by fromHandle is not secure between the functions. Because he has no persistence). OnIdle is overloaded, you can overload him and return True to keep the message loop in idle state.

Note: MS uses temporary objects for efficiency, making memory efficiently, and automatically revokes resources when idle. Regarding the translation of the handle into an object, there may be several methods. Generally, an object OBJ is applied first, then use Obj.attatch to bind a handle. This generated object is permanent, you must use obj.detach to release the object.

2, the second internal cycle Phase2. Start the message pump (PumpMessage) in this loop, if not a WM_QUIT message, the message pump sends a message to (:: DispatchMessage). The destination of the message is the window corresponding to the HWND field in the message structure. //thrdcore.cppbool cwinthread :: pumpMessage () {assert_valid (this); // If it is wm_quit to exit function (Return False), this will result in the end of the program. IF (! :: getMessage (& M_MSGCur, Null, Null, Null )) {#ifdef _DEBUG if (afxTraceFlags & traceAppMsg) TRACE0 ( "CWinThread :: PumpMessage - Received WM_QUIT./n"); m_nDisablePumpCount ; // application must die // Note: prevents calling message loop things in 'ExitInstance' // Will Never Be Decremented # endif returnaf false;}

#ifdef _DEBUG if (m_nDisablePumpCount = 0!) {TRACE0 ( "Error: CWinThread :: PumpMessage called when not permitted./n"); ASSERT (FALSE);} # endif # ifdef _DEBUG if (afxTraceFlags & traceAppMsg) _AfxTraceMsg (_T ("PumpMessage", & m_msgcur); # endif

// Process this message

IF (m_msgcur.MESSAGE! = WM_KICKIDLE &&! PretranslateMessage (& M_MSGCUR)) {: TranslateMsSage (& M_MSGCur); // Key Conversion :: DispatchMessage (& M_MSGCur); // Delivery message} Return True;}

There is a particularly important function in this step. Everyone must know: PretranslateMessage. This function performs pre-processing of the message before :: DispatchMessage sends a message to the window. The PretranslateMessage function is a member function of CWINTHREAD. When you overload, you are in the View class or the main window class, then how did it enter other classes? The code is as follows: //thrdcore.cppbool cwinthread :: pretranslateMessage (msg * pmsg) {assert_valid (this);

// If it is a thread message, the processing function IF of the thread message will be called (PMSG-> hwndMsSageex (PMSG)) Return True;

// Walk from target to main window cwnd * PMainWnd = AFXGETMAINWND (); if (cwnd :: walkpretranslatetree (pmainwnd-> getsafehwnd (), pmsg) Return True;

// in Case of Modeless Dialogs, Last Chance Route Through Main // Window's Accelerator Table IF (PMainWnd! = NULL) {CWND * PWND = CWnd :: fromHandle (PMSG-> HWND); if (PWND-> getTopleVelParent ()! = PMainWnd) Return PMainWnd-> PretranslateMessage (PMSG);

Return False; // no special processing}

It can be seen from this function:

First, if (PMSG-> hWnd == Null), this is a thread message. Call CWINTHREAD :: DispatchThreadMessageex to message mapping table Find Message Inlet and call message processing functions.

Note: Generally, the message between the thread is generally transmitted. He and the window message are different. You need to specify the thread ID. The message is placed in the message queue to the target thread; uses on_thread_message (message, memberfxn) macro to map thread messages And his handler. This macro must be in the application class (inherited from cwinthread) because only the application class handles thread messages. If you use this macro in other classes (such as view classes), the message processing function of thread messages will not be able to get a thread message. Second, the PretranslateMessage function of the target window of the message first gets the message processing, if the function returns false, then his parent window will get the handling of the message until the main window; if the function returns true (indicates that the message has been processed), Then do not need to call the PretranslateMessage function of the parent class. In this way, the target window of the message and his parent window can have the opportunity to call PretranslateMessage - Preprocessing before the message is sent to the window (if you handle it, return false ", if you want a message If you don't pass it to the parent class, you will return true.

Third, if the message target window and the main window do not have a parent-child relationship, then call the PretranslateMessage function of the main window. why is it like this? By the second step, the parent window of a window is not a main window, although its PretranslateMessage returns false, the main window does not have the opportunity to call the PretranslateMessage function. We know that the acceleration key is generally in the PretranslateMessage function of the frame window. I look for the processing of the accelerator key conversion in the MFC, only CFRAMEWND, CMDIFRAMEWND, CMDICHILDWND, etc. have. So, the third step means that if the target window (his parent window is not a main window, such as such a non-mode dialog box) makes the message's pre-processing continues to roam (his PretranslateMessage returns false), then give One chance to call PretranslateMessage for the main window (What is a certain acceleration message?), Which ensures that the main window can be made good if there is a non-mode dialog box. I made a small example, in PretranslateMessage in the dialog class, returned False. In the main window displays this non-Mode dialog, it is still able to activate the shortcut key of the main window when the dialog has focus.

In short, the entire framework is to make the target window of each message (including his parent window) have the opportunity to participate before the arrival. Oh ~

At this point, the mechanism of the message loop and the message pump of the non-dialog box is almost. This mechanism is in an unlimited loop, constantly obtains messages from the message queue, and ensures that the thread message of the program can be processed, and the window message is sent to the corresponding window processing process after the pre-processes. So, there is still a little doubt, why do you want to call :: PeekMessage, call :: getMessage, what is the difference?

Note: In general, getMessage is designed to efficiently get messages from the message queue. If there is no message in the queue, the function getMessage will cause a thread to sleep (let the CPU time). PeekMessage is a judgment if there is no message in the message queue, it immediately returns 0, which will not cause the thread to sleep in sleep.

In the first internal cycle above the first internal cycle, its parameter PM_NOREMOVE indicates that the message is not removed from the message queue, but a test query, if there is no message in the message queue, he returns 0, if this thread The idle words will cause the message loop to call the onIdle processing (the importance of this function is said). If you change :: PeekMessage, then if there is no message in the message queue, the thread will sleep until the next time the thread gets the CPU time and there is a message appearance, so that the message loop is free. Time is not available, and OnIdle will not be executed. This is why you need :: peekmessage, and use :: getMessage. Part II: Message Cycle Mechanism of Dialog Box Program

The MFC project based on the dialog is different from the message cycle mechanism. In fact, the MFC dialog box engineering is the Mode dialog. He and the difference between the non-dialog box procedures mentioned above, mainly in the initInstance () of the application object.

//dlg_5Dlg.cppBOOL CDlg_5App :: InitInstance () {AfxEnableControlContainer (); # ifdef _AFXDLL Enable3dControls (); // Call this when using MFC in a shared DLL # else Enable3dControlsStatic (); // Call this when linking to MFC statically # ENDIF

CDLG_5dlg DLG; // Defines a dialog object m_pmainwnd = & dlg; int nresponse = DLG.Domodal (); // Dialog box message loop start IF (NRESPONSE == idok) {// Todo: Place Code Here To Handle when the dialog is // dismissed with ok} else f (nresponse == idcancel) {// Todo: Place Code Here to Handle by Dialog Is // Dismbled with cancel}

// Since the Dialog Has Been Closed, Return False So That We Exit The // Application, Rather Than Start The Application's Message Pump. Return False;

Note: The InitInstance function returns false, which can be seen from the top programs that CwinThread :: run is not performed. That is, the message loop said above is not performed in the dialog. In fact, the dialog also has a message loop, and her message loop is in a runmodallOP function in the cdialog :: Domodal () virtual function.

The implementation of this function is in the CWND class: int CWnd :: Runmodalloop (DWORD dwFlags) {ask (:: iswindow (m_hwnd)); // WINDOW MUST BE CREATED ASSERT (! (M_nflags & wf_modalloop); // WINDOW MUST Not Already Be in Modal State

// for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0; BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && (GetStyle () & WS_VISIBLE); HWND hWndParent = :: GetParent (m_hWnd);! M_nFlags | = ( WF_MODALLOOP | WF_CONTINUEMODAL); MSG * PMSG = & AFXGETTHREAD () -> m_msgcur; // acquire and dispatch message unsterac (;;) {assert (continuemodal ());

// Phase1: Check to See if We Can do Idle Work While (Bidle &&! :: PeekMessage (PMSG, NULL, NULL, NULL, PM_NOREMOVE) {Assert (ContinueModal ());

// show the dialog when message queue {showwindow (sw_shownormal); UpdateWindow (); bshowder = false;}

// call OnIdle while in bIdle state if (! (DwFlags & MLF_NOIDLEMSG) && hWndParent! = NULL && lIdleCount == 0) {// send WM_ENTERIDLE to the parent :: SendMessage (hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM) m_hWnd) }} F ((DWFLAGS & MLF_NOKICKIDLE) ||! SendMessage (WM_KICKIDLE, MSGF_DIALOGBOX, LIDECOUNT )) {// stop idle processing next time bidle = false;}}

// Phase2: Pump Messages While Available Do {Assert (ContinueModal ());

// pump message, but the implementation of the BUT Quit On WM_QUIT / / PUMPMESSAGE (Message Pump) is similar. They are sent to the window. IF (! AFXGETTHREAD () -> PumpMessage ()) {AFXPOSTQUITMESSAGE (0); Return -1;}

// show the window when certain special messages rec'd if (bShowIdle && (pMsg-> message == 0x118 || pMsg-> message == WM_SYSKEYDOWN)) {ShowWindow (SW_SHOWNORMAL); UpdateWindow (); bShowIdle = FALSE;}

IF (! Continuemodal ()) goto exitmodal;

// reset "no idle" state after pumping "normal" message if (AFXGETTHREAD () -> ISIDLEMESSAGE; LIDECOUNT = 0;}} While (:: PeekMessage (PMSG, NULL, NULL, NULL , Pm_noremove));} // Unlimited loop

EXITMODAL: M_NFLAGS & = ~ (wf_modalloop | wf_continuemodal); return m_nmodalResult;}

Let me talk about how to quit this infinite loop, in the code: if (! ContinueModal ()) goto exitmodal; decide whether to exit the loop, the message loop function returns to the end of the program. Bool CWnd :: ContinueModal () {Return M_nflags & wf_continuemodal;

NOTE: CWND :: ContinueModal () Function Checks the dialog box to continue mode. Returns True, indicating that it is mode; return false, indicating that the dialog is no longer mode (will be over).

If you want to end the dialog, you will eventually call the function cWnd :: endmodalloop, which cancels the mode flag of m_nflags (the ContinueModal function in the message loop will return false, the message loop will end, the program will exit); then excit the message loop read news. That is, the end mode dialog is a flag and change this flag. His code is:

//wincore.cppvoid cWnd :: EndModalloop (int NRESULT) {assert (:: iswindow (m_hwnd));

// this result will be returned from cwnd :: runmodalloop m_nmodalResult = NRESULT

// Make Sure A Message Goes Through To EXIT The MODAL LOOP IF (M_nflags & Wf_Continuemodal) {m_nflags & = ~ wf_continuemodal; postmessage (wm_null);}}

Note: PostMessage (NULL) is useful. If there is no message in the message queue, ContinueModal () in the possible message loop will not be executed immediately, send an empty message is the excitation message loop.

Let's talk about what the message cycle in the CWnd :: Runmodalloop function is doing something: 1, the first internal loop. First query the message from the message queue, if the dialog is idle, and there is no news in the message queue, he will do three things, everyone should understand what it means from the literal. The most important thing is to send a WM_KICKIDLE message. why? The first part told that the non-dialog box program uses OnIDLE to update the user interface (UI), such as toolbar, status bar. Then, if there is a toolbar and status bar in the dialog, where is updated (there are many programs on the Internet)? You can handle WM_KICKIDLE message: LRESULT CDLG_5DLG :: ONKICKIDLE (WPARAM W, LPARAM L) {// Call CWnd :: UpdateDialogControls Update Update UpdateDialogControls (this, true);}

NOTE: CWND :: Updatedialog function Sends a CN_UPDATE_COMMAND_UI message to all user interface dialog controls. 2, the second internal cycle. The most important thing is the PumpMessage delivery message to the target window. Others, like the second IF statement, 0x118 message seems to be a WM_SYSTIMER message (a message used to notify the cursor beating). That is, if the message is WM_SYSTISKEYDOWN, and the idle display flag is true, the window is displayed and the window is notified immediately.

In short, the message circulation mechanism and non-dialog box (such as SDI, MDI) of the dialog are still similar, and only the side focus is different. The mode dialog is mode display, naturally has his characteristics. The following sections discuss the difference between the mode dialog and the Non-Mode dialog box. Because the Mode dialog has its own special message loop; not the mode dialog, the shared program message loop, and the ordinary window has no big difference.

Part III: Differences between Mode dialogs and non-mode dialogs

There are many people discuss this topic, I tell me what I understand. In the MFC framework, a dialog object Domodal can generate a mode dialog box, and create a non-Mode dialog box. In fact, whether it is a mode dialog or a non-Mode dialog box, it is called: CREATEDIALOGIRECT (***) function inside the MFC to create a non-mode dialog. Just the Mode dialog made more work, including invalidation of the parent window, then enter your own message loop, and more. :: CREATEDIALOGODIRECT (***) Function finally calls the CREATEWINDOWEX function notification system creates a form and returns the handle, and he does not implement its own message loop inside. The Non-Mode dialog is created immediately returned, and a message loop is shared with the main program. The non-mode dialog box must only return after the end of the dialog, you have a message loop. For example, the following code: CMYDLG * PDLG = New CMYDLG; PDLG -> Create (IDD_DIALOG1); PDLG-> ShowWindow (SW_SHOW); MessageBox ("ABC"); Non-mode dialogs and message box messages are almost simultaneously. If CREATE is changed to Domodal, then the mode dialog can only pop up, after the dialog is turned off (the mode dialog is ended, the message box is played.

Note: You can call getParent () -> EnableWindow in the Mode dialog; this, the menu of the main window, the toolbar is activated, which can be used. MFC uses a non-Mode dialog to analog mode dialog, and in the Win32 SDK program, the Mode dialog box inspires his parent window Enable operation.

About the message loop summary:

1. What is the high-level message loop? The message loop is actually not what is the truth. If a postman is constantly sent in a city, what do we ask him to do? Ask him to run back, but he can only appear in one place at a time. If our application has only one thread, we want him to convey a message for the window, what do we do? In a loop constant detection message and send him to the appropriate window. There are many windows, but there is only one message cycle, and only one place is in executing code a time. why? Look at the second point.

2. Because it is a single thread (when the program process starts, only one thread, we call him the main thread), so just like a postman, you can only work in some place. What does that mean? For example, use :: DIAPATCHMESSAGE delivery message, before the window processing (WinProc, window function) returns, he is blocking, will not return immediately, that is, the message cycle can no longer read the message from the message queue. Until :: DispatchMessage Returns. If you perform a dead loop operation in a window function, you will run with the PostQuitMessage function, and the program will drop. While (1) {PostquitMessage (0); // Sample Down.} So, when the window function processing does not return, the message loop does not read the message from the message queue. This is also why you want to use an infinite loop in the Mode dialog box, because this unlimited loop blocks the original message loop, so in this infinite loop, use GetMessage, PeekMessage, DispatchMessage to read from the message queue. The message is sent and sent. Otherwise the program will not respond, this is not what we hope. So, the message loop is placed in what the program is basically going, for example, in the DLL. However, it is best at any time, only one message is working (others are blocked). Then, what we have to do, how to quit from the message loop! Of course, using wm_quit can pull ~ (postthreadMessage is also a good idea), after this message loop exits, maybe the program exits, may also activate another block-up message loop, and the program continues to run. It depends on how you think, how to do it. At the end of the last message cycle, it may be the time of the program, because the execution code of the main thread is over (unless the BT is again made a dead loop). NOTE: Let the Windows system know that the only way to create a thread is to call the API CreatThread function (__beginthreadex is to call him internally to create a new thread). It seems that Windows core programming said that under Win2000, the system uses the CreateRemoteThread function to create a thread, and CreateThread calls CreateRemoteThread internally. However, this is not the focus of argument, at least Win98 CreateremoteThread does not work properly, or CreateThread hosted a big overall situation.

3. In the mechanism of the entire message loop, you must also talk about the reusability of window functions. What do you mean? It is the code that the window function (he is a callback function) can be called by the system (caller is typically user32 module). For example, in the window, send to your own window SendMessage (***); what is the execution process? We know that SendMessage is returned after you want to wait until the message is sent and is executed by the target window. Then the window is in processing the message, and then waits for the message just sent to this window (SendMessage Returns) continues to execute down, is the program not dead to each other? In fact, it will not be. Windows design a set of algorithms suitable for SendMessage, and he judges that if the message is sent to the window created by this thread, then call the window function directly by the USER32 module (possibly a window reintegration), and returns the result results of the message. This reflects the window revenue. The above example, we call sendMessage (***) Send a message to this window, then the window process is called again. After processing the message, the result is returned, and then the program after SendMessage is connected. For non-queued messages, if there is no window to revenue, I don't know what it will look like. Note: Due to the reusability of the window. The global variables and static variables should be used as possible in the Win32 SDK program, as possible windows in the window function, change these variables after reward, but your program continues to execute after the window is returned, It may be a global or static variable that has been changed. In the MFC (all window functions are basically AFXWndProc), organize in accordance with the ideology of classes, and general variables are more, good management.

4, the MessageBox function in the MFC (such as c ** view, cframewnd, etc.), and the AFXMessagebox function are blocked by the original message loop. A message loop from the message box is read from the message queue in a message queue (similar to the mode dialog). In fact, these message functions finally call :: MessageBox, which implements a message loop inside the message box (the original main program loop is blocked). In the Forum, I have encountered a few times about the timer and message box. Look at the following code: void ctest_reclaysoutview :: ONTIMER (uint nidEvent) {// Todo: add your message handler code here and / or call default messagebox ("ABC "); While (1); // Design a dead cycle cview :: ONTIMER (nidevent);} Let ONTIMER pop up a message box for about 5 seconds. Then, the message box is constantly popped, as long as the message box is not closed, the program will not enter the dead cycle. In fact, each time the dialog box is popped up, the top of the message box is mastered, and the other message loops are blocked. As long as you don't close the uppermost message box, while (1) is not executed. If you turn it off, the program enters the dead cycle, and can only solve the problem with Ctrl Alt Del.

5. The message loop has an application in many places. For example, the application is in the thread pool. A thread's execution cycle is generally ended after the thread function returns, so how to extend the life cycle of threads? One way is to add a message loop in the thread according to the idea of ​​the message cycle, and constantly read the message from the thread queue, and handle the message, the thread is maintained until this message loop.

Note: As long as the thread has interface elements or calls GetMessage, or have a thread message to send it, the system creates a message queue for the thread.

6. In a single-threaded program, if you want to perform a long-term complex operation, you can use your own message pump. For example, a blocking wait operation can be placed in a loop and set the timeout value, and then the message pump continues the message loop in each waiting segment, so that the interface can be operated in response to user operations. Wait, etc., you can apply a message pump (call a function like this): Bool cchildview :: peekandpump () {msg msg; while (:: PeekMessage (& MSG, NULL, 0, 0, PM_NOREMOVE) {IF ( AFXGetApp () -> pumpMessage ()) {: postquitMessage (0); return false;}} Return true;} actually, use multithreading can also solve the interface problem when complex operations, but not so convenient, and generally Join thread communication and synchronization, think more about things. In summary, the MFC message cycle is that, main thinking is similar to the SDK. The main features of this idea are manifested on the entire framework of the MFC, serving the entire framework for applications and functional services. This is my understanding. Oh ~

The more hurry, if there is any error, please correct it.

Enoloo5.28-5.29,2004 Welcome to 1noloo@163.com

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

New Post(0)