First, we should understand the message loop (: getmessage,: peekmessage) and message pump (cwinthread: pumpmessage) of MFC) messages in windows are different from messages in MFC. Application in MFC Program (The application class is inherited based on cwinthread), there must be a message loop, its role is to read the message from the application message queue, and send it out (:: dispatchmessage ). Message routing refers to the window to which the system (user32.dll) delivers the message after the message is sent, and the transfer of the message between windows in the future.
Messages are divided into queue messages (message queues that enter the thread) and non-queue messages (message queues that do not enter the thread ). For queue messages, the most common messages are those triggered by the mouse and keyboard, such as wm_mousermove and wm_char, and wm_paint, wm_timer, and wm_quit. When a mouse or keyboard event is triggered, the corresponding mouse or keyboard driver converts the event to a message and then delivers it to the system message queue, the Windows system is responsible for adding messages to the Message Queue of the corresponding thread, so there is a message loop (reading and dispatching messages from the Message Queue ). There is also a non-queue message, which bypasses the system queue and message queue and directly sends the message to the window process. For example, when a user activates a window system, wm_activate, wm_setfocus, and wm_setcursor are sent. Wm_create message is sent when the window is created. Later, you will see that the design of Ms makes sense and its complete implementation mechanism.
The message loop and message Pump of MFC are described here. First, let's take a look at how the program enters the message loop at startup:
|
_ Twinmain-> afxwinmain-> afxwininit-> cwinthread: initapplication-> cwinthread: initinstance-> cwinthread: Run |
The message loop of non-dialog programs starts from the run of the cwinthread...
Part 1: Message Loop Mechanism of Non-dialog programs.
|
// Thrdcore. cpp // Main running routine until thread exits Int cwinthread: Run () { Assert_valid (this ); // For tracking the idle time state Bool bidle = true; Long lidlecount = 0; // Acquire and dispatch messages until a wm_quit message is wrongly ed. For (;;) { // Phase1: Check to see if we can do Idle Work While (bidle && ! : Peekmessage (& m_msgcur, null, pm_noremove )) { // Call onidle while in bidle state If (! 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; Lelecount = 0; } } While (: peekmessage (& m_msgcur, null, pm_noremove )); } // Infinite loop. The exit condition is to receive the wm_quit message. Assert (false); // not reachable } |
This is an infinite loop. Its exit condition is to receive the wm_quit message:
|
If (! Pumpmessage ()) Return exitinstance (); |
In pumpmessage, if the message wm_quit is received, false is returned. Therefore, the exitinstance () function is executed and the program exits after the loop exists.Code. Therefore, to exit a program, you only need to call the function in the code.
Void postquitmessage (INT nexitcode ). Exit nexitcode to exit the program.
The following describes the run process of this function in two steps:
1. The first internal loop phase1. Bidle indicates whether the program is idle. It means that if the program is idle and there is no message to be processed in the message queue, the virtual function onidle is called for idle processing. In this process, the UI interface (such as the Enable and disable statuses of toolbar buttons) will be updated to delete temporary objects (such as object pointers obtained using fromhandle. For this reason, it is insecure to pass the Object Pointer obtained by fromhandle between functions because it has no persistence ). Onidle can be reloaded. You can reload it and return true to keep the message loop idle.
Note: Ms uses temporary objects for efficiency, so that the memory can be effectively used and resources can be automatically revoked when idle. There are several ways to convert a handle into an object. Generally, you declare an object OBJ first, and then bind it to a handle using obj. attatch. The generated object is permanent. You must use obj. Detach to release the object.
2. The second inner loop phase2. In this cycle, start the message pump (pumpmessage). If it is not a wm_quit message, the message pump sends the message (: dispatchmessage ). The Message destination is the window corresponding to the hwnd field in the message structure.
|
// Thrdcore. cpp Bool cwinthread: pumpmessage () { Assert_valid (this ); // If it is wm_quit, exit the function (return false), which causes the program to end. If (! : Getmessage (& m_msgcur, 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 Return 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 )) |
|
{ : Translatemessage (& m_msgcur); // key Conversion : Dispatchmessage (& m_msgcur); // send a message } Return true; } |
?
In this step, we have a particularly important function: pretranslatemessage. This function pre-processes the message before: dispatchmessage sends the message to the window. The pretranslatemessage function is a member function of cwinthread. When you reload the function, it is in the View class or main window class. How does it access other classes? The Code is as follows:
|
// Thrdcore. cpp Bool cwinthread: pretranslatemessage (MSG * PMSG) { Assert_valid (this ); // If it is a thread message, the processing function of the thread message will be called. If (PMSG-> hwnd = NULL & dispatchthreadmessageex (PMSG )) Return true; // Walk from target to Main Window Cwnd * pmainwnd = afxgetmainwnd (); If (cwnd: FIG (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 } |
The above function shows that:
First, if (PMSG-> hwnd = NULL), it indicates that this is a thread message. Call cwinthread: dispatchthreadmessageex to find the message entry in the message ing table, and then call the message processing function.
Note: Generally, the postthreadmessage function is used to send messages between threads. Different from window messages, the thread ID must be specified, and the message excitation is put into the message queue of the target thread by the system; the on_thread_message (message, memberfxn) macro can be used to map the thread message and its processing function. This macro must be in the application class (inherited from cwinthread), because only the application class can process thread messages. If you use this macro in other classes (such as view classes), the message processing function of the thread message will not receive the thread message.
Second, the pretranslatemessage function of the target window of the message first obtains the message processing permission. If the function returns false, its parent window will get the message processing permission until the main window; if the function returns true (indicating that the message has been processed), you do not need to call the pretranslatemessage function of the parent class. In this way, this ensures that both the target window of the message and its parent window have the opportunity to call pretranslatemessage-preprocessing before the message is sent to the window (if the message is processed and then false is returned,-_-B ), if you want to not pass the message to the parent class for processing, return true.
Third, if the target window of the message has no parent-child relationship with the main window, call the pretranslatemessage function of the main window. Why? Step 2 shows that if the parent window of a window is not the main window, although its pretranslatemessage returns false, the main window has no chance to call the pretranslatemessage function. We know that the conversion of the acceleration key is generally in the pretranslatemessage function of the Framework Window.
I have searched for the acceleration key Conversion Processing in MFC. Only window classes such as cframewnd, cmdiframewnd, and cmdichildwnd are available. Therefore, the third step means that if the target window of the message (its parent window is not the main window, such as a non-mode dialog box) if the pre-processing of a message continues roaming (his pretranslatemessage returns false), give a chance to call the pretranslatemessage for the main window (in case he is an acceleration key message ?), In this way, the acceleration key of the main window can be ensured in non-mode dialog box.
I made a small example. In the pretranslatemessage of the dialog box class, false is returned. The non-mode dialog box is displayed in the main window. When the dialog box has focus, the shortcut keys of the main window can still be activated.
In short, the entire framework gives each message's target window (including its parent window) the opportunity to participate in the processing before the message arrives. Haha ~
So far, the mechanism of message loop and message pump in non-dialog box is almost the same. This mechanism continuously acquires messages from the message queue in an infinite loop, and ensures that the thread messages of the program can be processed, the window message is sent to the corresponding window processing process after preprocessing. There is another question: Why do they need to call: peekmessage and: getmessage? What are their differences?
Note: In general, getmessage is designed to efficiently retrieve messages from message queues. If there is no message in the queue, the function getmessage will cause the thread to sleep (giving way to the CPU time ). Peekmessage is used to determine that if there is no message in the message queue, it immediately returns 0 without causing the thread to be in sleep state.
Peekmessage is used in the first loop of phase1. Its Parameter pm_noremove indicates that the message is not removed from the message queue, but a detection query. If no message exists in the message queue, the system immediately returns 0, if the thread is idle, it will cause the message to call the onidle processing process cyclically (the importance of this function is described above ). If you change: peekmessage to: getmessage (***), the thread will sleep if there is no message in the message queue, it is not possible for the thread to continue execution until the CPU time is obtained next time and a message appears. In this way, the idle time of the message loop is not applied, and the onidle will not be executed. This is why we must use: peekmessage (query) and: getmessage (actual work.
Part 2: Message Loop Mechanism of the dialog box Program
The dialog box-based MFC project is different from the message loop mechanism above. In fact, the MFC dialog box project is the mode dialog box. He is different from the non-dialog program mentioned above, mainly because the initinstance () of the application object is different.
|
// dlg_5dlg.cpp bool cdlg_5app: initinstance () {< br> 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; // define a dialog box object m_pmainwnd = & DLG; int nresponse = DLG. domodal (); // The message loop in the dialog box starts here If (nresponse = idok) {< br> // todo: place code here to handle when the dialog is // dismissed with OK }< br> else if (nresponse = idcancel) {< br> // todo: place code here to handle when the dialog is // dismissed 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. From the process above, we can see that cwinthread: Run is not executed. That is to say, the message loop mentioned in the first part of the preceding Section cannot be executed in the dialog box. In fact, there is also a message loop in the dialog box. Her message loops are in a runmodalloop function in the cdialog: domodal () virtual function.
The implementation body of this function is in the cwnd class:
|
Int cwnd: runmodalloop (DWORD dwflags) { Assert (: 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 messages until the modal state is done For (;;) { Assert (continuemodal ()); // Phase1: Check to see if we can do Idle Work While (bidle && ! : Peekmessage (PMSG, null, pm_noremove )) { Assert (continuemodal ()); // Show the dialog when the message queue goes idle If (bshowidle) { Showwindow (sw_shownormal ); Updatewindow (); Bshowidle = 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 ); } If (dwflags & mlf_nokickidle) | ! Sendmessage (wm_kickidle, msgf_dialogbox, lidlecount ++ )) { // Stop idle processing next time Bidle = false; } } // Phase2: pump messages while available Do { Assert (continuemodal ()); // Pump message, but quit on wm_quit // The implementation of pumpmessage is similar to that mentioned above. All messages 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 (PMSG )) { Bidle = true; Lelecount = 0; } } While (: peekmessage (PMSG, null, pm_noremove )); } // Infinite Loop Exitmodal: M_nflags & = ~ (Wf_modalloop | wf_continuemodal ); Return m_nmodalresult; } |
Let's talk about how to exit this infinite loop. In the Code:
|
If (! Continuemodal ()) Goto exitmodal; Determine whether or not to exit the loop. The message loop function returns, that is, the program is about to end. Bool cwnd: continuemodal () { Return m_nflags & wf_continuemodal; } |
Note: cwnd: continuemodal () function check dialog box: whether to continue the mode. Returns true, indicating that the current mode is used; returns false, indicating that the dialog box is no longer in the mode (to be ended ).
If you want to end the dialog box, the function cwnd: endmodalloop will be called internally to cancel the m_nflags pattern. (The continuemodal function in the message loop will return false, and the message loop will end, the program exits), and then the message is repeatedly read. That is to say, the end mode dialog box is a flag. You can change this flag. His code is:
|
// Wincore. cpp Void 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 no message exists in the message queue, the continuemodal () in the message loop may not be executed immediately. Sending an empty message will trigger the message loop to work immediately.
The following describes what the message loop in the cwnd: runmodalloop function does:
1. The first inner loop. First, query the message from the message queue. If the dialog box is idle and there is no message in the message queue, you should be able to understand the meaning of three things literally. The most important thing is to send the wm_kickidle message. Why? As mentioned in the first part, non-dialog programs use onidle to update user interfaces (UIS), such as the toolbar and status bar. So where can I update the toolbar and status bar in the dialog box (there are many such programs on the Internet )? Wm_kickidle messages can be processed:
|
Lresult cdlg_5dlg: onkickidle (wparam W, lparam L) { // Call cwnd: updatedialogcontrols to update the user interface Updatedialogcontrols (this, true ); Return 0; } |
Note: cwnd: updatedialog function sends a cn_update_command_ui message to all user interface dialog box controls.
2. The second inner loop. Most importantly, pumpmessage sends messages to the target window. For others, like the second if statement, the 0x118 message seems to be a wm_commandid message (a message used by the system to notify the cursor ). That is to say, if the message is wm_zookeeper or wm_syskeydown and the idle display flag is true, the window is displayed and the notification window is re-painted immediately.
In short, the message loop mechanism of the dialog box is similar to that of the non-dialog box (such as SDI and MDI), but the focus is different. The Mode dialog box is a mode display, which naturally has its own characteristics. The following sections describe the differences between the mode dialog box and the non-mode dialog box. Because the mode dialog box has its own special message loop, instead of the mode dialog box, there is no big difference between the message loop of the Sharing Program and the common window.
Part 3: differences between a mode dialog box and a non-mode dialog box
This topic has been discussed by many people.
In the MFC framework, a dialog box object domodal will generate a mode dialog box, and a non-mode dialog box will be generated after the create operation. In fact, both the mode dialog box and the non-mode dialog box call the: createdialogindirect (***) function in MFC to create the non-mode dialog box. Only the mode dialog box does more work, including making the parent window invalid and then entering its own message loop. : The createdialogindirect (***) function finally calls the createmediawex function to notify the system to create a form and return a handle. It does not implement its own message loop.
After the non-mode dialog box is created, return immediately and share a message loop with the main program. The non-mode dialog box will not be returned until the dialog box is complete. It has a message loop. For example, the following code:
|
Cmydlg * pdlg = new cmydlg; Pdlg-> Create (idd_dialog1 ); Pdlg-> showwindow (sw_show ); MessageBox ("ABC "); |
Non-mode dialog box and message box MessageBox are displayed at the same time. If you change "CREATE" to "domodal", you can only pop up the mode dialog box. After the dialog box is closed (the mode dialog box ends with its own message loop), the message box pops up.
Note: You can call getparent ()-> enablewindow (true) in the mode dialog box. In this way, the toolbar of the main window is activated and can be used. MFC uses a non-mode dialog box to simulate a mode dialog box. In the Win32 SDK program, the mode dialog box does not show any effect on the enable operation of its parent window.
Summary of message loops:
1. What height do we stand on to view the message loop? There is no esoteric truth about message loops. What should we ask a postman to send messages in a city? He is required to run back and forth, but he can only appear in one place at a time. If our application only has one thread, we want it to continuously send messages to the window. What should we do? Continuously detect messages in a loop and send them to the appropriate window. There can be many windows, but there is only one message loop, and at most one place is executing code at a time. Why? See the second point.
2. Because it is a single thread (only one thread exists when the program process starts, we call it the main thread), it is like a postman, you can only work in one place at a time. What does it mean? For example, the message is sent using: diapatchmessage. It is blocked and will not be returned immediately before the window processing process (winproc, window function) returns, that is, the message loop can no longer read messages from the message queue until: dispatchmessage is returned. If you execute an infinite loop operation in the window function, the program will be down even if you exit with the postquitmessage function.
|
While (1) { Postquitmessage (0); // The program is still down. } |
Therefore, when the window function does not return a message, the message loop will not read the message from the message queue. This is why we need to use an infinite loop to continue the message loop in the mode dialog box, because this infinite loop blocks the original message loop, so we need to use getmessage, peekmessage, dispatchmessage is used to read and deliver messages from the message queue. Otherwise, the program will not respond, which is not what we want.
Therefore, the place where the message loop is put in the program is basically gone, for example, in the DLL. However, it is best to have only one message loop working at any time (other messages are blocked ). Then, one thing we need to do is how to exit from the message loop! Of course, wm_quit can be used to pull ~ (Postthreadmessage is also a good idea). After the message is cyclically exited, the program may exit, or another blocked message loop may be activated, and the program continues to run. It depends on what you think and how you do it. At the end of the last message loop, it may be the end of the program, because the Execution Code of the main thread is almost finished (unless BT makes an endless loop ).
Note: The only way to let the Windows system know how to create a thread is to call the API creatthread function (_ beginthreadex and others must call it internally to create a new thread ). As for Windows core programming, in Win2000, the system uses the createremotethread function to create a thread. createthread internally calls createremotethread. However, this is not the focus of the debate. At least createremotethread under Win98 cannot work normally, or createthread hosts the overall situation.
3. The reentrant of window functions must also be discussed in the message loop mechanism. What does it mean? That is, when the code of the window function (which is a callback function) can be called by the system (the caller is generally the USER32 module. For example, in the window process, sendmessage (***) is sent to the window. What is the execution process?
We know that sendmessage is returned only after the message is sent and executed by the target window. Then the window is processing the message, and then wait until the message sent to this window is processed (sendmessage is returned) before it continues to run. Isn't the program deadlocked?
Actually, it won't. Windows designs a set of suitable sendmessageAlgorithmHe judges that if the message sent belongs to the window created by this thread, then the USER32 module calls the window function directly (there may be window re-entry ), and return the message processing result. This demonstrates window re-entry. In the preceding example, if sendmessage (***) is called to send a message to this window, the window process is called again. After the message is processed, the result is returned, then the program after sendmessage is executed. For non-queue messages, if there is no window re-import, I don't know what it will look like.
Note: The window is reentrant. Global variables and static variables should be used as little as possible in the Win32 SDK program, because during Window Function execution, Windows may be re-imported. If the variables are changed after re-entry, however, your program continues to execute after the window is re-imported and returned, possibly using changed global or static variables. In MFC (all window functions are afxwndproc), they are organized according to the idea of the class. Generally, the variables are in the class and are well managed.
4. The MessageBox function and afxmessagebox function in the window class (such as C ** view and cframewnd) in MFC block the original message loop. A message loop in the message box reads messages from the message queue and sends messages (similar to the mode dialog box ). In fact, these message functions ultimately call: MessageBox, which implements a message loop inside the message box (the original main program message loop is blocked ). The forum encountered several timer and message box problems. Let's look at the following code:
|
Void ctest_recalclayoutview: ontimer (uint nidevent) { // Todo: add your message handler code here and/or call default MessageBox ("ABC "); While (1); // design an endless loop Cview: ontimer (nidevent ); } |
Let ontimer pop up a message box in about five seconds. Then, the message box is constantly popped up. As long as the message box is not closed, the program will not enter an endless loop. In fact, each pop-up dialog box contains the message loop in the top message box, and other message loops are blocked. As long as the top message box is not closed, while (1); will not be executed. If you click Close, the program enters an endless loop. You can only use CTRL + ALT + DEL to solve the problem.
5. Message loops are applied in many places. For example, the application is in the thread pool. The execution cycle of a thread generally ends after the return of the thread function. How can we prolong the life cycle of a thread? One way is to add a message loop to the thread according to the message loop idea, constantly read messages from the thread queue and process messages, the thread's lifecycle is maintained until the message loop exits.
Note: As long as the thread has interface elements, calls getmessage, or sends a thread message, the system creates a message queue for the thread.
6. In a single-threaded program, if you want to execute a complex operation for a long time and have a corresponding interface, you can use your own message pump. For example, you can put a blocking wait operation in a loop and set the timeout value to a small value. Then, use the message pump to continue the message loop for each waiting segment, enables the interface to respond to user operations. And so on, you can apply the message pump (call a function similar to this ):
|
Bool cchildview: peekandpump () { MSG; While (: peekmessage (& MSG, null, 0, 0, pm_noremove )) { If (! Afxgetapp ()-> pumpmessage ()) { : Postquitmessage (0 ); Return false; } } Return true; } |
In fact, multithreading can also solve the interface problem during complex operations, but it is not so convenient. In addition, we usually need to add the line communication and synchronization, so we can consider more things.
To sum up, the message loop of MFC is similar to that in SDK. The main feature of this idea is to cater to the entire framework of MFC and serve the entire framework, as well as applications and functions. This is my understanding. Haha ~