Top Banner
How VC++ Programs Work C++ programs are either generated through AppWizard and ClassWizard or are hand coded by the programmer. It is often found that programmers are able to build reasonably complicated applications without a clear understanding of how the program works. Similarly, many programmers are able to tackle various Windows messages using the message maps. However, when it comes to knowing how a message gets transformed into a call to a message handler there is utter confusion. We think that it is imperative to understand the working of the VC++ program and the message maps before we can venture into advanced VC++ topics. Towards this end we would write a simple program that displays a V 1
28

Yashwant Kanitker - VC++, COM and Beyond

Apr 11, 2015

Download

Documents

api-3708828
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Yashwant Kanitker - VC++, COM and Beyond

How VC++

ProgramsWork

C++ programs are either generated through AppWizard and ClassWizard or are hand coded by the programmer. It is often found that programmers are able to build reasonably complicated applications without a clear understanding of how the program works.

Similarly, many programmers are able to tackle various Windows messages using the message maps. However, when it comes to knowing how a message gets transformed into a call to a message handler there is utter confusion. We think that it is imperative to understand the working of the VC++ program and the message maps before we can venture into advanced VC++ topics. Towards this end we would write a simple program that displays a message in a window. Then we would examine in detail the working of this program. Here is the program…

V

Program 1

myapp.h

class myapp : public CWinApp{

public :

BOOL InitInstance( ) ;

1

Page 2: Yashwant Kanitker - VC++, COM and Beyond

2 VC++, COM and Beyond

} ;

myapp.cpp

#include <afxwin.h>#include "myframe.h"#include "myapp.h"

myapp a ;

BOOL myapp::InitInstance( ){

myframe *p ;p = new myframe ;m_pMainWnd = p ;

p -> ShowWindow ( SW_SHOWNORMAL ) ;

return TRUE ;}

myframe.h

class myframe : public CFrameWnd{

public :

myframe( ) ;void OnPaint( ) ;

DECLARE_MESSAGE_MAP( )} ;

myframe.cpp

#include <afxwin.h>#include "myframe.h"

BEGIN_MESSAGE_MAP ( myframe, CFrameWnd )ON_WM_PAINT( )

END_MESSAGE_MAP( )

myframe::myframe( ){

Create ( 0, "Draw" ) ;}

void myframe::OnPaint( ){

CPaintDC d ( this ) ;d.TextOut ( 50, 50, "Hello", 5 ) ;

Page 3: Yashwant Kanitker - VC++, COM and Beyond

Chapter 1: How VC++ Programs Work 3

}

Is it necessary to keep different classes in separate files?

We are not bound by any rule to do so. However, we are bound by tradition. Among C++ programmers it is a tradition to write the class declarations in .h files and class implementations in .cpp files. We too would follow this strategy throughout this book. For small programs this might appear to be an overkill. But in large programs containing numerous classes it is worthwhile to give each class its own .h and .cpp files. The same convention has been followed while storing the files on the CD accompanying this book.

Once we create the .h and .cpp files we must include the .cpp files in the current project using the ‘Project | Add to Project | Files’ menu option.

On execution of this program the window shown in Figure 1-1 gets displayed.

Figure 1-1. A window with a message.

This program has two principal components:

(a) An application object (object of myapp class in our program)(b) A window object (object of myframe class in our program)

Of these, the application object represents the application itself, whereas, the window object represents the application’s window. The primary duty of an application object is to create a window. In our program this has been done in the InitInstance( ) function. The primary job of the

Important Stuff

Page 4: Yashwant Kanitker - VC++, COM and Beyond

4 VC++, COM and Beyond

window object is to process the messages that the window receives. This processing is done through various handler functions. One such handler is the OnPaint( ) function in our program. The application object and the window object can do little by themselves. The two objects work together in tandem to produce a working Windows application. Our program only shows the application and the frame window class. Hence you have to take it on faith that all the other requirements for the program to work are being fulfilled. You must be wondering where exactly do the windows classes get registered, when does the message loop get started, where is WinMain( ), etc. All these activities are happening inside the program (otherwise, it would not run). To understand how the application framework is making these things happen, we must take a peek at what is happening under the hood. So let’s get started…

