Click here to Skip to main content
15,861,366 members
Articles / Programming Languages / C++
Article

Win32 vs. MFC - Part II

Rate me:
Please Sign up or sign in to vote.
4.73/5 (36 votes)
25 Aug 2001CPOL8 min read 139.2K   119   17
Discusses MFC architecture

Introduction

In the <a href="http: www.codeproject.com="" cpp="" mfc_architecture.asp"="">first episode of these series, I outlined how MFC encapsulates WinMain function and how it constructs a message loop, internally. Moreover, I said that every Windows program has got at least two functions, WinMain and wndProc but I did not discuss wndProc in detail.

Today, I am going to focus on window procedure in an MFC application and how the MFC team has constructed this for MFC programmers. If you have not read the first part of this paper yet, I suggest you to start from there since it deals with the ABC of Win32 and MFC applications.

Having all of these in mind, lets see what I have got to say today.

Window procedure

The Window procedure (or window function) is an application-defined function that processes messages sent to a window. The messages vary from the messages generated by your mouse to the messages posted to your application by another process. This special function is not called by your program nor by MFC, but it is called by Windows. The truth is that the program waits until it is sent a message by Windows. Upon the reception of a message (through the specified callback function), the program is supposed to take an appropriate action by doing some processes, calling an API or two and providing the end-user with whatever functionality your program is expected to provide.

In Windows terminology, a callback function is a function that is called by Windows. Window procedure is a great example of a callback function that is prototyped as follows:

LRESULT CALLBACK WindowProc
    (
    HWND hwnd, // handle to window
    UINT uMsg, //message identifier
    WPARAM wParam, // first message parameter
    LPARAM lParam //second message parameter
    );

Where LRESULT is declared as long data type, and CALLBACK specifies the __stdcall calling convention (To see this, move the caret to your desired data type and press F12, whilst interacting in VC++ 6.0).

You could recall from the first episode of this paper how our window procedure works and what it is supposed to do. However, for the simplicity of reading, we bring the callback function here again:

LRESULT CALLBACK wndProc(HWND hWnd, UINT message,
                         WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hWnd, message, wParam,
            lParam);
    }
    return 0;
}

This function, as stated before, is called whenever Windows generates a message so that we could handle the message or pass it easily to the default window procedure by making a call to DefWindowProc function. The DefWindowProc function calls the default window procedure to provide default processing for any window messages that an application does not process. This function ensures that every message is processed, and not amazingly, is called with the same parameters received by the window procedure.

Upon the reception of WM_DESTROY, we make a call to PostQuitMessage that indicates to the system that a thread has made a request to terminate. Doing so causes our message loop to be broken and the program will be terminated successfully.

MFC's window procedure

Having completed the former section, it is now time to answer an important question: "Where the heck does window procedure reside in an MFC application?" To understand the answer of this question, I would suggest you to hold your breath whilst following my guidelines. The answer is a little bit complicated!

If you recall from the first part of this paper, the window procedure in an Win32 application is set by assigning the address of our window procedure to lpfnWndProc member of WNDCLASSEX. The same happens in an MFC application. However, the important thing is that this assignment could happen in more than one place, depending on how you create the application.

The application framework's window procedure, which is the heart of an MFC application, is named AfxWndProc and is prototyped the same as our familiar Win32 window procedure. This window function is implemented as follows:

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);
}

As far as you could see, AfxWndProc doesn't do the message dispatching. It, however, calls AfxCallWndProc, and this function in turn, makes a call to CWnd::WindowProc that in turn calls the CWnd::OnWndMsg(...) method. The actual message dispatching is done in this function, except  for WM_COMMAND or WM_NOTIFY! In the WM_COMMAND case, OnWndMsg makes a call to OnCommand function and in the case of WM_NOTIFY message, OnNotify function is called, both of which make a call to CCmdTarget::OnCmdMsg(...) to do the dispatching:

Image 1

What do I mean by actual dispatching and where does the message map come into play? How does MFC understand that upon the reception of WM_PAINT it has to call the OnPaint function?

