Here, I want to talk about some experience of learning MFC. I started to write a program under WINDOW in 1997. Before this, I have written some DOS programs, including a simple full-screen editor and a function-based expression interpreter, are some small programs. After Window 3.1 is popular, I started writing a program below it.
From writing a DOS program to writing a Window program, you need to make a relatively large adjustment from programming ideas. Write a program under DOS, the overall process of the program is completely controlled by the application; but under Window, the overall process of the program is controlled by the operating system, this is the DOS programmer "Huzu is" under DOS, especially I am not used to, I will be difficult to turn over, I always feel that the application process controlled by the operating system can meet any of our request? What should I do with the procedure you need to do with it? But later, as learned, I think this fear is complete, I have never encountered the problem.
Another transition is that under Window, the program is driven by an event (or message). The programmer is mainly the code that provides the event handler in the program, and then calls these code from the operating system, from the perspective of the programmer, Is the operating system in "callback" he or the code she written. This is also very uncomfortable, because under DOS, it is the code (API) of the application call operating system. Now, the role changes, can't stand it! However, this is slowly desalinated with the increase in programming.
I just started, I used SDK to program for half a year, I can't stand it, it is too much trouble, write a simple display "Hello, World!" To get the hundred lines of code, plus the annoying make Document and .def file (at that time I used Borland C 3.1, and I didn't know how to have an OWL). Later, listening to people said that now writes C or C programs under WINDOW is MFC, the MFC is very powerful! So I went to the library to borrow two books of VC, and I took a worship photo on the book.
To be honest, the worship is the most confused week, MFC is frightened. Yes, use MFC to write a "Hello, World!" Program only need to write a line of code itself, but I don't know when the code I have written, I don't know what MFC did it behind. These are not the most important, and let me feel unacceptable. I feel that all my programming actions are under the control of MFC, and control more "dead", my thoughts are drilled into the above mentioned "Dead Hutong" went. Later, I thought, if I saw some articles or books about the component, I thought, this "Dead Hu" is not there, it should not exist.
In fact, all of this is due to the unfamiliar number of MFCs, the MFC is a framework class library, and the framework class library is different from the general class libraries in the library. Contact, they are collaborative to complete tasks in accordance with the mode defined by the framework. Therefore, to learn MFC, first understand how each class collaborates and their interfaces.
Also, I think that if you are familiar with SDK, it is very helpful to understand the MFC and use the MFC writing program. Therefore, in the following explanations, I will insert some SDK knowledge as needed to assist in solutions. Finally, you must have a certain C knowledge. I don't know if C is used to use the MFC. I am really difficult to imagine the final result, it is best to grasp the basic knowledge of the C I told before.
MFC application control flow
General WINDOW Application Basic Process
<1> WinMain () function
Any application has an inlet function, under WINDOW, the program's portal function is based on the application
Type, there are two options: the entry function of the console program is Main (), the inlet function of the general Window interface program is WinMain (). Here only explores the WinMain () function related to the discussion below. Below is the prototype of this function: (VisualL C )
Int apientry Winmain
Hinstance hinstance,
Hinstance Hprevinstance,
LPSTR LPCMDLINE,
INT ncmdshow)
among them:
Hinstance is an instance that identifies the current process. It is actually the first address of the address space occupied by the process. In many Window APIs, it is passed as a parameter, so applications generally save it in a global In the amount. HPREINSTANCE is an instance handle of an instance of the application. This is the 16-bit Window residue, in the Win32 application, this parameter is always NULL. So some applications that are ported to 32 bits from 16, if HPREINSTANCE is used, the code should be modified accordingly. LPCMDLINE is a command line argument, which is similar to the Argv [] in main (). NCMDSHOW is used to indicate the display mode of the main window of the application (maximize display, minimize display, generally display).
<2> an instance
Below is a code for displaying "Hello, World", which reflects the basic process of a general Window application.
Int apientry Winmain (Hinstance Hinstance,
Hinstance Hprevinstance,
LPSTR LPCMDLINE,
INT ncmdshow)
{
MSG msg;
// Register window class
WNDCLASSEX WCEX;
Wcex.cbsize = sizeof (wndclassex);
WCEX.Style = CS_HREDRAW | CS_VREDRAW;
WCEX.LPFNWNDPROC = (WndProc) WndProc;
Wcex.cbclsextra = 0;
Wcex.cbWndextra = 0;
WCEX.HINSTANCE = HINSTANCE
WCEX.HICON = Loadicon (Hinstance, (LPCTSTR) IDI_HELLOWORLD);
Wcex.hcursor = loadingcursor (null, idc_arrow);
Wcex.hbrbackground = (Hbrush) (Color_Window 1);
WCEX.LPSZMENUNAME = (LPCSTR) IDC_HELLOWORLD;
WCEX.LPSZCLASSNAME = SzWindowClass;
Wcex.hiconsm = loadicon (Wcex.hinstance, (LPCTSTR) IDI_SMALL); RegisterClassex (& WCEX);
// Create a window of this type
Hwnd hwnd;
HWND = CREATEWINDOW (SzWindowClass, Sztitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, HINSTANCE, NULL
IF (! hwnd) Return False;
// NCMDSHOW mode display window
ShowWindow (HWND, NCMDSHOW);
UpdateWindow (HWND);
/ / Start the message loop, send the message to the corresponding window function
While (GetMessage (& MSG, NULL, 0, 0))
{
TranslateMessage (& MSG);
DispatchMessage (& MSG);
}
Return msg.wparam;
}
// Window function
Lresult Callback WndProc (HWND HWND, UINT MESSAGE, WPARAM WPARAM, LPARAM LPARAM)
{
Paintstruct PS;
HDC HDC;
Char * szhello = "Hello, World!";
Switch (Message)
{
Case WM_Paint:
HDC = BeginPaint (HWND, & PS);
RECT RT;
GetClientRect (hwnd, & rt);
DrawText (HDC, Szhello, Strlen (Szhello), & RT, DT_CENTER
Endpaint (hwnd, & ps);
Break;
Case WM_DESTROY:
PostquitMessage (0);
Break;
DEFAULT:
Return DefWindowProc (Hwnd, Message, WPARAM, LPARAM);
}
Return 0;
}
The execution process of the above program is as follows:
Registering a window class This is prepared for later creation window, and you must provide a string of identity window classes when using CreateWindWo () and CreateWindowEx (). The main intent of the creation window class is to provide a window handling function to the operating system. Create a window launch message loop, distribute and process messages.
The key part of it is the message loop:
While (GetMessage (& MSG, NULL, 0, 0))
{
TranslateMessage (& MSG);
DispatchMessage (& MSG);
}
Call getMessage () remove a message from the message queue of the thread, translate the message, then call
DispatchMessage () distributes the message to the corresponding window process. (In fact, DispatchMessage () is a window function that calls the message as a parameter called the corresponding window, which is the substance of the distribution), which we will discuss in detail the difference between the MFC's message ring. Difference.
MFC's WinMain () Using MFC programmers just started such a question: Where is my program started? The answer is: starting from WinMain (). This problem is that the WinMain () function is not seen in the MFC application they have written. This function is hidden in the MFC framework, and the MFC designer works very well (this is mainly due to the programming mechanism for Window's message-driven programming mechanism so that it is easy to make a universal WinMain (), so in general No need to change the code of Winmain (), the MFC designer does not advocate the programmer to modify the code of WinMain (). In the MFC, the code that actually implements WinMain () is an AFXWINMAIN () function (you know this is a global MFC function based on its prefix AFX). A Win32 application (or process) is composed of one or more concurrent threads, where the first start-up thread is called the main thread, under Window, generally divide the thread into two major classes, interface threads, and working threads. The working thread is a general thread. It does not have a window, no message queue, etc., the interface thread has one or more windows, with a message queue and other elements that are exclusive to interface threads. Before discussing AFXWinMain (), first mention two important classes in the MFC, CWINTHREAD and CWINAPP, Cwinthread is class used to encapsulate interface threads, and CWINAPP is derived from CwinThread. In CwinThread, there are two important virtual functions initInstance () and ExitiniStance (), and the MFC programmers should be familiar with these two functions. In CWINAPP, another virtual function initApplication () is added to discuss the main purpose of AFXWinMain () is how these functions are called.
AfxWinMain () code is as follows: int AFXAPI AfxWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {ASSERT (hPrevInstance == NULL); // at win32, hPrevInstance always is NULL int nReturnCode = -1; CWinThread * pThread = AfxGetThread (); CWinApp * pApp = AfxGetApp (); // AFX internal initialization if (! AfxWinInit (hInstance, hPrevInstance, lpCmdLine, nCmdShow)) goto InitFailure; // App global initializations (rare) if (pApp = NULL! ! && pApp-> InitApplication ()) goto InitFailure; // Perform specific initializations if (pThread-> InitInstance (!)) {if (pThread-> m_pMainWnd = NULL) {TRACE0 ( "Warning:! Destroying non-NULL m_pMainWnd / n "); pThread-> m_pMainWnd-> DestroyWindow ();} nReturnCode = pThread-> ExitInstance (); goto InitFailure;} nReturnCode = pThread-> Run (); InitFailure: AfxWinTerm (); return nReturnCode;} in the above In the code, AFXGETTHREAD () returns the pointer of the current interface thread object, and AFXGetApp () returns the pointer of the application object. If the application (or process) is only one interface thread, then the two returned Is a global application object pointer, this global application object is the default theApp object by the MFC application framework (when generating an SDI or MDI application each time you use AppWizard, Both AppWizard add this statement, and AFXGetApp () is the address of this THEAPP). Cwinapp :: initApplication (), cwinthread :: InitInstance (), cwinthread :: exitInstance () is called, I know from the code above, I don't have to repeat it. Below we put the focus on cwinthread :: run (). MFC's Control Center - Cwinthread :: Run () CWINTHREAD :: Run () is the control center of the MFC, and there is no exaggeration at all. In MFC, all from
The message of the message queue is all completed in the cwinthread :: run () function, like AfxwinMain (), this function is also invisible to the programmer, the truth is the same as AfxwinMain ().
The first thing to mention is that the message taken from the message team, the MFC is distributed according to a particular mode according to the type of message, and this distribution mode is the MFC's own definition. The fixed message distribution process and the virtual function of the active change in this process constitute the MFC message distribution mode. The application can locally customize these virtual functions to locally customize their messages distribution mode. It is through these virtual functions, and the MFC provides adequate flexibility for the application. All the code discussed below comes from the Threadcore.cpp file in the MFC source code, which are members of Cwinthread. CwinsRead :: Run () structure
The code for cwinthread :: run () is as follows:
Int cwinthread :: run ()
{
Assert_Valid (this);
// for tracking the iDLE TIME STATE
Bool bidle = true;
Long lidlecount = 0;
// acquire and dispatch message 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 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 (ISIDEMESSAGE (& M_MSGCur))
{
Bidle = True;
LidleCount = 0;
}
} While (:: PepMessage (& M_Msgcur, Null, Null, Null, PM_NOREMOVE));
}
Assert (false); // not Reachable
}
The process of the CWINTHREAD :: run () is as follows:
Depending on whether the idle flag and the message queue are empty, it is judged whether or not the current thread is in an idle state (this "idle" meaning is different from the meaning of the operating system, and is the so-called "idle" "idle" of the MFC yourself. If so, call CWINTHREAD :: OnIdle (), this is also a virtual function we are more familiar. If not, remove the message from the message queue, proceed until the message queue is empty.
Here, we found that the MFC does not call GetMessage () to take a message from the thread message queue, but call PeekMessage (). The reason is that getMessage () is a function with synchronous behavior. If there is no message in the message queue, getMessage () will always block, so that the thread is on the sleep state until there is one or more messages in the message queue, the operating system will Wake up this thread, getMessage () will return. If the thread is in sleep, it will not make the thread with the so-called "idle" state of the MFC; and PeekMessage () is a function with asynchronous behavior if the message queue is No message, it will return to 0 immediately, which will not cause the thread to sleep. In the above code, two functions are worth exploring, one is idle processing function onIdle (), and another is the message distribution processing function PumpMessage (). Don't ignore the CWINTHREAD's onder () function, which makes a lot of meaningful things. The following discusses PumpMessage (), onIdle () will be discussed in the following chapters.
Core () at Cwinthread :: Run () CWINTHREAD :: PumpMessage ()
The title emphasizes the importance of pumpMessage (), and Run () is the MFC's control center, and pumpMessage () is
Is the core of Run (), so the true control center from the MFC is pumpMessage (). PumpMessage () code is extremely simple:
Bool cwinthread :: pumpMessage ()
{
Assert_Valid (this);
IF (! :: getMessage (& m_msgcur, null, null, null)
Return False;
// Process this message
IF (M_Msgcur.Message! = WM_KICKIDLE &&! PretranslateMessage (& M_MSGCUR))
{
:: TranslateMessage (& M_MSGCur);
:: DISPATCHMESSAGE (& M_MSGCUR);
}
Return True;
}
First, pumpMessage () calls getMessage () takes a message from the message queue, because pumpMessage () is called when there is a message in the message queue, so getMessage () will immediately return, according to its return value, judgment the current Removing the message is not a wm_quit message (this message is generally placed in the thread message queue by calling postquitMessage ()), if so, return false, cwinthread :: run () to exit, cwinthread :: run () Direct Call CWINTHREAD :: EXIXSTANCE () Exit the application. Behind getMessage () is the translateMessage () and DispatchMessage () functions we are familiar with.
It can be seen that it is determined whether translateMessage () and DispatchMessage () are determined by a return value of a name as a PretranslateMessage () function. If the function returns True, the message will not be distributed to the window function processing.
As far as my personal point of view, I have this preted (), so that the MFC can flexibly control the distribution mode of the message, it can be said that PretranslateMessage () is the MFC message distribution mode.
BOOL CWINTHREAD :: PretranslateMessage (MSG * PMSG)
{
Assert_Valid (this);
// if this is a thread-message, short-circuit this function
IF (PMSG-> HWND == Null && DispatchthreadMessageEx (PMSG)) Return True;
// Walk from target to main window
CWND * PMainWnd = AFXGETMAINWND ();
IF (cwnd :: walkpretranslatettetree (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-> getTopy ()! = PMainWnd)
Return PMainWnd-> PretranslateMessage (PMSG);
}
Return False; // no special processing
}
The process of PretranslateMessage () is as follows:
First, it is determined whether the message is a thread message (the message of the window handle is an empty message), if yes, hand it over to the dispatchthreadMessageEx () processing. We are temporarily dispatchthreadmessageex (), it is not the focus of our discussion. Call CWnd :: WalkPretratesLateTree () Processes the message, note that one parameter of this function is the handle of the thread main window, which is the core code of PretranslateMessage (), which will be detailed after the function is detailed. For non-mode dialogs, this special, additional processing.
Let's discuss the CWnd :: WalkpretranslateTree () function in detail below, its code is simple:
Bool Pascal CWnd :: Walkpretranslatetree (HWND HWNDSTOP, MSG * PMSG)
{
Assert (hwndstop == null || :: iswindow (hwndstop);
Assert (PMSG! = Null);
// Walk from the target window up to the hwndstop window checking
// if any window wants to translate this message
For (hwnd hwnd = pmsg-> hwnd; hwnd! = null; hwnd = :: getparent (hwnd) {
CWND * PWND = CWND :: fromHandlepermanent (hwnd);
IF (PWND! = null)
{
// Target Window Is A C Window
IF (PWND-> PretranslateMessage (PMSG))
Return true; // trapped by target window (eg: accelerators)
}
// got to hwndstop window without intend
IF (hwnd == hwndstop)
Break;
}
Return False; // no special processing
}
CWnd :: WalkpretranslateTree () The policy is very simple, the window owns the message first gets the first
The handling of the message, if it does not want to process the message (the PretranslateMessage () function of the window object returns to false, hand it over to its father's window, so spread to the roots of the tree until you encounter hWndstop (in Cwinthread :: PretranslateMessage (), hWndStop represents the handle of the thread main window). Remember the direction of delivery of this message processing, is passed by a general node or leaves node of the tree to the root of the tree!
summary:
Here is a summary of this chapter.
The MFC message control flow is the most distinctive place is the virtual function of the CWnd class PretranslateMessage (). By overloading this function, we can change the MFC's message control process, and even a new control flow, in the following chapter Detailed introduction to the implementation of MFC. Only messages that pass through the message queue are affected by the PretranslateMessage (), using the message sent directly to the window without the message queue, and the presence of PretranslateMessage () is ignored. The message is an undelated message, which has not been treated by translateMessage (), in some cases, to be carefully processed, so as not to miss the message.
application
I often encounter someone asking me,
Window creation and window message mapping in MFC
I often encounter someone asking me about the problem of window creation, and they often use HWND to describe the system window.
The window object of the MFC described with CWnd is confused. Between the two is closely linked, but MFC adds some additional content in CWND for its own management, including how to generate CWnd from HWND.
In the MFC, there are several typical window objects, and the general window object described in CWnd, the view described in CView
Object, CFrameWnd describes the SDI box window object, CMDIFrameWnd describes the MDI box window object, and the like. In this chapter, it is mainly discussed below:
Message Map created by Windows in the MFC
For designers of the above MFCs, use high skills to ensure that the code's code is as small as possible.
The skills and the thoughts hidden behind them are worth learning. The contents are discussed below.
Creation of Windows in MFC
Under WINDOW, create a window to use two functions, CreateWindow (), and CreateWindowEx (),
They all need a parameter, this parameter is the string of the identification window class. So, if you want to create a window, the general approach is to use registerclass () or registerclassex () to register a window class, then use the window class to create a window. In front, I also mentioned that the most important purpose of the registration window class is to provide the address of the window function to be repurchad by the function such as DISPATCHMESSAGE (). In the MFC, the function of the creation window is a Create () or CreateEx method of CWnd or its derived class. The registration window class generally uses AFXREGIsterWndClass (). In this global function, there is no discovery window function address, so the brain There will be such a problem naturally: Where is the window function? How did it associate with the window? Below we will carefully analyze some of the code related to this, answering the above two questions.
The window function is in the MFC, there is a global function AFXWndProc (), as shown below, it is CWnd and all window functions from which the window class derived, its implementation is as follows: // The WndProc for all CWnd's and derived classes LRESULT CALLBACK AfxWndProc (HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) {// special message which identifies the window as using AfxWndProc if (nMsg == WM_QUERYAFXWNDPROC) return 1; // all other messages route through message map CWnd * pWnd = CWnd :: FromHandlePermanent (hWnd); ASSERT (pWnd = NULL!); ASSERT (pWnd-> m_hWnd == hWnd); return AfxCallWndProc (pWnd, hWnd, nMsg, wParam, lParam);} AfxCallWndProc () call pWnd WindowProc object virtual function (), which code is as follows: LRESULT CWnd :: (UINT message, WPARAM wParam, LPARAM lParam) {// OnWndMsg does most WindowProc of the work, except for DefWindowProc call LRESULT lResult = 0; if (! OnWndmsg (Message, WPARAM, LPARAM, & LRESULT) LRESULT = DefWindowProc (Message, WPARAM, LPARAM); RETURN LRESULT;} OnWndMSG () is used to process the function of the window message, if a message is not OnWndMsg () is handled, that is, the window does not provide a function that handles the message. It calls defWindowProc () to process. DefWindowProc () is also a virtual function, look at its code: LResult CWnd :: DefWindo wProc (UINT nMsg, WPARAM wParam, LPARAM lParam) {if (m_pfnSuper = NULL!) return :: CallWindowProc (m_pfnSuper, m_hWnd, nMsg, wParam, lParam); WNDPROC pfnWndProc; if ((pfnWndProc = * GetSuperWndProcAddr ()) == NULL) return :: DefWindowProc (m_hWnd, nMsg, wParam, lParam); else return :: CallWindowProc (pfnWndProc, m_hWnd, nMsg, wParam, lParam);} DefWindowProc () strategy is simple, window function calls the base class m_pfnSuper Process the message. Through the above analysis, it can be concluded that it is said that AfxWndProc () is the unique window function of the MFC, but also saying AfxWndProc () is the MFC window message distribution center.
It is because of this message distribution center, the MFC application can make a variety of shape-colored windows with a limited number of window classes, so that in the application, add CWnd derived classes, and does not increase the window in the system. The number of classes will be within a stable range of use of system resources. In addition to providing a window function, the registration window class also specifies some of the appearance of the window, such as whether there is title bar, window default background, etc. In the MFC framework, there are frames, views, and control bar, etc., which are different from outside the operational behavior, the appearance, etc., so the MFC registers several default window classes. In the MFC, there is a global function AFXEnddeferRegisterClass (long ftoregister) that is used to register the MFC predefined window class, including the same box window, the window class corresponding to the view. Since its code is very long, and it is also very simple, it does not list its code. If you are interested, you can find its implementation code in WinCore.cpp. Mount window function If you examine the implementation code of AFXEnddeferRegisterClass (), you will be confused,
The surface is listed is part of the AFXEFERREGISTERCLASS (), and the shadow part is the confusing code:
Bool AFXAPI AFXENDDEFERREGISTERCLASS (long ftoregister)
{
. . . . . .
Wndcls.lpfnwndproc = defWindowproc;
. . . . . .
}
MFC sets all predefined window functions to DefWindowProc. Everyone knows that DEFWINDOWPROC is the API that provides messages for the general window under Window. It is definitely not the window function required by the application, so MFC is definitely replaced in a place, where is the code of the defWindowProc?
As mentioned earlier, when creating a window in the MFC Create () and Createex (), Create (), is achieved by calling createex (), so the final window is created to createex () On the function. Therefore, we can infer the MFC to replace DEFWINDOWPROC in Createex (). In order to confirm this, see the code of CWnd :: Createex ():
Bool CWnd :: Createex (DWord DwexStyle, LPCTSTR LPSZCLASSNAME,
LPCTSTSTSZZWINDOWNAME, DWORD DWSTYLE,
INT X, INT Y, INT NWIDTH, INT NHEIGHT,
HWND HWNDPARENT, HMENU NIDORHMENU, LPVOID LPPARAM)
{
// Allow Modification of Several Common Create Parameters
Createstruct CS;
cs.dwexstyle = dwexstyle;
Cs.lpszclass = lpszclassname;
cs.lpszname = lpszwindowname;
CS.Style = DWStyle;
cs.x = x;
CS.Y = Y;
Cs.cx = nwidth;
cs.cy = nheight;
cs.hwndparent = hwndparent;
cs.hmenu = Nidorhmenu;
Cs.hinstance = AfxgetInstanceHandle ();
cs.lpcreateparams = lpparam;
IF (! PrecreateWindow (CS)) {
Postncdestroy ();
Return False;
}
AfxhookWindowCreate (this);
HWND HWND = :: CreateWindowex (cs.dwexstyle, cs.lpszclass,
Cs.lpszname, cs.style, cs.x, cs.y, cs.cx, cs.cy,
Cs.hwndparent, cs.hmenu, cs.hinstance, cs.lpcreateparams;
If (! AfXunhookWindowcreate ()) Postncdestroy (); // Cleanup if CreateWindowEx Fails Too Soon
IF (hwnd == null) Return False;
AskERT (hwnd == m_hwnd); // shop Have Been Set in Send MSG Hook
Return True;
}
From the above code, no explicit replacement DefWindowProc code is, in fact, it is hidden in AfxHookWindowCreate (this), and then looks at the code of AfxHookWindowCreate ():
Void AFXAPI AFXHOKWINDOWCREATE (CWND * PWND)
{
_Afx_thread_state * pthreadstate = _afxthreadState.getData ();
IF (pthreadstate-> m_pwndinit == pWnd)
Return;
IF (pthreadstate-> m_hhookingcbtfilter == null)
{
PthreadState-> M_HHHOOKOLDCBTFILTER = :: setwindowshookex (wh_cbt,
_Afxcbtfilterhook, null, :: getCurrentThreadId ());
IF (pthreadstate-> m_hhookingcbtfilter == null)
AfXTHROWMEMORYEXCEPTION ();
}
Assert (pthreadstate-> m_hookoldcbtfilter! = Null);
Assert (PWND! = Null);
Assert (pwnd-> m_hwnd == null); // only do overce
AskERT (pthreadstate-> m_pwndinit == null); // hook not already in Progress
PthreadState-> m_pwndinit = pwnd;
}
AfxHookWindowCreate () sets a thread-level CBT Hook, Hook the entry address is _AfxCbtFilterHook, _AfxCbtFilterHook is a global MFC function, _AfxCbtFilterHook entry address with AfxWndProc by calling SetWindowLong () () replacement out DefWindowProc.
summary:
In the MFC, the process of creating a window is:
Generating an object of a corresponding window class calling the Create () or createex () method of the object in CreateEx () (CREATE () is called CreateEx () implementation, first call the virtual function precomreWindow (), let the app have one Change the chance of window behavior, at the same time, in PrecreateWindow, a very important thing is that if the string pointer pointing to the window class is NULL, call AFXDEFERREGISTERCLASS () to register a suitable window class, will be registered Window class as a type parameter created by the window. AFXDeferRegisterClass () is AFXEnddeferRegisterClass (), the latter registers the corresponding window class according to the parameter, and will defWindowProc as the temporary window function of the window class. CreateEx () Set a thread-level CBT Hook before calling CREATEWINDOWEX (), which is called after the window creation is complete, and the MFC calls setWindowlong () in the hook function to replace the window function. AfxWndproc. From a Window perspective, any MFC application has only one window function AFXWndProc. The role of AFXWndProc is to intercept all send to window messages, and send these messages to the window function of the corresponding window object, so it is essentially a window message distributor, note that non-window messages are not distributed by AfxWndProc, They were handled by cwinthread :: pretranslateMessage () before AfxWndProc was called. As PretranslateMessage () Different, AfxWndProc () can intercept window messages from the message queue and the non-message queue (such as calling sendMessage ()).
MFC message mapping mechanism
MFC's designers are tightly grasping a goal when designing MFC, which is to make MFC generation
The code is small and the speed is as fast as possible. For this goal, they use many techniques, many of whom are embodied in macros, and the mechanism for realizing the Message map of the MFC is one of them.
The macro related to the MFC message mapping mechanism has the following:
DECLARE_MESSAGE_MAP () Macro Begin_Message_map (THECLASS, BASECLASS) and END_MESSAGE_MAP () Macro
The best way to understand the MFC message mapping mechanism is to find a specific instance, open these macros, and find
A relevant data structure.
Declare_message_map ()
Declare_MESSAGE_MAP () macro is defined as follows:
#define declare_message_map () /
PRIVATE: /
Static const AFX_MSGMAP_ENTRY _MESSAGEENTRIES []; /
protected: /
Static AFX_DATA Const AFX_MSGMAP MessageMap; /
Virtual const AFX_MSGMAP * getMessageMap () const; /
As can be seen from the above definition, declare_MESSAGE_MAP () makes the following three things:
Define a length of static array variables _MessageEntries []; define a static variable messageMap; define a virtual function getMessageMap ();
In the DECLARE_MESSAGE_MAP () macro, two external non-disclosed data structures AFX_Msgmap_Entry and AFX_MSGMap are involved in the MFC. In order to figure out the message mapping, it is necessary to investigate the definition of these two data structures.
Definition of AFX_MSGMAP_ENTRY
STRUCT AFX_MSGMAP_ENTRY
{
Uint NMessage; // Windows Message
Uint ncode; // control code or wm_notify code
Uint Nid; // Control ID (or 0 for Windows Messages)
UINT NLASTID; / / USED for Entries Specifying a Range of Control ID's
Uint nsig; // signature type (action) or pointer to message #
AFX_PMSG PFN; // Routine to Call (Or Special Value)
}
The meaning annotation of the structures has been clearly described, which is no longer described. It is seen from the above definition that the AFX_MSGMAP_ENTRY structure actually defines the mapping between the messages and the action of this message. Therefore, the static array variable _MessageEntries [] actually defines a table, each of the tables specifies the message to be processed by the corresponding object and the corresponding relationship of the function of this message, and thus this table is also called a message mapping. table. Take a look at the definition of AFX_MSGMap.
(2) Definition of AFX_MSGMap
Struct AFX_MSGMAP
{
Const AFX_MSGMAP * PBASEMAP;
Const AFX_MSGMAP_ENTRY * LPENTRIES;
}
It is not difficult to see that AFX_MSGMAP defines a single-way linked list, and the value of each of the linked list is a pointer to a message mapping table (actually the value _MessageEntries). Through this linked list, the message processing function that calls the base class in a class is easy. Therefore, "the message processing function of the parent class is the default message processing function of the subclass", "is" successful ". This will be explained in detail in the "Message Processing" section of the later "MFC window".
Begin_MESSAGE_MAP () and END_MESSAGE_MAP ()
They are defined as follows:
#define begin_Message_Map (Theclass, Baseclass) /
Const AFX_MSGMap * Theclass :: getMessageMap () const /
{RETURN & THECLASS :: MessageMap;} /
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP THECLASS :: MessageMap = /
{& Baseclass :: MessageMap, & Theclass :: _ Messagentries [0]}; /
AFX_COMDAT const AFX_MSGMAP_ENTRY THECLASS :: _ MessageEntries [] = /
{/
#define end_message_map () /
{0, 0, 0, 0, AFXSIG_END, (AFX_PMSG) 0} /
}; /
The definition of begin_message_map () may not be seen at once, but not tight, lift
An example is very clear. For Begin_Message_Map (CView, CWnd), the VC Preciator expands its form:
Const AFX_MSGMap * CView :: getMessageMap () Const
{
Return & CView :: messageMap;
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CVIEW :: MessageMap =
{
& CWnd :: MessageMap,
& CView :: _ MessageNtries [0]
}
AFX_COMDAT Const AFX_MSGMAP_ENTRY CVIEW :: _ MessageEntries [] =
{
As for END_MESSAGE_MAP (), it is defined that a sign item indicating the end of the mapping table, I think everyone should be very familiar with this simple skill, no need to describe it.
So, I think everyone has already thought of the specific role of macros like ON_COMMAND, nice they only define a type of message mapping item, see the definition of on_command:
#define on_command (id, memberfxn) /
{WM_COMMAND, CN_COMMAND, (WORD) ID, (WORD) ID, AFXSIG_VV, (AFX_PMSG) & MemberfxN},
According to the above definition, on_command (id_file_new, onfilenew) will be expanded by the VC precompilator
as follows:
{WM_COMMAND, CN_COMMAND, (WORD) ID, (WORD) ID, AFXSIG_VV,
(AFX_PMSG) & onfilenew},
At this point, the Message mapping mechanism of the MFC has been clear, and two problems will now be made as a summary of this section.
Why not directly use the virtual function to implement the message processing function? This is a good question. As mentioned earlier, MFC designers have a clear goal in designing MFC, which is to make "MFC code as small as possible, speed as possible", if the virtual function is used, then for all window messages, There must be a virtual function corresponding to it, so there is a large virtual function table VTBL for each class derived from the CWND. However, in practical applications, only only for the minority message, most of them are given to the system default processing, so most of the tables are unused, so they areted to waste a lot of memory resources, this is the same MFC design The design objectives are contrary. Of course, the methods used by the MFC are only one of the ways such as such problems, and there is still other solutions, but in my personal opinion, this is the best solution, reflecting a high skill. Sex, worth our study. As for this second problem, it is attached to the above problem. How do you handle this problem if the same message appears in subclass and parent class? How do VC compilers handle this problem? The VC does not view them as errors, while dealing with a way to deal with virtual functions, but for message processing functions (with an AFX_MSG prefix), the virtual function table VTBL will not be generated.
Frame window, view and documentation and its relationship
Another feature of the MFC architecture is its box, view, and document this three-piece structure, it is a code
Model MVC (Model, View and Controler) structure. Strictly speaking, the box window does not belong to any of the MVC, and the MFC designer adds the box window in order to better coordinate documents and views. This is the application logic of the application itself in the MVC. In these three, special attention is needed, and it is also possible to reflect the personal programming level is the frame window. Once the three exist in memory, their relationship is very simple. This chapter will discuss the following:
1. MFC's RTTI (Run Time Type Inspection, runtime type check) box, view, and document creation order and procedure. Frame windows, views, and documents delete order and procedures. The mutual access interface between the frame, the view, and the document. Frame window, view, and documentation for menus and toolbar message processing
MFC's RTTI
The Earlier C designer used in C did not realize the importance of RTTI (runtime type check), later
As the framework structure is increasing, RTTI is increasingly important. For example, the following statement:
CWND * PWND;
Anyone knows that the object P is a CWND type pointer. But if there is a class cView is derived from the CWnd, for the following statement:
CWND * CREATEVIEW ()
{
Return new cView;
}
For users who use createview (), PWND = CREATEVIEW (), how do he determine the true type of object points to the PWND? Therefore, there must be a method that can determine the type of object type at runtime, such as a method of adding an iskindOf () to each type of object, and judge the type of the pointer object by this method.
Later, RTTI was added to the C specification and became a built-in characteristic of C .
When the MFC designer designs MFC, there is no RTTI in the C specification, but they have realized this problem very early, so they use a unique way to implement RTTI in the MFC, using this way of implementing RTTI For an object, it is not necessary, that is, the MFC designers do not impose RTTI on the type designed by the user, but allow the user to choose whether he needs RTTI according to its own needs. Therefore, this approach is more flexible than RTTI built in the C specification.
The MFC designers use the following methods in the MFC to implement RTTI:
Design a base class cobject, add RTTI functions in COBject, any type, if you need RTTI function, you must directly or indirectly derived from COBJECT using macro to implement RTTI, for some or indirectly derived from COBJECT. , This macro
There is no, if there is a macro, it has an RTTI function, but it is nothing.
<1> Investigation COBJECT
Let's start with COBJECT, below is its definition:
Class AFX_NOVTABLE COBJECT
{
PUBLIC:
// Object Model (Types, Destruction, Allocation)
Virtual cruntimeclass * getruntimeclass () const;
Virtual ~ cobject (); // Virtual Destructors Are Necessary
// Diagnostic Allocations
Void * Pascal Operator new (size_t nsize);
Void * Pascal Operator New (size_t, void * p);
Void Pascal Operator Delete (Void * P);
Void Pascal Operator Delete (Void * P, Void * PPLACE);
Void Pascal Operator Delete (Void * P, LPCSTR LPSZFILENAME, INT NLINE);
// disable the copy constructor and assignment by Default So you will get // compiler errors instead of unexpected behaviour iF you pass objects
// by value or assocign objects.
protected:
COBJECT ();
Private:
COBJECT (Const Cobject & Objectsrc); // No Implementation
Void Operator = (const cobject & objectsrc); // no importation
// attributes
PUBLIC:
Bool isserializable () const;
Bool iskindof (const cruntimeclass * pclass) const;
// Overridables
Virtual Void Serialize (CARCHIVE & A);
// Implementation
PUBLIC:
Static const AFX_DATA CRUNTIMECLASS CLASSCOBJECT;
}
In general, COBject defines two basic capabilities of all members of their derived families:
Dynamic Type Check (RTTI) capabilities and serialization capabilities at runtime. In the early C version, there is no ruled RTTI, but the authors of the MFC have long been unobstructed, defined in the form of this architecture and implemented RTTI. Reflecting RTTI is two member functions in COBject:
Virtual cruntimeclass * getruntimeclass () const;
Bool iskindof (const cruntimeclass * pclass) const;
Among them, the previous function is used to access a CRUNTIMECLASS type of the RTTI information, and the latter function is used to determine the type of time at runtime. Let's take a look at the definition of the CruntimeClass structure to see what types of information it has saved.
<< from afx.h >>
Struct cruntimeclass
{
// attributes
LPCSTR M_LPSZCLASSNAME;
INT m_nObjectsize;
Uint m_wschema; // Schema Number of the loading class
COBJECT * (Pascal * m_pfncreateObject) (); // null => Abstract Class
Cruntimeclass * m_pbaseclass;
// Operations
COBJECT * CREATEOBJECT ();
Bool isderiveDFrom (const cruntimeclass * pbaseclass) const;
// Implementation
Void Store (CARCHIVE & AR) Const;
Static CruntimeClass * Pascal Load (CARCHIVE & Ar, Uint * Pwschemanum);
// CruntimeClass Objects Linked TOGETHER in Simple List
Cruntimeclass * m_pnextclass; // linked list of registered classes
}
The above is the definition of CruntimeClass, the name of the m_lpszclassname saves the class, the size of the M_NObjectSize saves the instance data of the class. We focus on the M_PBaseClass member, which is a pointer to the cruntimeclass of the base class named M_LpszClassName. Therefore, CruntimeClass has formed a inheritance list, which records a certainty class's inheritance relationship. Realization of RTTI:
In addition to the above two functions, there are several related macros in addition to the two functions above. Let's take a look at getRuntimeClass () and iskindof () implementation.
1. GetRuntimeClass () implementation
Cruntimeclass * COBject :: getRuntimeClass () const
{
Return Runtime_Class (COBject);
}
The key is in Runtime_Class, the implementation of the runtime_class macro is as follows:
#define runtime_class (Class_name) (CRUNTIMECLASS *) (& class_name :: class ## class_name))
The macro is displayed, the implementation above is:
Cruntimeclass * COBject :: getRuntimeClass () const
{
Return (CRUNTIMECLASS *) (& COBJECT :: ClassCObject);
}
That is, it returns a Static type member classcobject of the COBJECT class.
2. ISKINDOF () implementation
Bool COBject :: Iskindof (const cruntimeclass * pclass) Const
{
Assert (this! = Null);
// IT better be in Valid Memory, At Least for COBJECT SIZE
ASSERT (AFXISVALIDADDRESS (this, sizeof (cobject));
// Simple Si Case
Cruntimeclass * pclassthis = getRuntimeClass ();
Return Pclassthis-> isderiveDFrom (PCLASS);
}
The front two lines should we don't care about it, the key is the last line PCLASSTHIS-> ISDERIVEDFROM (PCLASS), and the root is the ISDeriveDFROM () method to call CruntimeClass. Here is the implementation of the member of CruntimeClass's ISDERIVEDFROM ():
Bool Cruntimeclass :: isderiveDFrom (const cruntimeclass * pbaseclass) const
{
Assert (this! = Null);
ASSERT (AFXISVALIDIDDRESS (THIS, SIZEOF (CRUNTIMECLASS), FALSE);
Assert (PBaseClass! = NULL);
ASSERT (AFXISVALIDADDRESS (PBASECLASS, SIZEOF (CRUNTIMECLASS), FALSE);
// Simple Si Case
Const cruntimeclass * pclassthis = this;
While (pclassthis! = null)
{
IF (PCLASSTHIS == PBaseclass) Return True;
PCLASSTHIS = PCLASSTHIS-> M_PBASECLASS;
}
Return false; // walked to the top, no match}
The key is the above cycle code:
While (pclassthis! = null)
{
IF (PCLASSTHIS == PBaseclass) Return True;
PCLASSTHIS = PCLASSTHIS-> M_PBASECLASS;
}
It starts from a node this of the inheritance list, and searches for later search, determines inheritance relationship.
It will be here, maybe someone is asking, how is these CRUNTIMECLASS structures? This is a good problem, solving this problem, fully understand the implementation of RTTI in the MFC. People who use the Visual C developer should remember the two macros of DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC, which are used to define the Static CruntimeClass members of the corresponding class and initialize the member.
Declare_dynamic macro definition:
#define declare_dynamic (class_name) /
PUBLIC: /
Static const AFX_DATA CRUNTIMECLASS CLASS ## Class_name; /
Virtual cruntimeclass * getRuntimeClass () const; /
For example, Declare_Dynamic (CView) is expanded to be:
PUBLIC:
Static const AFX_DATA CRUNTIMECLASS CLASSCVIEW;
Virtual cruntimeclass * getruntimeclass () const;
It can be seen that the DECLARE_DYNAMIC macro is used to define a static CruntimeClass variable and a virtual GetRuntimeClass () function in the definition of the class. It can be inferred that the import_dynamic macro must be used to initialize the static variable and implement the getRuntimeClass () function. Yes, just this!
Implement_dynamic macro definition:
#define import_dynamic (class_name, base_class_name) /
Implement_runtimeclass (class_name, base_class_name, 0xfffff, null)
#define import_runtimeclass (class_name, base_class_name, wschema, pfnnew) /
AFX_COMDAT constimeclass class_name :: class ## Class_name = {/
#class_name, sizeof (class class_name), WSChema, PFNNEW, /
Runtime_class (base_class_name), NULL}; /
CruntimeClass * Class_name :: getRuntimeClass () const /
{RETURN Runtime_class (class_name);} /
For example, Implement_Dynamic (CView, CWnd) is expanded as follows:
// The code expanded below is used to initialize the static CruntimeClass variable.
AFX_COMDATA Const AFX_DATADEF CRUNTIMECLASS CVIEW :: ClasscView =
{
"CView", // m_lpszclassname
Sizeof (Class CView), // m_nObjectsize
0xffff, // m_wschemanull, // m_pfncreateObject
(CruntimeClass *) (& CWND :: ClasscWnd), // m_pbaseclass
NULL / / M_PNEXTCLASS
}
// The following code is used to implement the getRuntimeClass () function
Cruntimeclass * cview :: getRuntimeclass () const
{Return (CRUNTIMECLASS *) (& CView :: ClasscView);
In general, the macro related to RTTI has the following pairs:
Declare_Dynamic and Implement_Dynamic This pair of macros can provide running is the type of judgment. (Define and implementing iskindof ()) declare_dyncreate and import_dyncReate This pair of macros can provide the ability to provide dynamically created objects. (Define and implement iskindof () and createObject ()) Declare_serial and import_serial
This pair of macros have eradicated type judgment capabilities, and dynamically create object capabilities, it also has serialization.
(Define and implement iskindof (), createObject () and serialize ())
Create order and process of frame, view, and document object
As mentioned earlier, the frame, the view, and the document are a three-piece frame structure, but in fact, this triple
It is not tight, this "not tight coupling" means that the three can be separated, and the document can be removed, but only reserve the view and box window and maintain the original relationship between the two; you can also remove the view and document, and Leave only frame windows, the program can operate in the framework.
In the MFC, the three organizations are organized, and the document template is not necessary in the general application, in general applications, in general applications.