Application Framework And MFC

Microsoft Foundation Classes (MFC) provides an Application Framework specifically tailored for creating applications for Microsoft’s Windows operating system. To create C++ based object-oriented tools for Windows applications, developers at Microsoft established the AFX group in 1989. AF didn’t sound very catchy, so the letter X was thrown in. The X in AFX doesn’t really mean anything. The first prototype of application framework that the AFX group came out with, turned out to be completely unrelated to Windows. Hence this prototype was scrapped and a new design goal was set up. This time the focus was on creating a simple yet elegant framework for Windows developers using C++. The priority was to allow developers to utilize their existing knowledge of Windows and develop real world applications using C++ and object oriented techniques. In 1992, Microsoft released MFC version 1.2 containing around 60 C++ classes for Windows application development. There were also general-purpose classes for time, string, collections, files, persistent storage, memory management, exceptions and diagnostics. To simplify Windows development, the AFX team grouped the AFX functions into logical units. While doing this they took advantage of C++ classes and inheritance to make development easier. For example, all API functions related to device context were gathered in a CDC class. Similarly, all functions related to a window were pooled into a CWnd class. However, instead of a whole new abstraction for Windows development, MFC 1.0 turned out to be little more than a thin wrapper around the Windows API.

Spurred by the acclaim received by MFC 1.0 and the suggestions received from developers, the AFX group added two features to MFC 2.0:

(a) High-level architecture support (b) Pre-built components

This simplified Windows development and now the developers could easily build applications that contained popular features like toolbars, status bars, print-preview and context-sensitive help. The MFC architecture now provided hooks for the developers to customize and extend the generic application. However, MFC 2.0 didn’t force the user to use the high-level abstraction. The developers could still access the framework at lower level. If required, the developers could access Windows API directly. MFC could hide away the messy details of Windows programming such as registering window classes, window and dialog procedures and message routing. The primary feature of MFC 2.0 was the new application architecture and the high-level abstraction. The application architecture classes provided standard implementations of common Windows features, such as documents and views, printing and command processing. The high-level

Page 5: Yashwant Kanitker - VC++, COM and Beyond

Chapter 1: How VC++ Programs Work 5

abstractions provided a set of pre-built components for common user interface elements, such as toolbars and status bars. MFC 2.5 was released along with Microsoft Visual C++ 1.5. In this version of MFC, support was provided for OLE and ODBC. This was followed by MFC 3.0, which was released in 1994. This version made a major improvement in thread safety. Messaging Application Interface (MAPI) and WinSock support was added in version 3.1 in 1995. In late 1995, MFC 4.0 was released along with VC++ 4.0. This version greatly enhanced the development environment with major focus on reusability of code. As of this writing we are using MFC 6.0. So much for the history of MFC. Let us now get back to our program from the last section.

What, No WinMain( )?

The way a C++ under DOS/Unix program begins its execution with main( ), similarly a C++ under Windows program begins its execution from WinMain( ). However, surprisingly our program doesn’t contain WinMain( ). While writing a Visual C++ program we are not required to write WinMain( ). It is linked into our application by the VC++ compiler. You will find WinMain( ) function in the file APPMODULE.CPP. Here is how it looks like…

_tWinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )

{AfxWinMain ( hInstance, hPrevInstance, lpCmdLine, nCmdShow ) ;

}

As you can see from the code, WinMain( ) delegates the processing to a function called AfxWinMain( ). This function is present in WINMAIN.CPP. Here is the pseudo-code of AfxWinMain( ).

int AFXAPI AfxWinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )

{int nReturnCode = -1 ;

CWinApp* pApp = AfxGetApp( ) ;

// AFX internal initializationAfxWinInit ( hInstance, hPrevInstance, lpCmdLine, nCmdShow ) ;

// App global initializations (rare)pApp -> InitApplication( ) ;

// Perform specific initializationsif ( !pApp -> InitInstance( ) ){

if ( pApp -> m_pMainWnd != NULL ){

pApp -> m_pMainWnd -> DestoryWindow( ) ;

Page 6: Yashwant Kanitker - VC++, COM and Beyond

6 VC++, COM and Beyond

}nReturncode = pApp -> ExitInstance( ) ;

goto InitFailure ;}