To understand all of these, you first have to know that MFC doesn't route messages according to their message ID. In other words, MFC doesn't rely on message IDs (WM_PAINT, WM_MOVE and WM_SIZE to new a few) to call your handler functions. Instead, it switches on the prototype (or the signature) of the message handler function.

If you create a dialog-based MFC application, and you name it sample, you will find out that MFC wizard will generate a message map for your dialog class, something as follows:

BEGIN_MESSAGE_MAP(CSampleDlg, CDialog)
    //{{AFX_MSG_MAP(CSampleDlg)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

Take a precise look at this automatically generated message map! There's an entry, say, ON_WM_PAINT, that's defined as follows:

#define ON_WM_PAINT() \
    {WM_PAINT, 0, 0, 0, AfxSig_vv, \
    (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))&OnPaint},

where AfxSig_vv is the signature of the message handler function. Dumb, huh?

AfxSig_vv means "Application framework's signature, that returns a void value as well as taking a void parameter". In other words, it says that the OnPaint function is prototyped as follows:

void OnPaint(void);

What happens in case of WM_SIZE? You might already guess that the signature of its handler function has to be defined as AfxSig_vwii, since the prototype of OnSize states this:

void OnSize(UINT nType, int cx, int cy)

and therefore, by AfxSig_vwii we mean that the handler function returns a void value, while taking 3 parameters, UINT, int and int. So, you could probably guessed that the ON_WM_SIZE macro is defined as follows:

#define ON_WM_SIZE() \
    { WM_SIZE, 0, 0, 0, AfxSig_vwii, \
    (AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(UINT, int, int))&OnSize },

And where are all of these signatures defined? They are declared in the Afxmsg_.h header file under the include directory of the compiler. We already said that MFC switches on the signature of the handler function rather than its message ID. If you take a precise look to OnWndMsg member function of CWnd (declared at ..\MFC\SRC\WinCore.cpp), you will soon find out how MFC dispatches messages.

It first, makes a call to GetMessageMap function that gets the message map of your window class. This function returns a structure of type AFX_MSGMAP that has got two entries:

struct AFX_MSGMAP
{
#ifdef _AFXDLL
    const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
    const AFX_MSGMAP* pBaseMap;
#endif
    const AFX_MSGMAP_ENTRY* lpEntries;
};

Where _AFXDLL shows how MFC DLLs has to be linked to the program either in a shared DLL or in a static library. On the other hand, AFX_MSGMAP_ENTRY is declared as follows:

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)
};

Now, let's see what happens in case of reception of WM_PAINT message. Within the CWnd::OnWndMsg member function, the message map is taken (by making a call to GetMessageMap(...) function) and the lpEntries member of AFX_MSGMAP structure is filled up by the following values:

nMessage = 15
nCode = 0
nID = 0
nLastID = 0
nSig = 12
pfn = CSampleDlg::OnPaint()

This means that upon the reception of WM_PAINT message (defined as 0x000F), the OnPaint() handler function has to be called whose signature is 12 (AfxSig_vv). Then, a switch is made on the nSig, which in this example is equal to AfxSig_vv:

union MessageMapFunctions mmf;
mmf.pfn = lpEntry->pfn;

Switch(nSig)
{
    //snipped
case AfxSig_vv:
    (this->*mmf.pfn_vv)(); 
    break;
    //snipped
}

And therefore the CSampleDlg::OnPaint function is called. And what about MessageMapFunctions? MessageMapFunctions, is a union that indicates a pointer to a function of all types, pfn_bb, pfn_vv, pfn_vw and more. Neat, huh? However, do not release your breath, yet! There's a little bit more to say.

You already noticed that MFC dispatches on the signature code instead of the message ID. The question is why does MFC do so?

To answer this question, let's take a precise look to WM_SIZE and how it has to be handled in non-MFC world. Whenever a window size is changed, WM_SIZE message is sent to the application through its window procedure:

LRESULT CALLBACK WindowProc(HWND hwnd, // handle to window
    UINT uMsg, // WM_SIZE
    WPARAM wParam, // resizing flag
    LPARAM lParam); // client area

