Introduction
In the 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,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);
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)
{
AfxWndProc
if (nMsg == WM_QUERYAFXWNDPROC)
return 1;
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:

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)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
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;
UINT nCode;
UINT nID;
UINT nLastID;
UINT nSig;
AFX_PMSG pfn;
};
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)
{
case AfxSig_vv:
(this->*mmf.pfn_vv)();�
break;
}
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,
UINT uMsg,
WPARAM wParam,
LPARAM lParam);
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,
mehdi_mousavi@hotmail.com
For the next episode, I am going to cover Document/View architecture in an MFC SDI
application. Until then, aloha!