nRetturnCode = pApp -> Run( ) ;

InitFailure :AfxWinTerm( ) ;

return nReturnCode ;}

Before we trace how the control flows in WinMain( ), let’s look at some important structures used by the framework.

MFC State Information

It’s often useful for an application to maintain certain information throughout the life of the program. MFC too maintains a great deal of information internally including main window handles, resources, instance handle etc. Most of the information is stored in a structure called AFX_MODULE_STATE. There is one important element in this structure—a pointer to a CWinApp object (CWinApp *m_pCurrentWinApp).

We would not look at other elements of this structure. However, now you know AFX_MODULE_STATE is a place to look if you need access to some bit-state information about your application. Some of these members are even accessible through functions like AfxGetInstanceHandle( ) and AfxGetApp( ). Warning! The structure within AFX_MODULE_STATE may change in future. Hence, you should exercise caution while using these structures specially if you are assigning values to the fields within them. Let’s now see how this state information is utilized during execution of WinMain( ).

Back To WinMain( )

Let’s now take a closer look at AfxWinMain( ). Right at the beginning it calls a function, AfxGetApp( ). This function returns the address of the global application object (in our program we have called this object a). This address is assigned to a CWinApp pointer trough the statement,

CWinApp *pApp = AfxGetApp( ) ;

This leads to two questions:

(a) How can we get the address of the application object when it has not even been created?

(b) From where does the AfxGetApp( ) function gets to know the address of the application object?

Page 7: Yashwant Kanitker - VC++, COM and Beyond

Chapter 1: How VC++ Programs Work 7

The answer to the first question is simple. C++ programs construct their global objects before anything else is doneeven before WinMain( ) is called.

Now the answer to the second question. When we build the object a through the statement,

myapp a ;

the constructor of myapp gets called. This in turn calls the base class’s (CWinApp’s) constructor. In this constructor if we use this it would contain the address of the global application object. This address is stored by the constructor in AFX_MODULE_STATE’s data member, m_pCurrentWinApp. You may recall that the type of m_pCurrentWinApp is CWinApp *. Thus, what the constructor has managed to do is—set up a pointer to base class’s object with the address of the derived class object. When we call AfxGetApp( ) it retrieves the address of the global application object from AFX_ MODULE_STATE’s m_pCurrentWinApp data member.

AfxWinInit( )

Once the global application object’s address is obtained, AfxWinMain( ) proceeds to call AfxWinInit( ) to initialize the framework. This function takes the same four parameters—the current instance handle, the previous instance handle, the command-line parameters and the show command. AfxWinInit( ) sets these parameters into the member variables of the CWinApp class.

A function that begins with the word Afx does not belong to any MFC class. Strangely Afx stands for Application Framework.

The pointer retrieved using AfxGetApp( ) is now used by AfxWinMain( ) to call the CWinApp::InitApplication( ) function.

InitApplication( )

Under 16-bit Windows there was a difference between the first instance of the application and the subsequent ones (which could be determined by examining hPrevInstance). Hence under 16-bit Windows, initializations that were necessary for the entire application used to get performed in InitApplication( ), whereas initializations which were necessary for every new instance of the application used to get performed in InitInstance( ). However, when Windows moved to 32 bits the difference between various instances of the same application vanished. Hence in 32-bit Windows programs all initializations take place in InitInstance( ).

Important Stuff

Page 8: Yashwant Kanitker - VC++, COM and Beyond

8 VC++, COM and Beyond

InitInstance( )

When a program begins to execute it is necessary to perform certain initializations. These are usually done in the InitInstance( ) function. The MFC class CWinApp, as well as its derived version, myapp, contains the InitInstance( ) function. This function has been defined as virtual in CWinApp class. It is called from AfxWinMain( ) through the statement,

pApp -> InitInstance( )

Since pApp is a pointer to the base class object and it holds the address of the derived class object, the call transfers to the derived class implementation of the InitInstance( ) function.