Where wParam specifies the type of resizing requested. The low-order of lParam specifies the new width of the client area and the high-order of lParam specifies the new height of the client area. Now imagine what happens if an MFC programmer has to convert all of these parameters into meaningful arguments, manually? In this case, he just needs to take the high order and/or low order of lParam to take the width and height of the new client area. However, when dealing with other messages, more processing is required. Moreover, it would be a nightmare to handle all of these manually, of course, from the MFC programmer's point of view.

To overcome this problem, MFC first packages each argument of the handler function into a more type-safe object and thereafter it makes a call to your handler function with those type-safe arguments. In other words, it converts wParam and lParam parameters into more meaningful values. In case of WM_SIZE, we already saw that the prototype of the handler function is as follows:

void OnSize(UINT nType, int cx, int cy)

It means that MFC has converted wParam and lParam into 3 parameters, nType, cx and cy that's more efficient to deal with for a programmer.

Now, release you breath! From now on, the MFC is not a black box for you. You got an overview of how it is implemented and how it dispatches messages, as well as how it uses message maps effectively. However, the MFC architecture, IMHO, takes a book or two to be described well. I think you already got the idea, though. Anyway, if someday for some reason you write a book about the MFC architecture, I would be so pleased if you could share a copy with me.

As ever before, I would like to state that all comments, questions and/or suggestions are welcome. When I published the first episode on www.codeproject.com, I received an amazing number of emails asking me to write about Windows Sockets, OpenGL and other technologies as well. The truth is that I will certainly do so the day I will be retired! :) Anyhow, you can reach me via my email address, <a href="mailto:mehdi_mousavi@hotmail.com"> mehdi_mousavi@hotmail.com

For the next episode, I am going to cover Document/View architecture in an MFC SDI application. Until then, aloha!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
Mehdi Mousavi is a professional computer programmer. He has written many programs using C, C++, MFC, Win32, COM, DCOM, JavaScript, ASP, HTML, DHTML, SQL, C# and MC++.

Comments and Discussions

 
Generalnice tutorial.. Pin
marigold12341-Apr-14 6:02
marigold12341-Apr-14 6:02 
QuestionInteresting! Pin
WuRunZhe6-Dec-13 14:20
WuRunZhe6-Dec-13 14:20 
QuestionGreat Article Pin
Udyawar Raghavendra 30-Nov-11 18:28
Udyawar Raghavendra 30-Nov-11 18:28 
GeneralMy vote of 5 Pin
Anand Todkar31-Aug-10 17:38
Anand Todkar31-Aug-10 17:38 
GeneralSuper Pin
Bullet19791-Jul-10 0:11
Bullet19791-Jul-10 0:11 
QuestionHow a message is associated to message handler in WndProc via Message Map? Pin
nrj2327-May-08 18:44
nrj2327-May-08 18:44 
Generalserlization base class Pin
dinesh_kr0163-Jan-08 0:54
dinesh_kr0163-Jan-08 0:54 
Questionwhat diffence between api and funtion Pin
dinesh_kr0163-Jan-08 0:51
dinesh_kr0163-Jan-08 0:51 
Questionbut how is AfxWndProc called? Pin
code_discuss21-Jun-07 19:27
code_discuss21-Jun-07 19:27 
GeneralRe: but how is AfxWndProc called? Pin
xavierakx5-Jan-08 22:49
xavierakx5-Jan-08 22:49 
Questionhow to convert dialog based to SDI Application Pin
yogendra kaushik29-Jun-06 21:27
yogendra kaushik29-Jun-06 21:27 
GeneralExcellent Article Pin
Ken M20-Apr-06 10:29
Ken M20-Apr-06 10:29 
GeneralGr8 Job!! Pin
Sreekanth Muralidharan6-Dec-05 19:31
Sreekanth Muralidharan6-Dec-05 19:31 
GeneralGreat Article! Pin
antoine@orchus-tech15-Nov-03 17:46
antoine@orchus-tech15-Nov-03 17:46 
Generalnice job Pin
3-May-02 6:32
suss3-May-02 6:32 
GeneralMissing Image Pin
26-Aug-01 4:24
suss26-Aug-01 4:24 
GeneralRe: Missing Image Pin
Mehdi Mousavi26-Aug-01 4:56
Mehdi Mousavi26-Aug-01 4:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.