Introduction
In a sunny day on May 29th, 2001, I and a friend of mine were talking
about the usefulness of MFC and how it has simplified our programming life.
He started by dropping the question of "what is the document/view architecture"
in our minds. Then we started to think around this question and we soon understood
that we do not know what it is and how the MFC encapsulates Win32 APIs internally.
When the second question aroused, we both agreed on having a look to some books,
articles and to schedule some interviews with this or that to find out the answers.
The second question was this: "Where the heck does
WinMain
function reside in an MFC application?"
This article is submitted to feed those hungry people who suffer from the lack of
resources about the document/view architecture and the structure of MFC wrapper,
and is lovingly dedicated to my parents, A.Shahideh, and S.Mousavi.
The ABC of Win32 applications
A Windows program contains at least two functions:
WinMain
and
wndProc
.
Every Windows program needs the
WinMain
function, since it is the entry point.
The other is a window procedure that processes window messages (Although the name window
procedure is not too bright, since it actually processes any messages corresponding to the program).
Let's start by discovering what the WinMain
is and talk about its functionality
and then, we will gear to wndProc
.
A WinMain
function is divided into three parts: procedure declaration,
program initialization and a message loop.
When a program starts running, Windows passes it some information, including but not
limited to the current instance handle of the application and the previous instance handle,
if available. Let's have a look to a Win32 API generated by the MSVC++ compiler:
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
The first parameter,
hInstance
, is declared as
int
, and is
the handle of the currently running instance of the application.
The second parameter, hPrevInstance
, is the handle of the previously
running instance of the same application. However, in 32-bit API of Windows NT, this
parameter is always NULL
, regardless of running multiple instances of
the same application. The reason lies behind this fact that in 32-bit Windows API,
each copy of the every program runs in its address space and will not share anything
with other currently running instances, for the security reasons.
The third parameter, lpCmdLine
, contains the command line arguments for
our program.
The final parameter, nCmdShow
, is used to declare the style of
the main window when the program first starts. It tells Windows how to show our
main window, i.e. maximized, minimized and the like.
The function's return value is prototyped as APIENTRY
,
that's defined as follows:
#define APIENTRY WINAPI
and in turn,
WINAPI
is declared as follows:
#define WINAPI __stdcall
And what the heck is
__stdcall
about? Unfortunately the answer
is beyond the scope of this article so we have to skip it and leave it for the
reader.
By the way, that's all what procedure declaration is about. Now the second
phase comes into play: program initialization.
Initialization involves calling three Windows API routines: RegisterClass
(or its extended version, RegisterClassEx
), CreateWindow
(or its the extended counterpart, CreateWindowEx
) and ShowWindow
(There's no extension for this API, though).
To create a window, we have to fill out the members of WNDCLASSEX
structure and pass an instance of this structure to the RegisterClassEx
API. Here's how:
WNDCLASSEX wcl;
wcl.cbSize = sizeof(WNDCLASSEX);
wcl.hInstance = hInstance;
wcl.lpfnWndProc = (WNDPROC)wndProc;
wcl.style = CS_HREDRAW | CS_VREDRAW;
wcl.hIcon = LoadIcon(hInstance, IDC_ARROW);
wcl.hIconSm = NULL;
wcl.hCursor = LoadCursor(NULL, IDC_ARROW);
wcl.lpszMenuName = NULL;
wcl.cbClsExtra = 0;
wcl.cbWndExtra = 0;
wcl.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wcl.lpszClassName = "myClass";
if(!RegisterClassEx(&wcl))
return 0;
When we do so, Windows copies the elements of this structure to somewhere called the
Class Database. When a program wants to create a window, it references an entry
in the class database, and thereafter, Windows uses that information to create
the window. Neat, huh?
Now it is time to have a look to the members of this structure, but since
it has got many members we leave it simply for the reader to discover the
functionality of each member. However, we are interested in one of those members,
since it directs us to the window procedure; the second function we are going to have
in our program. Yeah, that's lpfnWndProc
member. But hold on! We have to
finish the program initialization part of the WinMain
function first.
After the class data being registered, we call the CreateWindowEx
API to create the actual window.
HWND hWnd = CreateWindow("myClass",
"WindowTitle",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
And then we present the window using
ShowWindow
API as follows:
ShowWindow(hWnd, nCmdShow);
That's what program initialization is all about. Now let's go back to
the
lpfnWndProc
member of
WNDCLASSEX
structure.
As you've noticed from the function's Hungarian naming, this member is
a "long pointer to a function that's called window procedure". We've
assigned this member to
wndProc
function, so we will
declare a function named
wndProc
in our program whose
job is to process window messages.
For now, it is time to gear from program initialization to the
message loop, the third and the last part of a WinMain
function.
Every Windows program has got a message loop to let it continually
poll for messages. In this way every Windows program stays in touch with
the supply of messages that are vital to the proper operation of a Windows program.
Here's the typical message loop:
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
This loop runs continuously until the reception of
WM_QUIT
message. As
soon
as this message is received, a program breaks its message loop and terminates.
Suffice to say that each iteration through the above-mentioned loop means a
reception of a message, either from the hardware event queue or from the application message queue.
The message loop, as you have noticed, consists of three main APIs: GetMessage
that
pulls messages into our program, TranslateMessage
that translates each keystroke
messages to a proper character values and places it into the application's private message
queue as WM_CHAR
message, and the final API, DispatchMessage
that pushes the
retrieved message (msg) into the window procedure for processing.
With this information in hand, we are now ready to discover the functionality of a
window procedure. You recall that we already assigned the lpfnWndProc
member
of WNDCLASSEX
structure into wndProc. Now let's see what wndProc looks like:
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;
}
Where
LRESULT
is declared as the
long
data type, and
CALLBACK
specifies the
__stdcall
calling convention.
Our window procedure is called whenever a message is generated. We could handle the
message if this suites us or we could skip the message and pass it to the DefWindowProc
API.
DefWindowProc
does whatever is necessary to make our window box work.
Microsoft provided us with the source code of this procedure in Windows SDK. By the way,
upon the reception of WM_DESTROY
, we call PostQuitMessage
API that handles the
application's shutdown process.
The ABC of an MFC application
An MFC application encapsulates most of those Win32 APIs in a manner that has simplified every
programmer's life. We all have certainly read many books and articles about the entry point of
an MFC application. They all claim (more or less) that the entry point of an MFC application
is the
InitInstance
function of the program.
This brings a new question to the life: Where the heck WinMain
function resides
if the InitInstance
function is the entry point?
To clarify what goes beyond the scene, I would like you to create a sample program
to explore and navigate through the details that has been hidden from an MFC programmer.
To create the sample program, launch MSVC++ 6.0. From the file menu, select the new item.
With the project tab selected, highlight MFC App Wizard (.exe) and in the project name
edit box, type "sdisample" and press the Ok button. Select Single document and press
the Finish button. Press Ok to let the wizard create the application's framework for you.
At the first glance, you will find out that the application contains the following classes:
CAboutDlg
CMainFrame
CSdiSampleApp
CSdiSampleDoc
CSdiSampleView
And one of those classes is the one called CSdiSampleApp
that's derived from
CWinApp
class:
The above-mentioned class has got a member function called InitInstance
that is
claimed to be the entry point of our application. The question is why this function
is called the entry point of an MFC application? The answer to this question lies behind
the MFC architecture. Dumb, huh?
We said that every Windows program contains two functions, WinMain
and
wndProc
. Now we announce that the same goes to MFC applications. They have
got the WinMain
function as well as a wndProc
too.
Launch your program by pressing the F10 key. You'll soon found that the execution of
the program is paused at the following function:
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
Take a precise look! The function has got those familiar parameters, the same as our
WinMain
function. But what are those nasty things declared before
WINAPI
?
extern "C"
instructs the compiler how to compile
this function and
WINAPI
is defined as follows:
#define WINAPI __stdcall
and what about
_tWinMain?
It's declared as follows:
#define _tWinMain WinMain
Wow... We are again at the same position! Here's our
WinMain
function discussed earlier:
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
and here's the MFC generated one:
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
Compare those two functions and you will see they are amazingly the same. Neat, huh?
Now let's have a closer look to the
_tWinMain
function and it's implementation:
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
As you see, it calls and returns the
AfxWinMain
function,
the Application Framework's
WinMain
function! But what it is
and how it is implemented? Here is the important part of
AfxWinMain
function:
int AFXAPI AfxWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
goto InitFailure;
if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;
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();
}
You see, it first makes a call to
AfxWinInit
, a function that
calls
MFCO42D.DLL (if the program runs in debug version) as well as initializing
member variables to appropriate execution filename, help file and the .ini one.
(Although it does a little bit more, but we are not going to re-design MFC here,
so we skip it simply).
Then it calls InitApplication
function, the one that we could easily
override in our CSdiSampleApp
class using the class wizard. This function is
used to perform one-time initialization of an application. And then, some lines later,
it makes a call to the InitInstance
function, the one we claimed is the entry
point of an MFC application. Then the AfxWinMain
function makes a call to the
Run function! This function calls the appropriate CWinApp::Run()
function, and
this one in turn makes a call to the CWinThread::Run()
one.
The message loop is provided in this method, that's implemented as follows:
int CWinThread::Run()
{
ASSERT_VALID(this);
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
for (;;)
{
while (bIdle && !::PeekMessage
(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
{
if (!OnIdle(lIdleCount++))
bIdle = FALSE;
}
do
{
if (!PumpMessage())
return ExitInstance();
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur,
NULL, NULL, NULL, PM_NOREMOVE));
}
ASSERT(FALSE);
}
As you see, this function contains a loop, and this loop will break if and only if
the
WM_QUIT
message is received and therefore, has got the functionality
of our previously-mentioned message loop:
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
However, there are some differences between these two loops. First and the most
important one is that
CWinThread::Run()
is developed to
call an
OnIdle
member function of the application while the program has
got nothing to process. Second, it makes a call to
ExitInstance
method after
reception of
WM_QUIT
message and before exiting the program. So, an MFC programmer
can do whatever is necessary by overriding
ExitInstance
method, being sure
that his code will be called whenever the program is going to be dropped.
PumpMessage
, on the other hand, encapsulates
TranslateMessage
and DispatchMessage
API internally.
Now, we have got our own message loop in place for an MFC application.
However, we have not mentioned anything about wndProc
of an MFC
application, yet! This function, IMHO, is the beauty of MFC, that I will try to
cover it on second part of this article.
Hopping these Win32 vs. MFC series of my articles opens new doors to you.
I would love to hear your comments, questions and/or suggestions about this article.
You can reach the author via his email address,
mehdi_mousavi@hotmail.com