In fact, InitInstance( ) of CWinApp simply returns TRUE. Hence it is necessary for us to override it in the myapp class. Otherwise, the application would simply terminate without even showing a window on the screen.

Let us now understand the stuff inside the myapp::InitInstance( ) function.

The InitInstance( ) Stuff

The myapp::InitInstance( ) function is reproduced below:

BOOL myapp::InitInstance( ){

myframe *p ;p = new myframe ;m_pMainWnd = p ;p -> ShowWindow ( SW_SHOWNORMAL ) ;return TRUE ;

}

When the statement,

p = new myframe ;

is executed, space is allocated for a myframe object on the heap and the myframe class’s constructor is called. In the constructor we have created a frame window by calling CFrameWnd::Create( ). The first parameter passed to Create( ) signifies the window class name. By passing a value 0 for it we are indicating that a default window class be used.The second parameter passed to it is the title of the window. CFrameWnd::Create( ) in turn calls the CreateWindowEx( ) API function.

But then did we not learn in C/SDK programming that before a window can be created a window class must be registered with the OS. This job is done by the PreCreateWindow( ) function which is called by CFrameWnd::Create( ) before calling the ::CreateWind-owEx( ) function.

Page 9: Yashwant Kanitker - VC++, COM and Beyond

Chapter 1: How VC++ Programs Work 9

Once the window is created and the control comes back from the myframe constructor we display the window on the screen by calling CFrameWnd::ShowWindow( ) function. Just before the control goes out of InitInstance( ) we have initialized m_pMainWnd to the myframe object’s address stored in p. Here m_pMainWnd is a public data member of the CWinApp class. It is necessary to initialize m_pMainWnd because other functions that may want to access the frame window object would be able to do so using m_pMainWnd. m_pMainWnd can be accessed through the expression,

AfxGetApp( ) -> m_pMainWnd ;

Now that the window is up and kicking it is time to interact with it. This is achieved through the CWinApp::Run( ) function.

Is it necessary to create the myframe object on the heap?

Yes. Had it been created using the statement,

myframe p ;

we would have still been able to store the address of the myframe object in m_pMainWnd through the statement,

m_pMainWnd =&p ;

However, this would be suicidal because when the control goes out of InitInstance( ) the object would die; whereas, m_pMainWnd would continue to hold the address of the location where the object was present. A classic case of a dangling pointer.

MFC’s Message Pump

The GetMessage( )..DispatchMessage( ) loop that we use in C/SDK programming is implemented in MFC by CWinApp::Run( ) function.

However, MFC handles message routing a bit differently. Instead of a huge switch statement to filter out a window’s messages, MFC uses Message Maps. It is difficult to understand how message maps work. We would soon see their internal working.

Important Stuff

Page 10: Yashwant Kanitker - VC++, COM and Beyond

10 VC++, COM and Beyond

GetMessage( )..DispatchMessage( ) loop implemented by the Run( ) function gets terminated when a WM_QUIT is picked up from the message queue.

ExitInstance( ) And AfxWinTerm( )

The way in InitInstance( ) the instance-initializations are done, similarly, ExitInstance( ) is the place to perform shutdown and cleanup. The ExitInstance( ) function is called only if InitInstance( ) returns a FALSE. On the other hand, AfxWinTerm( ) function is called every time the application is about to terminate. This function unregisters the window classes.

The ExitInstance( ) function has been declared as virtual in CWinApp. Hence if you want to perform some specific clean up activity you can do so by overriding it in the myapp class.

The essence of the working of the VC++ program that we discussed in the last few pages is caught in Figure 1-2.

Global application object is created

CWinApp’s constructor is called

m_pCurrentWinApp is assigned the address of the global application object

Execution begins with WinMain( )

WinMain( ) calls AfxWinMain( )

AfxGetApp( ) gets the address of the global application object from m_pCurrentWinApp

AfxWinInit( ) copies hInstance, nCmdShow, etc.to the data members of the application object

AfxWinMain( ) calls CWinApp::InitApplication( )

AfxWinMain( ) calls InitInstance( ) of myapp class

Yes No

Constructor of myframe object gets called

From the constructor CFrameWnd::Create( ) gets called

CFrameWnd::Create( ) calls PreCreateWindow( )

