Ui thread and WINDOWS Message Queue
Note:
I am not familiar with the underlying mechanism of windows. please correct me if I have any errors in this article.
Jin xuliang
========================================================== ==========
In Windows applications, a form is created by a special type of thread called "User Interface thread.
First, the UI thread is a "Thread", so it has all the features that a thread should have, such as a thread function and a thread ID.
Second, "UI thread" is "special" because the thread function of the UI thread creates a special object-form. At the same time, it is also responsible for creating various controls on the form.
You are familiar with forms and controls. These objects have the function of receiving user operations. They are the media for users to use the entire application, users cannot control the running and stopping of the entire application, and often cannot directly view the running process and final result of the program.
Then, how do forms and controls respond to user operations? Is this response "active" by the form and control itself?
In other words:
Do forms and controls have the ability to independently respond to user operations (such as keyboard and mouse operations?
The answer is no.
That's strange. For example, we clicked a button with the mouse and saw it "trapped". Then we restored it. Then, we can see that the program executes the task corresponding to this button. Isn't it a button to respond to user operations?
This is actually an illusion. The root cause of this illusion is that you do not understand the internal operating mechanism of windows.
Simply put, the key to responding to user operations is that the UI thread responsible for creating them has a "message loop )". This message loop is started by the thread function, which usually has the following "appearance" (represented in C ++ code ):
MSG; // indicates a message
Bool Bret;
// Retrieve a message from the UI thread Message Queue
While (Bret = getmessage (& MSG, null, 0, 0 ))! = 0)
{
If (Bret =-1)
{
// Error handling code, usually exit the program directly
}
Else
{
Translatemessage (& MSG); // convert the Message format
Dispatchmessage (& MSG); // send messages to the corresponding form
}
}
We can see that the message loop is actually a while loop statement.
The getmessage () function extracts a message from the message queue each time, and the message content is filled in the variable MSG.
The translatemessage () function is mainly used to convert wm_keydown and wm_keyup messages to wm_char messages.
Tip:
When a Windows program is developed using C ++, each message has a corresponding symbolic constant. For example, wm_keydown and wm_keyup indicate the messages generated after you press the next key.
The key to message processing is the dispatchmessage () function. Based on the form handle contained in the retrieved message, this function forwards the message to the form object corresponding to the handle.
The function for the form to respond to a message is called window procedure. The form process is a function, and each form has one, it generally has the following "appearance" (C ++ code ):
Lresult callback mainwndproc (......)
{
//......
Switch (umsg) // sorts messages based on their identifiers
{
Case wm_create:
// Initialize the form.
Return 0;
Case wm_paint:
// Draw a form
Return 0;
//
// Process other messages
//
Default:
// If the form does not define the code for processing this message, it is transferred to call the default message processing function of the system.
Return defwindowproc (hwnd, umsg, wparam, lparam );
}
//......
}
As you can see, the form process is just a multi-branch statement. In this statement, the form processes different types of messages.
In Windows, the UI control is also regarded as a "window", and it also has its own "Form Process". Therefore, it can be the same as the form and have the ability to process messages.
From this we can know that the general work done by the UI thread is:
The UI thread starts a message loop. Each time a message is taken from the corresponding message queue of the thread, it forwards the message to a specific form object based on the message inclusion information, the form process function corresponding to this form object is called to process these messages.
The above description only introduces the second half of the story. You also need to know the first half of the story, that is:
How does a user "run" A message to the Message Queue of the UI thread?
We know that Windows can run multiple processes at the same time, and each process has multiple threads, some of which are UI threads, which may create more than one form, then the problem occurs:
When a user clicks a mouse at a certain position on the screen, how does the relevant information pass to a specific UI thread and is ultimately handled by the "form process" of a specific form?
The answer is that the operating system is responsible for sending messages.
The operating system monitors the keyboard, mouse, and other input devices on the computer and generates a message for each input event (triggered by a user operation, such as a user pressing a key. Based on the event situation (for example, the current activated form is responsible for receiving user buttons, and you can know in which form area the user clicks the Mouse Based on the coordinates of the user clicks the mouse ), the operating system determines the form object to which the message should be sent.
These generated messages are temporarily placed in a system message queue. Then, the operating system has a dedicated thread to retrieve messages from this queue, according to the target object of the message (that is, the form handle ), move it to the Message Queue corresponding to the UI thread that created it. When the operating system creates processes and threads, A large amount of control information is recorded at the same time (for example, the process control block and handle table can be used to find all threads created by the process and the referenced core objects). Therefore, it is very easy for the operating system to determine the UI thread that the message belongs to based on the form handle.
Note that each UI thread has a message queue instead of a message queue for each form!
Then, will the operating system create a message queue for each thread?
The answer is: only when a thread calls the GDI (Graphics Device Interface) and user functions in the Win32 API can the operating system regard it as a UI thread, and create a message queue for it.
It should be noted that the message loop is started by the thread function of the UI thread. Regardless of this, the operating system only creates a message queue for the UI thread. Therefore, if the thread function of a UI thread does not define a message loop, the forms it owns cannot be correctly drawn.
See the following code:
Class Program
{
Static void main (string [] ARGs)
{
Form1 FRM = new form1 ();
FRM. Show ();
Console. readkey ();
}
}
The above code belongs to a console application. In the main () function, a form1 form object is created, and its show () method is called for display. Then, the console is called. the readkey () method waits for the user to press the key to end the process.
The program runs as follows:
As shown in, a blank box is displayed in the form and no mouse or keyboard operations are received.
Why?
The reasons for this phenomenon can be explained as follows:
Because the console program needs to run in a "Console window", the operating system considers it as a UI thread and creates a message queue for it.
Because the main () function is the program entry point, the thread that executes it is the first thread of the process (that is, the main thread). In the main thread, a form1 form object is created, the call to its show () method only sets its visible attribute = true, which causes windows to call the corresponding Win32 API function to display the form, but this call is not a blocking call, the show () method Returns quickly and continues executing the next "console. readkey (); ", the execution of this sentence causes the main thread to call the corresponding Win32 API function to wait for the user button, blocking the execution.
Note: If you click the form and try to interact with the form, the corresponding message is indeed sent to the Message Queue of the main thread of the console application, but the main thread does not start a message loop (Do you see any loop statements in the Main () function ?) To retrieve the messages in the message queue and "Distribute" them to the form. Therefore, the form function is not called and cannot be correctly drawn.
If the form itself is displayed by calling the showdialog () method, this is a blocking call and it starts a message loop internally. This message loop can be used to extract messages from the message queue of the main thread, to make the form a "normal" form.
After the user closes the form, the subsequent code of the main () method continues to be executed until the running is complete.
If the main thread does not call "console. readkey (); "and other methods" Pause ", but directly exit. This will cause the operating system to stop the entire process and reclaim all core objects. Therefore, the created form will also be destroyed, it is impossible to see it again.
Now let's look at the complexity: What if we create and display a form in another thread?
Class Program
{
Static void main (string [] ARGs)
{
Thread th = new thread (showwindow );
Th. Start (); // create and display the form in another thread
Console. writeline ("the form has been created. Press any key to exit ...");
Console. readkey ();
Console. writeline ("the main thread exits ...");
}
Static void showwindow ()
{
Form1 FRM = new form1 ();
FRM. showdialog ();
}
}
The program running result is as follows:
As you can see, the form is displayed using showdialog (), so both the console window and the application form can normally receive users' keyboard and mouse messages. Even if the main thread exits, as long as the form is not closed, the operating system will think that the "process" is still being executed. Therefore, the console window will remain displayed until the form is closed and the entire process ends.
In this case, there are two UI threads in this example. One is the console window and the other is the thread that creates the application form.
If the display is changed to the show () method after the form is created in the thread function, because the show () method does not start the message loop, the form cannot be correctly drawn, in addition, resources will be reclaimed by the operating system as the UI thread created is terminated.
Interestingly, you can use Visual Studio to set "console Applications" without creating "console windows". You only need to change the project type to "Windows application.
When the sample program runs, Visual Studio reports an error:
The cause of this error is that the main thread of the application no longer creates a console window, the operating system no longer considers it as a UI thread, and does not create a message queue for it, the main thread will not be able to receive any key messages, so the console. the WIN32API function called at the underlying layer of readkey () cannot run normally, causing a program exception.
Conclusion:
This article is a summary of my personal exploration of the. NET technology, hoping to help you develop multi-threaded programs. In particular, if I have any incorrect understanding of the technologies involved in this article, please correct me.
======================================
Internet analyst pointed out:
The last misunderstanding error indicates that the console window does not exist or the console input is redirected to a file, which has nothing to do with message queue. In most language runtime libraries, two standard input and output interfaces are defined, namely stdin/stdout in C and CIN/cout in C ++ ,. net, but the console class is encapsulated. The input and output interfaces can be redirected to console windows, files, or pipelines. Because your program does not have a console window and the input is not directed to the console window, the. NET runtime detects this situation, so an exception is thrown to you.
Reply to analyst:
I asked ANALYST: where does the key information in CIN come from? Where does the Message Queue not start? Does a Windows operating system allow a user process to directly monitor the keyboard hardware?
In fact, each console window can have one or more "screen buffer" and one "input buffer", which are created synchronously when the console is created. After the input buffer is created, the key information can be extracted from the thread message queue.
Cin/cout is only an object-oriented encapsulation of These buffers, known as "standard input/output stream ". No message queue. What Do You buffer? Currently activated screen buffer handles are standard output and standard error handles.
Console. readkey () receives the buttons by calling the Win32 API function readconsoleinput () at the underlying layer. The declaration of this function is as follows:
Bool winapi readconsoleinput (
_ In handle hconsoleinput,
_ Out pinput_record lpbuffer,
_ In DWORD nlength,
_ Out lpdword lpnumberofeventsread
);
Note that the first parameter represents the handle of the input buffer. An exception is thrown because the input buffer in the sample program does not exist.
If you call the console. Read () method, no exception is thrown. Because this method internally calls the streamreader. Read () method, when the screen easing area does not exist, it calls streamreader. null. Read (), this method will not cause exceptions.
The related key code is as follows:
Try
{
//......
Stream stream = openstandardinput (0x100 );
If (Stream = stream. null)
{
@ Null = streamreader. null;
}
Else
{
//......
}
Thread. memorybarrier ();
_ In = @ NULL;
}
Finally
{
//...
}
Return _ in;