See for reality (2): Windows window, message, subclass, and superclass
This article was intended to introduce the two more "uncommon" terms of class and super class. Windows and messages are discussed for the purpose of describing integrity, and processes and threads are also briefly discussed. Subclassing and superclassing are two methods to reuse code generated by the Windows window mechanism. Do not confuse subclass and superclass with the derived classes and base classes in the object-oriented language. The "class" in "subclass and superclass" refers to the Windows window class.
0 run the program
Before reading this section, read section 0th and appendix 0 at the beginning of "talk about character encoding in Windows. Section 0th describes several important modules of windows. The appendix 0 describes the Windows Startup Process, from power-on to start cmder.exe. This section describes what happens when you run a program.
0.1 program startup
When we run a program through assumer.exe, assumer.exe calls the CreateProcess function to request the system to create a process for this program. Of course, other programs can also call the CreateProcess function to create a process.
After the system allocates internal resources to the process and establishes an independent address space, a main thread is created for the process. We can regard processes as units and threads as employees. The process has resources, but the real running and Scheduling On the CPU is the thread. The system creates a main thread in the suspended state, that is, the main thread is created and does not run immediately, but waits for system scheduling. The system registers the newly created process and thread with csrss.exe, the administrator of the win32subsystem. After the registration is complete, the system notifies the suspended main thread to run and the new program starts to run.
At this time, the CreateProcess function is returned in the creation process. In the created process, the main thread enters the entry function (entry-point) of the program after the final Initialization is completed ). The creation process and the created process run independently in their respective address spaces. At this time, even if we end the creation process, the created process will not be affected.
0.2 Program Execution
The file header structure of an executable file (PE file) contains the address of the entry function. Entry functions are generally provided in the Runtime Library of windows. We can set them according to the program type during compilation. The entry-point is discussed in the tips for compiling and running programs in VC. For your reference, see.
The process before the entry function can be considered as the program loading process. During the loading, the system has initialized global and static variables (addresses can be determined during compilation), and global variables with initial values have their initial values, the variable without an initial value is set to 0. We can set a breakpoint at the entry function to confirm this.
After entering the entry function, the program continues to establish the running environment, for example, calling the constructor of all global objects. When everything is ready, the program calls the main function we provide. The main function name is determined by the entry function, such as main or winmain. If the main function required by the entry function is not provided, a link error occurs during compilation.
0.3 processes and threads
We usually call executable files on storage media (such as hard disks) as programs. After the program is loaded and run, it becomes a process. The system will create a main thread for each process. The main thread enters the provided main function through the entry function. We can create other threads in the program.
A thread can create one or more windows, or do not create windows. The system creates a message queue for a thread with a window. A thread with a message queue can receive messages. For example, we can use the postthreadmessage function to send messages to the thread.
If a thread without a window calls peekmessage or getmessage, the system also creates a message queue for it.
1. Message Queue for window and message 1.1 thread
Each running program is a process. Each process has one or more threads. Some threads have no windows, and some threads have one or more windows.
We can send messages to the thread, but most messages are sent to the window. Messages sent to the window are also placed in the thread message queue. We can regard the thread message queue as the mailbox and the window as the receiver. When we send a message to a specified window, the system will find the thread to which the window belongs, and then put the message in the message queue of the thread.
The thread message queue is the internal data structure of the system. We cannot see this structure in the program. However, we can use Windows APIs to send and deliver messages to message queues, receive messages from message queues, and convert and dispatch received messages.
1.2 minimum Windows program
Windows programmers have probably seen such a minimal windows program:
// Routine 1
#include "windows.h"
Static const char m_szname [] = "window ";
////////////////////////////////////////////////////////////////////////////////////////////////////
// If defwindowproc is used for the callback function of the main window, the message loop is not ended when the window is closed.
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_DESTROY:
Postquitmessage (0); // when the window is closed, wm_quit is sent to end the message loop.
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Main Function
int __stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASS wc;
memset(&wc, 0, sizeof(WNDCLASS));
wc.style = CS_VREDRAW|CS_HREDRAW;
wc.lpfnWndProc = (WNDPROC)WindowProc;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
wc.lpszClassName = m_szName;
Registerclass (& WC); // registration window class
HWND hWnd;
hWnd = CreateWindow(m_szName,m_szName,WS_OVERLAPPEDWINDOW,100,100,320,240,
Null, null, hinstance, null); // create a window
Showwindow (hwnd, ncmdshow); // display window
MSG sMsg;
While (int ret = getmessage (& smsg, null, 0, 0) {// message loop
if (ret != -1) {
TranslateMessage(&sMsg);
DispatchMessage(&sMsg);
}
}
return 0;
}
Although this program only displays one window, it is often used to describe the basic structure of the Windows program. Similar program structures can also be found in the MFC framework. This program contains the following basic concepts:
- Window class, window, and Window Process
- Message Loop
The following sections describe each other.
1.3 window class, window and Window Process
When creating a window, you must provide the name of the window class. A window class is equivalent to a window template. We can create multiple Windows based on the same window class. We can use Windows pre-registered window classes. But in more cases, we need to register our own window class. When registering a window class, you must register the name, style, icon, cursor, menu, and other items. The most important thing is the address of the window process.
The Window Process is a function. All messages received by the window will be sent to this function for processing. So how is the message sent to the thread Message Queue delivered to the window?
1.4 message loop
Programmers familiar with embedded multitasking programs know that the structure of tasks (equivalent to Windows threads) is basically:
while (1) {
Waiting signal;
Processing signals;
}
If the task receives a signal, it will be processed; otherwise, it will be suspended for other tasks to run. This is the basic structure of the message driver. Windows programs are usually like this:
While (int ret = getmessage (& smsg, null, 0, 0) {// message loop
if (ret != -1) {
TranslateMessage(&sMsg);
DispatchMessage(&sMsg);
}
}
Getmessage receives a message from the message queue. translatemessage generates a wm_char message based on the buttons and puts it in the message queue. dispatchmessage distributes the message to the window based on the window handle in the message, that is, the window processing function is called to process the message.
1.5 Message Communication
The function that creates a window returns a window handle. The window handle identifies a unique window instance within the system range (not the process range. By sending messages to the window, we can implement communication between processes and processes.
We can use sendmessage or postmessage to send or deliver messages to the window. Sendmessage is returned only when the message is processed in the target window. I tried: If sendmessage is sent to a window without message loops, The sendmessage function will never return. Postmessage is returned immediately after the message is put into the thread's message queue.
In fact, only the delivered messages are delivered to the window through dispatchmessage. The message sent through sendmessage is already distributed to the window when the thread getmessage is sent without dispatchmessage.
1.5.1 window program and console program communication instance
Do you think "Routine 1" is meaningless? Let's use it to make a small game: Let "Routine 1" have a close contact with a console program. First, modify the Window Process of "Routine 1":
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static DWORD tid = 0;
switch (uMsg) {
case WM_DESTROY:
Postquitmessage (0); // when the window is closed, wm_quit is sent to end the message loop.
break;
case WM_USER:
Tid = wparam; // Save the thread ID of the console Program
Setwindowtext (hwnd, "received ");
break;
case WM_CHAR:
if (tid) {
switch(wParam) {
case '1':
Postthreadmessage (TID, wm_user + 1, 0, 0); // Send Message 1 to the console Program
break;
case '2':
Postthreadmessage (TID, wm_user + 2, 0, 0); // send message 2 to the console Program
break;
}}
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
Then, create a console program with the following code:
#include "windows.h"
#include "stdio.h"
static HWND m_hWnd = 0;
void process_msg(UINT msg, WPARAM wp, LPARAM lp)
{
char buf[100];
static int i = 1;
if (!m_hWnd) {
return;
}
switch (msg) {
case WM_USER+1:
SendMessage(m_hWnd, WM_GETTEXT, sizeof(buf), (LPARAM)buf);
Printf ("your current name: % s/n", Buf); // read and display the name of the other party
break;
case WM_USER+2:
Sprintf (BUF, "I am window % d", I ++ );
Sendmessage (m_hwnd, wm_settext, sizeof (BUF), (lparam) BUF); // modify the name of the other party
Printf ("RENAME/n ");
break;
}
}
int main()
{
MSG sMsg;
printf("Start with thread id %d/n", GetCurrentThreadId());
M_hwnd = findwindow (null, "window ");
if (m_hWnd) {
Printf ("find window % x/n", m_hwnd );
SendMessage(m_hWnd, WM_USER, GetCurrentThreadId(), 0);
}
else {
Printf ("no window/n" is found ");
}
While (int ret = getmessage (& smsg, null, 0, 0) {// message loop
if (ret != -1) {
process_msg(sMsg.message, sMsg.wParam, sMsg.lParam);
}
}
return 0;
}
Can you understand how to play this game? First Run "Routine 1" WND, and then run the console program MSG. MSG finds the WND window and sends its main thread ID to WND. After WND receives the MSG message, it displays the received message. At this time, WND and MSG have established a communication channel: WND can send messages to the main thread of MSG, and MSG can send messages to the window of WND.
If we press '1' In the WND window, WND will send message 1 to MSG. After receiving the message, the WND window name will be obtained and displayed through the wm_gettext message. If we press '2' In the WND window, WND will send message 2 to MSG. After receiving the message, wm_settext will modify the window name of WND.
This example demonstrates the message loop of the console program, sending messages to the thread, and message communication between processes.
1.5.2 address space problems
Different processes have independent address spaces. If we include the address of process a in the message parameters, it is then sent to process B. If process B operates on this address in its own address space, an error occurs. So why can wm_gettext and wm_setext work normally in the above example?
This is because wm_gettext and wm_setext are messages defined by windows. Windows knows the meaning of the parameters and performs special processing, that is, allocating a piece of memory as a transit in the space of process B, and copy data between the buffer zone of process a and process B. For example, in the example in Section 1.5.1, if we set breakpoint observation, we will find that lparam in the wm_settext message sent by MSG is not equal to lparam in the wm_settext message received by WND.
If we pass the memory address in our own defined message, the system will not perform any special processing, so an error will inevitably occur.
Windows provides the wm_copydata message to transmit data to the window. Windows also performs special processing for this message.
When sending messages between processes that require additional memory allocation, we should use sendmessage instead of postmessage. Because sendmessage will wait for the receiver to return after processing, the system will have the opportunity to release the allocated memory. When postmessage is used in this case, the system will ignore the message to be delivered. You can test it in the MSG program.
2 subclass and super class
A window class is a window template, and a window is an instance of a window class. The window class and each window instance have their own internal data structure. Although Windows does not disclose the data structure, it provides APIs for reading and writing the data.
For example, the getclasslong and setclasslong functions can be used to read and write data in the window class. The getwindowlong and setwindowlong functions can be used to read and write data in the specified window instance. You can use these interfaces to read or modify the window process address of a window class or window instance at runtime. These interfaces are the basis for subclass implementation.
2.1 subclass
The purpose of subclass is to extend the function of the existing window without modifying the existing code. The idea is simple. It is to change the window process address to a new function address. The new window process function processes messages of interest and transmits other messages to the original window process. Through subclass, We can customize the window function without the source code of the existing window.
Subclass can be divided into instance subclass and global subclass. Subclass is to modify the window process address of the window instance, and global subclass is to modify the window process address of the window class. Subclass only affects the modified window. Global subclass will affect all windows created according to the window class after modification. Obviously, global subclass does not affect the window created before modification.
Although the subclass method was a concept two decades ago, the OCP: the open-closed principle (OCP: the open-closed principle) is a good practice.
2.2 superclass
The concept of superclass is simpler, that is, to read the data of the existing window class and save the function address of the window process. Make necessary modifications to the window data, set the new window process, and register a new window class with another name. The window procedure function of the new window class only processes messages of interest, and transmits other messages to the original window procedure function for processing. You can use the getclassinfo function to read data of the existing window class.
3. Message loop and subclass in MFC
It is a good example that MFC applies subclass methods to the fullest. The in-depth analysis of the main framework of MFC has been thoroughly analyzed by Mr Hou Jie. This section only looks at the message loop of MFC and briefly analyzes the application of MFC to subclass.
3.1 message loop
Create an MFC single-document program, add the wm_rbuttondown handler to the View class, and set breakpoints in the handler. Run and view the call stack after disconnected:
CHelloView::OnRButtonDown(unsigned int, CPoint)
CWnd::OnWndMsg(unsigned int, unsigned int, long, long *)
CWnd::WindowProc(unsigned int, unsigned int, long)
AfxCallWndProc(CWnd *, HWND__ *, unsigned int, unsigned int, long)
AfxWndProc(HWND__ *, unsigned int, unsigned int, long)
AfxWndProcBase(HWND__ *, unsigned int, unsigned int, long)
USER32! 7e418734()
USER32! 7e418816()
USER32! 7e4189cd()
USER32! 7e4196c7()
CWinThread::PumpMessage()
CWinThread::Run()
CWinApp::Run()
AfxWinMain(HINSTANCE__ *, HINSTANCE__ *, char *, int)
WinMain(HINSTANCE__ *, HINSTANCE__ *, char *, int)
WinMainCRTStartup()
KERNEL32! 7c816fd7()
Winmaincrtstartup is the entry function of this program. Mr Hou Jie has introduced afxwinmain in detail. Let's take a look at the message loop in cwinthread: pumpmessage:
BOOL CWinThread::PumpMessage()
{
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)) {
return FALSE;
}
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
::TranslateMessage(&m_msgCur);
::DispatchMessage(&m_msgCur);
}
return TRUE;
}
This is the message loop in the main thread of the MFC program. It distributes messages sent to the thread message queue to the thread window.
3.2 subclass
Cwnd: createex call the setwindowshookex function before creating a window to install a hook function _ afxcbtfilterhook. After the window is created, the hook function _ afxcbtfilterhook is called. _ Afxcbtfilterhook calls setwindowlong to replace the window process with afxwndprocbase, and saves the original window address returned by setwindowlong to the member variable oldwndproc. The above section calls afxwndprocbase in the stack.
It can be seen that all windows created through cwnd: createex are quilt-ized, that is, their window process will be replaced with afxwndprocbase. Why does MFC do this?
Let's take a look at the function cwnd: windowproc in the call Stack:
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = 0;
if (!OnWndMsg(message, wParam, lParam, &lResult))
lResult = DefWindowProc(message, wParam, lParam);
return lResult;
}
According to Mr. Hou Jie, cwnd: onwndmsg is the "MFC message pump" portal, through which messages flow into the message processing function in the MFC message ing. The message pump only processes the messages we have customized. the messages we have not added will flow through the "message pump" intact and enter the defwindowproc function. In the defwindowproc function, the original window address oldwndproc stored when the message is passed to the subclass.
Will the hooks in cwnd: createex subclass all windows? Actually not all. Indeed, all window-related classes of MFC are derived from cwnd. Instances of these classes call cwnd: createex when creating windows, and all classes are subverted. However, the window created through the dialog box template is created through createdlgindirect without passing through the cwnd: createex function.
But this is actually not a problem, because if we want to use MFC to customize the message ing of a control, we must first subclass the control, MFC still has the opportunity to replace the window process with its own afxwndprocbase. The next section describes the subclass of the dialog box control.
4 Examples of subclass and superclass
I wrote a very simple dialog box program to demonstrate subclass and superclass. This dialog box contains two editing boxes. I replaced the right-click menu in the editing box with a message box. The customization of the two edit boxes adopts subclass and superclass respectively:
4.1 subclass example
First, cmyedit1 is derived from cedit, and wm_rbuttondown processing is customized. In many articles, we recommend that you use subclassdlgitem in the oninitdialog dialog box to implement subclass:
m_edit1.SubclassDlgItem(IDC_EDIT1, this);
Of course this can be done. In fact, if we have added a cmyedit1 object for idc_edit1:
void CSubclassingDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CSubclassingDlg)
DDX_Control(pDX, IDC_EDIT1, m_edit1);
//}}AFX_DATA_MAP
}
Ddx_control automatically completes subclass, and there is no need to manually call subclassdlgitem. You can set a breakpoint in presubclasswindow.
The effects of the control through ddx_control or subclassdlgitem subclass are the same. MFC replaces the Window Process with afxwndprocbase. Messages added to the processing function flow into the user's processing function through the MFC message pump.
4.2 path: presubclasswindow
Presubclasswindow is a good custom control location. If we reload the cwnd: precreatewindow custom control, and the user uses the control in the dialog box. Since the control window in the dialog box is created through createdlgindirect without passing through the cwnd: createex function, the precreatewindow function will not be called.
In fact, to use custom controls in the dialog box, you must use the DDX or subclassdlgitem function subclass controls. In this case, presubclasswindow will be called.
If a user directly creates a custom control window, the cwnd: createex function will be called, and the control window will be classified to install the MFC message pump. Therefore, in MFC, presubclasswindow is the only way to create a window.
4.3 superclass
I seldom see examples of superclass (except for the Win32 compilation of Luo yunbin). In most applications, subclass technology is enough. However, I wrote an example: cmyedit2 is derived from cedit. Cmyedit2: registerme obtains the window class Edit information, saves the original window process, sets the new window process mywndproc and the new name myedit, and registers a new window class. In the new window process, mywndproc customizes the messages to be processed and sends other messages back to the original window.
In the oninitdialog dialog box, I first call cmyedit2: registerme to register the new window class, and then create a window. In this way, the window must pass through cwnd: createex, so MFC will change the Window Process to afxwndprocbase. Messages that are not intercepted by MFC message ing will flow into mywndproc.
5 conclusion
This article introduces some basic knowledge about windows and MFC. The purpose of this article is not to introduce any programming skills, but to give us a better understanding of what happens when the program runs. Only by going deep into it can we jump out of it and be free from embarrassment.