CFrameWnd::Create( ) calls ::CreateWindowEx( ) API function

myframe object is created

Control returns from the constructor

m_pMainWnd is intialised to myframe object’s address

CFrameWnd::ShowWindow( ) displays the window

If InitInstance( ) returns TRUE

A BAfxWinMain( ) calls ExitInstance( )

B

AfxWinMain( ) and WinMain( ) terminates

AfxWinMain( ) calls AfxWinTerm( )

This loop terminates when GetMessage( ) retrieves WM_QUIT

A

AfxWinMain( ) calls Run( ) function

GetMessage( )..DispatchMessage( ) loop is executed

Control returns back from Run( ) to AfxWinMain( )

AfxWinMain( ) calls AfxWinTerm( )

AfxWinMain( ) and WinMain( ) terminates

Page 11: Yashwant Kanitker - VC++, COM and Beyond

Chapter 1: How VC++ Programs Work 11

Figure 1-2. Working of a VC++ program.

That brings us to the end of a long and convoluted journey that began with the creation of global application object. Let us now move to another intriguing concept called Message Maps.

Message Maps Under The Hood

Be it a simple dialog based application or be it a Doc/View application, message maps are part and parcel of any MFC program. Using message maps in a program is one of the simplest things to do in MFC programming. However, understanding how message maps work under the hood is a totally different cup of tea. It is important for a MFC power programmer to understand how a WM_PAINT message gets converted into a call to the OnPaint( ) handler. Before we understand this it would be worthwhile to find out why should one use the message maps at all.

Why Message Maps

Instead of using message maps we can think of using virtual functions to convert a message into a call to an appropriate handler. To understand how virtual functions can be used, look at the following code segment:

class CFrameWnd{

virtual void OnPaint( ){}

} ;

class myframe : public CFrameWnd{

myframe( ){

Create ( 0, "Draw" ) ;}

Page 12: Yashwant Kanitker - VC++, COM and Beyond

12 VC++, COM and Beyond

void OnPaint( ){}

} ;

class myapp : public CWinApp{

public :

BOOL InitInstance( ){

myframe *p ;p = new myframe ;m_pMainWnd = p ;

p -> ShowWindow ( SW_SHOWMAXIMIZED ) ;

return TRUE ;}

} ;

myapp a ;

The compiler creates a table called VTABLE for each class that contains virtual functions and for the classes derived from it. The compiler places the addresses of the virtual functions for the particular class in the VTABLE. It you don’t redefine a function that was declared virtual in the base class, the compiler uses the address of the base class version in the derived class’s VTABLE. When objects of base class or derived class are created the compiler secretly places a pointer called vpointer (abbreviated as vptr) in the object. This pointer points to the class’s VTABLE.

The vptr is placed at the beginning of the object and is initialized to point to the starting address of its class’s VTABLE. This initialization is done in the constructor. All of this—setting up the VTABLE for each class, initializing the vptr, inserting the code for the virtual function call—happens automatically. The VTABLES for the two classes CFrameWnd and myframe are shown in Figure 1-3.

Figure 1-3. Virtual Tables.

In the 16-bit version of MFC, the MFC window classes were registered with AfxWndProc( ) as the message handler. In this function all the messages used to get handled. Since the 32-bit version of MFC, instead of AfxWndProc( ) the MFC window classes are registered with DefWindowProc( ) as a window procedure. Even now all messages are ultimately routed to

VTABLE of CFrameWnd

&CFrameWnd::OnPaint

VTABLE of myframe

&myframe::OnPaint

Page 13: Yashwant Kanitker - VC++, COM and Beyond

Chapter 1: How VC++ Programs Work 13

AfxWndProc( ) from where they are dispatched to various CWnd-derived objects. Why all messages still end up in AfxWndProc( ) has something to do with MFCs message hook mechanism. Exploring this mechanism is beyond the scope of this book.

Had a virtual function been used for converting a message into a call to the appropriate handler, the window procedure would have looked like this.

CFrameWnd *p ;p = AfxGetApp( ) -> m_pMainWnd ;p -> OnPaint( ) ;

