Click here to Skip to main content
Click here to Skip to main content

Win32 vs. MFC - Part I

, 2 Aug 2001 CPOL
Rate this:
Please Sign up or sign in to vote.
Discusses MFC architecture

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)
{
    // call shared/exported WinMain
    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)
{
    // call shared/exported WinMain
    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)
{
    //Snipped 
    // 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(); 

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

    // for tracking the idle time state
    BOOL bIdle = TRUE;
    LONG lIdleCount = 0;

    // acquire and dispatch messages 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 while 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 (IsIdleMessage(&m_msgCur))
            {
                bIdle = TRUE;
                lIdleCount = 0;
            }
        } while (::PeekMessage(&m_msgCur, 
            NULL, NULL, NULL, PM_NOREMOVE));
    }

    ASSERT(FALSE); // not reachable
}
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

License

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

Share

About the Author

Mehdi Mousavi
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 way of explanation PinmemberYogender Solanki9-Aug-14 0:35 
QuestionBeautiful - I am enlightened PinmemberBillyGoatGruff25-Apr-14 3:31 
GeneralMy Vote of 5 PinmemberMehul Donga23-Oct-13 21:28 
GeneralMy vote of 5 Pinmemberfantasywindy6-Mar-13 22:26 
GeneralMy vote of 4 Pinmembershetkarabhijeet4-Jan-13 20:06 
GeneralMy vote of 4 Pinmemberali_reza_zareian28-Nov-12 0:36 
QuestionExcellent Base for Win32 Programming. PinmemberSandipPune11-Jul-12 22:10 
GeneralMy vote of 5 PinmemberTheHelenLee11-May-11 23:35 
General"Document/View architecture in an MFC SDI application" Pinmembermoulikirankumar21-Mar-11 22:38 
GeneralMy vote of 5 Pinmembermoulikirankumar21-Mar-11 22:35 
GeneralMy vote of 5 PinmemberAnand Todkar31-Aug-10 18:39 
GeneralCombo Box Pinmemberdilara semerci9-Aug-10 3:50 
Generalrun method PinmemberMember 282329730-Jun-10 22:47 
Questionwhat means, int APIENTRY WinMain? Pinmemberdrenato28-Mar-09 13:19 
AnswerRe: what means, int APIENTRY WinMain? PinmemberMehdi Mousavi29-Mar-09 1:49 
GeneralVery well job Pinmemberyosmany5-Dec-08 14:48 
Questionhow to change to vc++ PinmemberAbdulwahid Murshid31-May-08 6:08 
QuestionIs there any Part II as well?? Pinmemberabhishektaneja3-May-07 21:56 
AnswerRe: Is there any Part II as well?? PinmemberMehdi Mousavi8-May-07 22:01 
Generalabout the wndclass Pinmemberwzh1983122115-Dec-06 3:11 
Generalabout the message Pinmemberwzh1983122113-Dec-06 14:44 
Questionwhat the heck is __stdcall about? Pinmembersruti_p25-May-06 0:22 
AnswerRe: what the heck is __stdcall about? Pinmember\r9-Jul-06 4:48 
GeneralEVT3 --> VS2005b2 conversion PinmemberMike Landis18-Oct-05 20:17 
GeneralGetting command line parameters in MFC PinmemberSibilant25-Jul-04 16:07 
GeneralRe: Getting command line parameters in MFC PinmemberMehdi Mousavi26-Jul-04 10:48 
GeneralRe: Getting command line parameters in MFC PinmemberSibilant27-Jul-04 13:11 
GeneralMFC for TAPI PinmemberJoe Momberg1-Sep-03 18:56 
GeneralRe: MFC for TAPI PinmemberRodLima13-Sep-03 5:47 
GeneralRe: MFC for TAPI Pinmemberykutanoor14-Apr-04 20:59 
GeneralRe: MFC for TAPI PinmemberMehdi Mousavi6-Jan-05 12:48 
GeneralReading files with CFile Pinmembervanesa21-Aug-03 22:11 
GeneralRe: Reading files with CFile PinmemberMehdi Mousavi6-Jan-05 12:59 
GeneralPumpMessage PinsussAnonymous17-Aug-02 1:02 
Generalrun only one copy of application PinsussPavel Karev21-Jul-02 22:17 
GeneralRe: run only one copy of application PinmemberMehdi Mousavi23-Jul-02 20:42 
GeneralThanks a lot PinsussPavel Karev25-Jul-02 19:47 
GeneralRe: run only one copy of application Pinmemberjan larsen25-Jul-02 4:05 
GeneralThanks a lot PinsussPavel Karev25-Jul-02 19:46 
GeneralLame PinmemberAnonymous1-May-02 19:07 
Generalflickering into image paint Pinmemberuseradmin26-Feb-02 19:36 
GeneralRe: flickering into image paint PinmemberMehdi Mousavi27-Feb-02 10:39 
GeneralRe: flickering into image paint PinmemberDavid Stidolph27-Sep-02 9:53 
Generalas you probably know... PinmemberProtoform X28-Dec-01 15:34 
GeneralRe: as you probably know... PinmemberTim Smith28-Dec-01 16:04 
QuestionMFCO42D.DLL? PinmemberAnonymous9-Oct-01 8:53 
AnswerRe: MFCO42D.DLL? PinmemberMehdi Mousavi19-Oct-01 8:09 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 3 Aug 2001
Article Copyright 2001 by Mehdi Mousavi
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid