Topic : MFC Introduction
Author : Prof Devi
Page : << Previous 2  Next >>
Go to page :


are very difficult to track.  But, MFC has several mechanisms to deal with memory leaks.  MFC even offers mechanisms to compare a heap at two different times.   The heap is that portion of a process space where new objects are dynamically created and destroyed(ie new and delete).   By comparing the heap at different times, it is possible to see which objects have been created and destroyed.  As MFC is quite elaborate, we will start with the simplest debugging mechanisms.

The macro TRACE can be used to print trace information when a debug version of your application is running.  The default behavior of TRACE is to send information to the DEBUG pane of the Output Window found in the Visual C++ IDE.  The TRACE macro expands to an empty statement in the release version of your application.   The arguments of the macro TRACE are similar to those passed to the printf function.   The first argument is a format specifier string, and the remaining arguments can be a variable number.  The Box #1 program demonstrates the TRACE Macro.  The TRACE macro's usefulness can not be overstated when dealing with GUI applications that do not have a console window for error checking.

// Box #1: TRACE Macro Example
#include <afxwin.h>
#include <stdio.h>

int main(void)
   {
   if(AfxWinInit(::GetModuleHandle(NULL),NULL,::GetCommandLine(),0)==FALSE)
      {  printf("Unable to init MFC\n");  return 1; }

   TRACE("\n\n\nBeginning Main\n");
   
   int total=0;

   for(int i=1;i<=100;++i)
      {  total+=i;  TRACE("Total=%d  i=%d\n",total,i); }
   
   printf("Total=%d\n",total);  TRACE("Program Done\n");
   
   
   return 0;
   }



Similar to cerr object in C++, and like the TRACE method, the afxDump object can be used with the overloaded left shift operator(<<) to print out information in the Debug Pane of the output window.  The afxDump object is an instance of a class called CDumpContext(discussed in the chapter on CObject).  This object is defined only in the debug version of your application.  If you intend to use it instead of TRACE, you will need to conditionally compile your code with preprocessing guards based on either the symbols _DEBUG or NDEBUG.  The Box #2 program demonstrates the usage of the afxDump object.  The program does exactly the same thing as the Box #1 program.

// Box #2: afxDebug Object

#include <afxwin.h>
#include <stdio.h>

int main(void)
   {
   if(AfxWinInit(::GetModuleHandle(NULL),NULL,::GetCommandLine(),0)==FALSE)
      {  printf("Unable to init MFC\n");  return 1; }

#ifdef _DEBUG
   afxDump<<"\n\n\nBeginning Main\n";
#endif _DEBUG
   
   int total=0;

   for(int i=1;i<=100;++i)
      {  
      total+=i;  
      
#ifdef _DEBUG
      afxDump<<"Total="<<total<<"  i="<< i <<"\n";
#endif _DEBUG
      }
   
   printf("Total=%d\n",total);  TRACE("Program Done\n");

#ifdef _DEBUG
      afxDump<<"Program Done\n";
#endif _DEBUG
   
   
   return 0;
   }



The macro ASSERT is much like the assert function from assert.h.  The MFC ASSERT displays a Popup Dialog Box with the assertion that failed and its location in a file. If you press retry and cancel on the popup dialog and subsequent dialog, you will be able continue debugging from failed assertion.    Like the TRACE MACRO, the debug version of your program, the ASSERT has code for generating the popup dialog box.  In the release version of your program, the ASSERT maps to an empty statement.  The Box #3 program demonstrates the ASSERT macro.

// Box #3: ASSERT Example
#include <afxwin.h>
#include <stdio.h>


int Sum(int a,int b) // a>0 and b>0
   {
   ASSERT(a>0 && b>0);  // Assert that arguments are positive

   return a+b;
   }

int main(void)
   {
   if(AfxWinInit(::GetModuleHandle(NULL),NULL,::GetCommandLine(),0)==FALSE)
      {  printf("Unable to init MFC\n");  return 1; }

   printf("Sum of two +ive numbers\n",Sum(10,300));

   int *p=new int[1000];  ASSERT(p!=NULL);  // Assert that memory was acquired

   delete p;  

   return 0;
   }



Simple memory leaks can be caught with the #define new DEBUG_NEW preprocessing statement.  If any memory leaks are found between the start of this declaration and the declaration #undef new, the memory leak information and the line number of file where the memory leak occurred are displayed in the debug pane of the Output Window.  In the release version of your program, the DEBUG_NEW is defined to be new.    The Box #3 program demonstrates how to use this preprocessing statement.

// Box #3: Finding Memory Leaks in MFC
#include <afxwin.h>
#include <stdio.h>


#define new DEBUG_NEW


int main(void)
   {
   if(AfxWinInit(::GetModuleHandle(NULL),NULL,::GetCommandLine(),0)==FALSE)
      {  printf("Unable to init MFC\n");  return 1; }

   new int[100];  // Memory leak!!!

   return 0;
   }



Memory leak checking can be controlled using the void AfxEnableMemoryTracking(BOOL flag) method.  In the following Box #4 program,   only a small section of code is checked for memory leaks.  The other sections are not checked.

// Box #4: Controlling where memory leak checking is done

#include <afxwin.h>
#include <stdio.h>


#define new DEBUG_NEW


//////////////////////////////////////
int main(void)
   {
   if(AfxWinInit(::GetModuleHandle(NULL),NULL,::GetCommandLine(),0)==FALSE)
      {  perror("Unable to init MFC\n");  return 1; }
   
   AfxEnableMemoryTracking(FALSE);  // Turn Off Memory Leak Checking

   new int;  


   AfxEnableMemoryTracking(TRUE);   // Turn On Memory Leak Checking

   new char[100];

   AfxEnableMemoryTracking(FALSE);  // Turn On Memory Leak Checking
   new float;

   return 0;
   }


Only the section of code that allocates the 100 byte character array will be found as a memory leak.  

More advanced methods for debugging requires a discussion of the class CObject.   This object is discussed in the next chapter.




CObject


Many of the classes in the MFC library are derived directly or indirectly from a base class called CObject.  The CObject class has runtime error checking capabilities.  This error checking is not based on C++ Run Time Type Identification(RTTI)(When MFC first came out, there was no RTTI).  The CObject class also has methods that can be used to dynamically create instance objects on the heap at run time.  It also has  base methods for serialization and deserialization of objects.  Serialization and deserialization can be used to store, retrieve, and transmit objects(by value).

The CObject class can not be instantiated, as the constructor is a protected member of the class.  It can only be inherited.  The virtual member method void AssertValid(void) const can be overridden to perform error checking on the state of the current object.  This method is defined to be a member of the CObject class in the debug version, while the release version of the class has no such member.  If you do intend to override and implement this function, sandwich the declaration and implementation in a conditional compilation based on either symbols _DEBUG or NDEBUG.  Any calls in your code that calls this method should also be conditionally compiled.  CObject's base classes implementation of this method checks whether the current running object is a valid object.  The base method can check it the current run time object does really exist.  It is always good practice to call this base class method whenever you override this method in a derived class.   The Box #1 Example demonstrates an instance of a class called Accum.  The class is used to accumulate the sum of several positive numbers.

// Box #1: AssertValid Example

#include <afxwin.h>
#include <stdio.h>


///////////////////////////////////////
class Accum :public CObject
   {
   int mTotal;
   public:
   ///////////////////////////////////
   Accum() { mTotal; }

   ///////////////////////////////////
   ~Accum() { }
   
   ///////////////////////////////////
   int GetTotal(void)
      { return mTotal; }

   ///////////////////////////////////
   void Reset(void)
      { mTotal=0; }


   ///////////////////////////////////
   void Add(int inc)
      {
      ASSERT(inc>=0);
      mTotal+=inc;
      }

#ifdef _DEBUG
   //////////////////////////////////
   void AssertValid(void) const
      {
      CObject::AssertValid();
      ASSERT(mTotal>=0);
      }

#endif _DEBUG   

   };
///////////////////////////////////////


//////////////////////////////////////
int main(void)
   {
   if(AfxWinInit(::GetModuleHandle(NULL),NULL,::GetCommandLine(),0)==FALSE)
      {  perror("Unable to init MFC\n");  return 1; }

   Accum x;  x.Reset();

   for(int i=1;i<=100;++i)
      {  x.Add(i);  }

#ifdef _DEBUG
   x.AssertValid();
#endif _DEBUG

   printf("Total=%d\n",x.GetTotal());

   return 0;
   }
//////////////////////////////////////




The CObject class contains the method  virtual void Dump( CDumpContext& dc ) const.   This method is only defined in the debug version of your application, and as such, you must use a compiler guard when you call or override this method.  In your overridden method, you can pass information about the current state of your object using the left shit operator(<<).  In most cases, you probably will not call the method directly.  You can call the method void AfxDump(const CObject *obj) passing the address of a CObject derived class.   The AfxDump function calls the Dump method with the argument of the address of afxDump(see chapter on Debugging).   The dumped information can be seen in the debug pane of the output window.   The AfxDump function is only defined in the debug version of your application.  The Box #2 program demonstrates a simple Person Class which is dumped with with the AfxDump method.

// Box #2: AfxDump Example
#include <afxwin.h>
#include <stdio.h>


class Person:public CObject
   {
   const char *mName;
   const char *mAddress;
   const char *mPhone;
   public:
   Person(const char *Name="John Doe"
         ,const char *Address="Unknown Address"
         ,const char *Phone="000-0000")
      {  mName=Name;  mAddress=Address;  mPhone=Phone; }

   ~Person(void){ }


#ifdef _DEBUG
   void Dump(CDumpContext &dc) const    // Write an implementation of Dump
      {
      dc<<"Person Object Dump\n"<<
      "\tName:    "<<mName<<"\n"<<
      "\tAddress: "<<mAddress<<"\n"<<
      "\tPhone  : "<<mPhone<<"\n";
      }

#endif _DEBUG
   };


//////////////////////////////////////
int main(void)
   {
   if(AfxWinInit(::GetModuleHandle(NULL),NULL,::GetCommandLine(),0)==FALSE)
      {  perror("Unable to init MFC\n");  return 1; }


   Person a("Jill"),b("Hill","123 Willow","123-45678");


#ifdef _DEBUG
   AfxDump(&a);   // Dump a
   AfxDump(&b);   // Dump b
#endif _DEBUG

   return 0;
   }

//////////////////////////////////////



If you wish, you can call the Dump method directly, instead of through the AfxDump.   By calling it directly, you can redirect the dumped information to a file or to a console window.  The Box #3 program demonstrate a direct call to Dump.   Notice that the Dump is passed an argument of afxDump.

// Box #3: Calling Dump directly

#include <afxwin.h>
#include <stdio.h>


class Person:public CObject
   {
   const char *mName;
   const char *mAddress;
   const char *mPhone;
   public:
   Person(const char *Name="John Doe"
         ,const char *Address="Unknown Address"
         ,const char *Phone="000-0000")
      {  mName=Name;  mAddress=Address;  mPhone=Phone; }

   ~Person(void){ }


#ifdef _DEBUG
   void Dump(CDumpContext &dc) const
      {
      dc<<"Person Object Dump\n"<<
      "\tName:    "<<mName<<"\n"<<
      "\tAddress: "<<mAddress<<"\n"<<
      "\tPhone  : "<<mPhone<<"\n";
      }

#endif _DEBUG
   };




//////////////////////////////////////
int main(void)
   {
   if(AfxWinInit(::GetModuleHandle(NULL),NULL,::GetCommandLine(),0)==FALSE)
      {


Page : << Previous 2  Next >>