Here, AfxGetApp( ) fetches the address of the global application object (created from class myapp). m_pMainWnd is a public data member of the CWinApp class. This data member contains the address of the myframe object assuming that the window is built using the myframe class. In the statement,

p = AfxGetApp( ) -> m_pMainWnd ;

we are storing the address of a derived class object in a pointer to the base class object. When we call the OnPaint( ) handler through the statement p -> OnPaint( ) it must be determined whether the OnPaint( ) handler of CFrameWnd class or one in the myframe class should get called. To determine this, contents of p are used. p contains address of the myframe object. First two bytes starting at this address contain the vptr. The vptr points to the VTABLE belonging to myframe class. From the VTABLE the address of myframe::OnPaint( ) is obtained and the control is transferred to this address. Thus, it is the derived class’s (myframe’s) OnPaint( ) which gets called. Had OnPaint( ) not been declared as virtual in CFrameWnd class then through

p -> OnPaint ( ) ;

the CFrameWnd::OnPaint( ) would have been called.

This in effect means that for every possible message that CFrameWnd object is going to receive there must be a virtual function in it. Otherwise we would never be able to override it in a class derived from CFrameWnd. This would lead to a huge virtual table. Message maps is MFC’s way of avoiding these lengthy virtual tables.

Another difficulty with virtual tables arises in case of user-defined messages. Suppose we write a handler, say fun( ) in the myframe class. And now to call it if we say p -> fun( ), an error will result. This is because fun( ) has not been defined as virtual in the CFrameWnd class. The difficulty is we do not have the source code of the CFrameWnd class hence cannot make the virtual declaration. In effect it means that we cannot tackle user-defined messages through the mechanism of virtual functions.

Let us now understand the mechanism of message maps. Any class derived from CCmdTarget can contain a message map. What MFC does internally to implement a message map is hidden

Page 14: Yashwant Kanitker - VC++, COM and Beyond

14 VC++, COM and Beyond

behind some rather complex macros. To understand the message map mechanism consider the following small code snippet:

class myframe : public CFrameWnd{

void OnPaint( ){}DECLARE_MESSAGE_MAP( )

} ;BEGIN_MESSAGE_MAP( )

ON_WM_PAINT( )END_MESSAGE_MAP( )

The macros DECLARE_MESSAGE_MAP( ), BEGIN_MESSA-GE_MAP( ) and END_MESSAGE_MAP( ) have been defined in the file ‘afxwin.h’. During preprocessing the DECLARE_MESSA-GE_MAP( ) macro adds three members to the class declaration:

(a) A private array of structures called _messageEntries[ ]. Each element of this array is a structure called AFX_MSGMAP_ENTRY. In addition to other elements, this structure contains a message id and a pointer to a function that should get called when a message with this id arrives.

(b) A structure called messageMap. This structure contains a pointer to class’s _messageEntries[ ] array and a pointer to base class’s messageMap structure.

(c) A virtual function called GetMessageMap( ) that returns messageMap structure's address.

Note that the _messageEntries[ ] array and the messageMap structure are static members of the class. This means there is one _messageEntries[ ] array and one messageMap structure for all objects of a class. The BEGIN_MESSAGE_MAP( ) macro contains the implementation of the GetMessageMap( ) function and the code to initialize the messageMap structure. The macros that appear between BEGIN_MESSAGE_MAP( ) and END_ME-SSAGE_MAP( ) fill in the _messageEntries[ ] array. Finally, END_MESSAGE_MAP( ) marks the end of the _messageEntr-ies[ ] array with a NULL entry. Figure on the following page shows all these expansions at one glance.

Terminates _messageEntries[ ] array

Fills _messageEntries[ ] array

Initializes messageMap structure

Ptr to myframe’s _messageEntries[ ] arrayPtr to CWnd’s messageMap structureGetMessageMap( ) function

Page 15: Yashwant Kanitker - VC++, COM and Beyond

Chapter 1: How VC++ Programs Work 15

class myframe : public CWnd

{void OnPaint( ) {}DECLARE_MESSAGE_MAP( )

} ;

BEGIN_MESSAGE_MAP( )ON_WM_PAINT( )

END_MESSAGE_MAP( )

