Exploration of C# message pump

Keywords: C# Windows

Message pump


The message pump is called message circulation.

Message recycling uses a graphical user interface under Microsoft Windows. Windows programs with GUI are event driven. Windows maintains a separate message queue for each thread that creates a window. Usually only the first thread creates the window. Windows drop messages enter the queue whenever mouse activity occurs on the thread's window, whenever the window has focus, when keyboard activity occurs, and other times. Processes can also add messages to their queues. In order to accept user input and for other reasons, each thread with a window must constantly retrieve messages from its queue and take action on them. By writing a circular call GetMessage (blocking the message and retrieving it), calling DispatchMessage (scheduling message) and repeating it indefinitely, the process can do this. This is the message loop. There is usually a message loop in the main program, which runs on the main thread, and there is an additional message loop in each modal dialog box created. Each window that leaves a message to the process passes through its message queue and is processed by its message cycle.

Therefore, to sum up, a message loop is an event loop.

Implementation of message pump

private bool LocalModalMessageLoop(Form form)
            {
                try
                {
                    NativeMethods.MSG msg = default(NativeMethods.MSG);
                    bool flag = false;
                    bool flag2 = true;
                    while (flag2)
                    {
                        if (UnsafeNativeMethods.PeekMessage(ref msg, NativeMethods.NullHandleRef, 0, 0, 0))
                        {
                            if (msg.hwnd != IntPtr.Zero && SafeNativeMethods.IsWindowUnicode(new HandleRef(null, msg.hwnd)))
                            {
                                flag = true;
                                if (!UnsafeNativeMethods.GetMessageW(ref msg, NativeMethods.NullHandleRef, 0, 0))
                                {
                                    continue;
                                }
                            }
                            else
                            {
                                flag = false;
                                if (!UnsafeNativeMethods.GetMessageA(ref msg, NativeMethods.NullHandleRef, 0, 0))
                                {
                                    continue;
                                }
                            }

                            if (!PreTranslateMessage(ref msg))
                            {
                                UnsafeNativeMethods.TranslateMessage(ref msg);
                                if (flag)
                                {
                                    UnsafeNativeMethods.DispatchMessageW(ref msg);
                                }
                                else
                                {
                                    UnsafeNativeMethods.DispatchMessageA(ref msg);
                                }
                            }

                            if (form != null)
                            {
                                flag2 = !form.CheckCloseDialog(closingOnly: false);
                            }
                        }
                        else
                        {
                            if (form == null)
                            {
                                break;
                            }

                            if (!UnsafeNativeMethods.PeekMessage(ref msg, NativeMethods.NullHandleRef, 0, 0, 0))
                            {
                                UnsafeNativeMethods.WaitMessage();
                            }
                        }
                    }

                    return flag2;
                }
                catch
                {
                    return false;
                }
            }

The above is the core source code of the message pump implementation in WinForm. We see that after entering the method, the unsafenaturemethods.PeekMessage method will be executed to read the message. Next, let's take a look at Microsoft's definition of PeekMessage.

  We can see that this method will get any type of message triggered by the current thread (in fact, the main thread) From. When no message is obtained or the character set of the form is Unicode, it will get the message of the message queue of the current thread through the GetMessageW function. Otherwise, it will call GetMessageA. Then, the message preprocessing process is carried out. If you have time to analyze it in detail in the future, first post the preprocessing core code below.

[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
        internal static PreProcessControlState PreProcessControlMessageInternal(Control target, ref Message msg) {
            if (target == null) {
                target = Control.FromChildHandleInternal(msg.HWnd);
            }

            if (target == null) {
                return PreProcessControlState.MessageNotNeeded;
            }

            // reset state that is used to make sure IsInputChar, IsInputKey and 
            // ProcessUICues are not called multiple times.
            // ISSUE: Which control should these state bits be set on? probably the target.
            target.SetState2(STATE2_INPUTKEY,  false);
            target.SetState2(STATE2_INPUTCHAR, false);
            target.SetState2(STATE2_UICUES,    true);

            Debug.WriteLineIf(ControlKeyboardRouting.TraceVerbose, "Control.PreProcessControlMessageInternal " + msg.ToString());
            
            try {
                Keys keyData = (Keys)(unchecked((int)(long)msg.WParam) | (int)ModifierKeys);

                // Allow control to preview key down message.
                if (msg.Msg == NativeMethods.WM_KEYDOWN || msg.Msg == NativeMethods.WM_SYSKEYDOWN) {
                    target.ProcessUICues(ref msg);                    

                    PreviewKeyDownEventArgs args = new PreviewKeyDownEventArgs(keyData);
                    target.OnPreviewKeyDown(args);
                    
                    if (args.IsInputKey) {
                        Debug.WriteLineIf(ControlKeyboardRouting.TraceVerbose, "PreviewKeyDown indicated this is an input key.");
                        // Control wants this message - indicate it should be dispatched.
                        return PreProcessControlState.MessageNeeded;
                    }                    
                }

                PreProcessControlState state = PreProcessControlState.MessageNotNeeded;
                
                if (!target.PreProcessMessage(ref msg)) {
                    if (msg.Msg == NativeMethods.WM_KEYDOWN || msg.Msg == NativeMethods.WM_SYSKEYDOWN) {

                        // check if IsInputKey has already procssed this message
                        // or if it is safe to call - we only want it to be called once.
                        if (target.GetState2(STATE2_INPUTKEY) || target.IsInputKey(keyData)) {
                            Debug.WriteLineIf(ControlKeyboardRouting.TraceVerbose, "Control didn't preprocess this message but it needs to be dispatched");
                            state = PreProcessControlState.MessageNeeded;
                        }
                    }
                    else if (msg.Msg == NativeMethods.WM_CHAR || msg.Msg == NativeMethods.WM_SYSCHAR) {

                        // check if IsInputChar has already procssed this message
                        // or if it is safe to call - we only want it to be called once.
                        if (target.GetState2(STATE2_INPUTCHAR) || target.IsInputChar((char)msg.WParam)) {
                            Debug.WriteLineIf(ControlKeyboardRouting.TraceVerbose, "Control didn't preprocess this message but it needs to be dispatched");
                            state = PreProcessControlState.MessageNeeded;
                        }
                    }                  
                }
                else {
                    state = PreProcessControlState.MessageProcessed;
                }

                return state;
            }
            finally {
                target.SetState2(STATE2_UICUES, false);
            }
        }

Posted by kooper on Mon, 18 Oct 2021 18:33:37 -0700