With this infrastructure in place let us now understand how a message passed to the application window gets converted into a call to the appropriate message handler.

Suppose a message, say WM_PAINT, gets posted in the message queue. This message would be retrieved by the GetMessage( ) function and would be dispatched to the AfxWndProc( ) function by the DispatchMessage( ) function. Inside the AfxWndProc( ) function, the framework retrieves the C++ object associated with the window handle using CWnd::FromHandlePermanent( ) function. AfxWndProc( ) then calls that object's (the one retrieved using FromHandlePermanent( )) WindowProc( ) function. The WindowProc( ) function gets the address of myframe's messageMap structure. As discussed earlier, this structure contains the address of _messageEntries[ ] array. The id of the message picked up from the message queue is now searched in this array. If the entry is found then the handler corresponding to it is immediately called (as you may recollect, the _messageEntries[ ] contains message ids and the names of their handlers). If the message id is not found in the _messageEntries[ ] array of the myframe class then the _messageEntries[ ] array of the myframe’s base class is searched. But how can we reach the _messageEntries[ ] array of base class? This is achieved using the pointer to the base class’s messageMap structure, which is stored in myframe’s messageMap structure. If the base class doesn't have the handler for the message the framework ascends another level and consults base class’s base class, systematically working its way up the inheritance chain until it finds the message handler or it has reached the father of all classes. If it still cannot find the message handler then the framework passes the message to Windows for default processing. This entire process is shown schematically in the following figure.

Page 16: Yashwant Kanitker - VC++, COM and Beyond

16 VC++, COM and Beyond

Figure 1-4. Working of message maps.

The MFC's message mapping mechanism amounts to an efficient way of connecting messages to message handlers without using virtual functions. In contrast to virtual tables the amount of memory used by message maps is proportional to the number of message entries it contains. In short, message maps is the way of connecting messages to message handlers.

Are Virtual Functions Still Required

We saw in the last section that to avoid lengthy virtual tables for each class in a class hierarchy, MFC uses Message Maps. However, MFC still makes use of the virtual function mechanism. When the framework wants that the programmer should get a chance to implement his ideas before framework does some specific job, it uses the virtual function mechanism.

For example, to create a window we call the function CFrame-Wnd::Create( ). This function in turn calls PreCreateWindow( ) followed by ::CreateWindowEx( ). In CFrameWnd::Create( ) a CREATESTRUCT structure is created and its elements are set up. If we want to modify some elements of the CREATESTRUCT structure (window style, for example) we can provide our implementation of PreCreateWindow( ). Now there are two PreCreateWindow( )’s—one in CFrameWnd and another in our CFrameWnd-derived class. Since CFrameWnd::PreCreateWin-dow( ) has been declared as virtual, it is our PreCreateWindow( ) that gets called. If we so desire we can call the base class implementation of PreCreateWindow( ) from our implementation. When the control reaches our implementation of PreCreateWindow( ) we can change the window style by manipulating one of the elements of the CREATESTRUCT structure. This modified structure is lastly passed to ::CreateWindowEx( ) to create the window.

GetMessage( ) … DespatchMessage( )

AfxWndProc( )

AFX finds C++ object associated with window handle using FromHandlePermanent( )

AfxWndProc( ) calls object’s WindowProc( ) WindowProc( )

WindowProc( ) calls myframe’s GetMessageMap( )

Using messageMap search _messageEntries[ ] array

Found

Return

Call Default Window Procedure

Call function in myframe or CFrameWnd

No Yes

Page 17: Yashwant Kanitker - VC++, COM and Beyond

Chapter 1: How VC++ Programs Work 17

A doubt may come to your mind—why can’t the same thing be done using message maps instead of virtual functions? Since there is no Windows message like WM_PRECREATEWINDOW, the calls to these functions cannot be managed through message maps. Therefore, the only alternative to implement calls to them is through the virtual function mechanism.

In Chapter 2 you would see a similar mechanism at work when CView::OnPaint( ) calls a function OnDraw( ) which is defined as virtual in the CView class. Since a CView-derived class called myview also contains the implementation of OnDraw( ) it is the myview::OnDraw( ) that gets called.

To help you fix these ideas in your mind I am giving below a program that implements this concept. To keep things simple the program has been developed as a console application. It has been suitably commented. Understand it well because this concept is used by the framework at several places.

#include <iostream.h>#include <string.h>

struct CREATESTRUCT{

char windowtitle[30] ;int width ;int height ;

} ;

void CreateWindowEx ( CREATESTRUCT cs ){

cout << cs.windowtitle << endl ;cout << cs.width << endl ;cout << cs.height << endl ;

}

class CFrameWnd{

public :

Create( ){

CREATESTRUCT cs ;

strcpy ( cs.windowtitle, "Hello" ) ;cs.width = 100 ;cs.height = 80 ;

PreCreateWindow ( cs ) ; // calls myframe::PreCreateWindow( ) CreateWindowEx ( cs ) ; // calls the global CreateWindowEx( )

}

virtual void PreCreateWindow ( CREATESTRUCT &cs ){}

Controls returns from constructor

Page 18: Yashwant Kanitker - VC++, COM and Beyond

18 VC++, COM and Beyond

} ;

class myframe : public CFrameWnd{

public :

myframe( ){

Create( ) ;}

void PreCreateWindow ( CREATESTRUCT &cs ){

strcpy ( cs.windowtitle, "Hi" ) ;}

} ;

main( ){

myframe *p ;p = new myframe ; // allocates memory, calls constructor

}

Page 19: Yashwant Kanitker - VC++, COM and Beyond

Chapter 1: How VC++ Programs Work 19

What are the series of actions that take place when I try to close a window?

When we try to close an application by clicking on the close button, a WM_CLOSE message is sent to our application. If we do not handle this message then it is passed on to the default window procedure. The default window procedure destroys the window by calling the ::DestroyWindow( ) API function. This function places a WM_DESTROY message in the message queue of our application. If we do not handle this message once again the default window procedure gets a shot at it. Following WM_DESTROY, is another message called WM_NCDESTROY. In reaction to this message a handler OnNcDestroy( ) gets called. This handler calls another function—PostNcDestroy( ). It is in this function that the ::PostQuitMessage( ) function is called. This function places a message WM_QUIT in the message queue. When this message is retrieved, the message loop is terminated. This ultimately leads to termination of the application. This procedure is outlined in the figure shown below:

Exercise

[A] State True or False:

(a) Virtual functions permit calling of derived class functions using the base class pointer.

(b) There is common VTABLE for all the objects of the class.

(c) There is one vptr per object.

Important Stuff

WM_CLOSE DefWindowProc( ) DestroyWindow( )

PostQuitMessage( ) WM_QUIT

GetMessage( ) returns 0

WM_DESTROYOnDestroy( )WM_NCDESTROY

OnNcDestroy( )

Page 20: Yashwant Kanitker - VC++, COM and Beyond

20 VC++, COM and Beyond

(d) Virtual functions permit functions from different classes to be executed from the same function call.

(e) In a class hierarchy of several levels if you want a function at any level to be called through a base class pointer then the function must be declared virtual in the base class.

(f) In principle, calls to message handlers for the messages that are received by an application can be implemented using virtual functions.

(g) Message maps consume more memory than virtual tables.

(h) Message maps work faster than virtual tables.

(i) In 32-bit MFC, the registration of window classes is done in the function AfxWndProc( ).

(j) The WinMain( ) function linked into a VC++ program by the framework simply contains a call to AfxWinMain( ) function.

[B] Answer the following:

(a) Why has InitApplication( ) lost its importance in the Win32 environment?

(b) What does the AFX_MODULE_STATE structure contain?

(c) Which mechanism is used to call the InitInstance( ) function?

(d) Why is it necessary to create the frame window object on the heap?

(e) Why is it necessary to initialize the m_pMainWnd data member of CWinApp?

(f) What does the DECLARE_MESSAGE_MAP( ) macro expands into? How would you see this expanded code?

(g) Why is it that for some messages we are required to use the message map mechanism, whereas, for some other we have to use the virtual function mechanism?

(c) Write a program which displays a message ‘Hello’ in the center of the window. Ensure that the window does not have a border.