//=--------------------------------------------------------------------------=
// Macros.Cpp
//=--------------------------------------------------------------------------=
// Copyright  1997  Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF 
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A 
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
// Handy macros like the ones we use in the VB code base.
//=--------------------------------------------------------------------------=
#include "pch.h"

#ifdef DEBUG
#include <winuser.h>

// for ASSERT and FAIL
//
SZTHISFILE


//=--------------------------------------------------------------------------=
//  Debug control switches
//=--------------------------------------------------------------------------=
DEFINE_SWITCH(fTraceCtlAllocs);	//  Trace all Heap allocations and frees
				//  fOutputFile should also be on with this switch
DEFINE_SWITCH(fOutputFile);	//  Logs all debug info in file: 
				//    %CurrentDir%\ctldebug.log
DEFINE_SWITCH(fNoLeakAsserts);	//  No Heap memory leak asserts are displayed 
				//    when turned on.



//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//                            !DEBUGGING HEAP MEMORY LEAKS!
// To debug a leak you need to figure out where and when the allocation was made.
// The top of the assert dialog will give you the OCX/DLL causing the leak.
// Goto Project/Build...Settings.
// On the Debug tab, select "additional DLLs"
// Locate and select the OCX/DLL causing the leak.
// Put a breakpoint on the noted line below. 
// Goto Edit...Breakpoints.
// Select the new breakpoint.
// Press 'Condition'
// In the 'Enter number of times to skip before breaking' put the value of nAlloc-1.
// (if the leak was nAlloc=267 then you want to skip the breapoint 266 times, enter 266)
//
// WARNING: Each control (OCX/DLL) will have its own instance of the framewrk, and thus
//	    its own instance of the memory leak implementaion.  Adding a breakpoint 
//	    anywhere in the framewrk will actually add multiple breakpoints - one for 
//	    each control.
//          Go back to Edit...Breakpoints.
//	    Deselect or remove the breakpoints for the OCX's/DLL's not causing leaks
//
// Run your scenario.
// When you hit this breakpoint verify that pvAddress and nByteCount are correct and then
// look down the callstack to see where the allocation was made.
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
void PutBreakPointHere(void * pvAddress, ULONG nByteCount, ULONG  nAlloc, char * szFile, ULONG uLine)
{
  pvAddress=pvAddress;  nAlloc=nAlloc;  nByteCount=nByteCount;
  szFile=szFile;
  uLine=uLine;
  HINSTANCE hInstance = g_hInstance;  //  hInstance of the OCX/DLL calling this breakpoint
  int PutBreakPointOnThisLine = 1;                              // <--- breakpoint here.
} //  PutBreakPointHere



//=--------------------------------------------------------------------------=
//
//  Debug Heap Memory Leak implementations
//

class CAddressNode
{
public:
  void * m_pv;		    //	Address of memory block allocated
  ULONG  m_cb;		    //	Size of allocation in BYTES
  ULONG  m_cAlloc;	    //	Allocation pass count.  
  LPSZ   m_szFile;	    //	Source file where the allocation was made
  ULONG  m_uLine;	    //	Source line number where the allocation was made
  CAddressNode * m_pnNext;  //	Nodes are stored in a linked list

  void * operator new(size_t cb);
  void operator delete(void * pv);

  //  We maintain a freelist to speed up allocation of AddressNodes.
  static CAddressNode * m_pnFreeList;
};


CAddressNode *	m_rInstTable[NUM_INST_TABLE_ENTRIES];  // Hashing table of all instances of 
						       // mem alloc

CAddressNode *	m_pnEnumNode;	      //  Next node for enumerator to return
UINT		m_uEnumIndex;	      //  Current index into m_rInstTable for enumerator
static ULONG	m_cGlobalPassCount;   //  Pass count of allocation.  Common to all heaps    

ULONG m_cCurNumAllocs;		  // Current number of allocations
ULONG m_cNumAllocs;		  // Total number of allocations ever done.
ULONG m_cCurNumBytesAllocated;	  // Current number of bytes allocated.
ULONG m_cNumBytesAllocated;	  // Total bytes allocated.
ULONG m_HWAllocs;		  // High water allocations.
ULONG m_HWBytes;		  // High water bytes.
static ULONG m_OverallCurAlloc;   // These are overall statistics to since we
static ULONG m_OverallCurBytes;   // wouldn't mind the overall high water.
static ULONG m_OverallHWAlloc;
static ULONG m_OverallHWBytes;


//  Forward declarations
VOID AddInst(VOID * pv, DWORD dwBytes, LPSZ szFile, UINT uLine);
VOID DebugInst(ULONG cb);
VOID AnalyzeInst(LPVOID pv);
VOID DumpInst(CAddressNode * pn, LPTSTR lpTypeofAlloc);
LPSTR DumpInstTable(LPSTR lpLeak);
VOID DeleteInst(LPVOID pv);
VOID VerifyHeaderTrailer(CAddressNode * pn);
VOID CheckForLeaks(VOID);
VOID HeapCheck(VOID);
VOID OutputToFile(LPSTR szOutput);
CAddressNode * FindInst(LPVOID pv);
CAddressNode * EnumReset();
CAddressNode * EnumNext();


//  Initialize a header and trailer for all memory to be allocated.
//  Use 8 bytes so it is also compatible with RISC machines.
char * g_szHeader  = "HEADHEAD";
char * g_szTrailer = "END!END!";

#define HEADERSIZE 8	    // # of bytes of block header
			    // 0 ==> no block header signature
#define TRAILERSIZE 8	    // # of bytes of block trailer
			    // 0 ==> no block trailer signature



//=--------------------------------------------------------------------------=
//  CtlHeapAllocImpl:
//	Debug wrapper for HeapAlloc to track memory leaks:
//=--------------------------------------------------------------------------=
LPVOID CtlHeapAllocImpl(
			HANDLE g_hHeap, 
			DWORD dwFlags, 
			DWORD dwBytesRequested, 
			LPSTR lpszFile, 
			UINT line
		       )
{
  LPVOID lpvRet;
  DWORD dwBytes;
  LPTSTR lpTypeofAlloc = "HeapAlloc   ";
  

  //  If someone tries to allocate memory before PROCCESS_ATTATCH (such as in a 
  //  global constructor), do not track it because neither our heap nor our 
  //  hInstance have been initialized yet.
  //
  if (!g_fInitCrit)
    {
    g_flagConstructorAlloc = TRUE;
    return HeapAlloc(g_hHeap, dwFlags, dwBytesRequested);
    }


  //  Increase size to make space for header and trailer signatures
  dwBytes = dwBytesRequested + HEADERSIZE + TRAILERSIZE;

  //  Allocate memory
  lpvRet = HeapAlloc(g_hHeap, dwFlags, dwBytes);
  if (lpvRet)
    {
    //	Initialize memory (non-zero)
    if (!(dwFlags & HEAP_ZERO_MEMORY))
      memset(lpvRet, 0xAF, dwBytes);

    //	Add instance to hash table
    AddInst(lpvRet, dwBytesRequested, lpszFile, line);

    //	Trace allocations if switch is on
    if (FSWITCH(fTraceCtlAllocs))
      {
      CAddressNode *pn = FindInst(lpvRet);
      DumpInst(pn, lpTypeofAlloc);
      }

    //	Advance pointer past header signature.
    lpvRet = (LPVOID) ((char *)lpvRet + HEADERSIZE);
    }
  return lpvRet;
} //  CtlHeapAllocImpl



//=--------------------------------------------------------------------------=
// CtlHeapReAllocImpl:
//   
//=--------------------------------------------------------------------------=
LPVOID CtlHeapReAllocImpl(
			  HANDLE g_hHeap, 
			  DWORD dwFlags, 
			  LPVOID lpvMem, 
			  DWORD dwBytesRequested, 
			  LPSTR lpszFile, 
			  UINT line
			 )
{
  LPVOID lpvRet;
  CAddressNode * pn;
  int byte;
  DWORD cbOffset, dwBytes;
  LPTSTR lpTypeofAlloc = "HeapReAlloc ";

  //  Move pointer to beginning of header
  lpvMem = (LPVOID)((char *)lpvMem - HEADERSIZE);

  //  Find instance in hash table
  pn = FindInst(lpvMem);
  if (!pn)
    {
    FAIL("CtlHeapReAllocImpl - could not find lpvMem in the instance table.  See debug \
          output for more info.");
    AnalyzeInst(lpvMem);
    return 0;
    }

  //  Increase size to make space for header and trailer signatures
  dwBytes = dwBytesRequested + HEADERSIZE + TRAILERSIZE;
  lpvRet = HeapReAlloc(g_hHeap, dwFlags, lpvMem, dwBytes);
  if (lpvRet)
    {
    //	If the reallocation grew, we must intialize new memory
    if (dwBytesRequested > pn->m_cb)
      {
      if (dwFlags & HEAP_ZERO_MEMORY)
        byte = 0x0;
      else
        byte = 0xAF;

      //  Get the byte offset of trailer in the old allocation
      cbOffset = pn->m_cb + HEADERSIZE;
      memset((char *)lpvRet + cbOffset, byte, dwBytes - cbOffset);
      }
    //	Update hash table
    EnterCriticalSection(&g_csHeap);
    DeleteInst(lpvMem);
    AddInst(lpvRet, dwBytesRequested, lpszFile, line);
    LeaveCriticalSection(&g_csHeap); 

    //	Trace Allocations if switch is on
    if (FSWITCH(fTraceCtlAllocs))
      {
      CAddressNode *pn = FindInst(lpvRet);
      DumpInst(pn, lpTypeofAlloc);
      }

    //	Advance pointer past header signature.
    lpvRet = (LPVOID)((char *)lpvRet + HEADERSIZE);
    }
  return lpvRet;
} //  CtlHeapReAllocImpl

			       

//=--------------------------------------------------------------------------=
//  CtlHeapFreeImpl:
//	Debug wrapper for HeapFree
//=--------------------------------------------------------------------------=
BOOL CtlHeapFreeImpl(
		     HANDLE g_hHeap, 
		     DWORD dwFlags,
		     LPVOID lpvMem
		    )
{
  BOOL fRet = FALSE;
  CAddressNode * pn;
  LPTSTR lpTypeofAlloc = "HeapFree    ";


  //  If someone tries to de-allocate memory after PROCCESS_DETATCH (such as in a 
  //  global destructor), Re-initialize critical section and free memory.
  //
  if (!g_fInitCrit)
    InitializeCriticalSection(&g_csHeap);
	

  //  Move pointer to beginning of header
  lpvMem = (LPVOID) ((char *)lpvMem - HEADERSIZE);

  //  Find the instance in the hash table
  pn = FindInst(lpvMem);
  if (pn)
    {
    //	Verify the memory has not been overwritten 
    VerifyHeaderTrailer(pn);

    //	Trace allocations if switch is on
    if (FSWITCH(fTraceCtlAllocs))
      {
      CAddressNode *pn = FindInst(lpvMem);
      DumpInst(pn, lpTypeofAlloc);
      }

    //	Free memory  -- NOTE: WinNT will set free memory to 0xEEFEEEFE which is "����"
    fRet = HeapFree(g_hHeap, 0, lpvMem);
    if (!fRet)
      FAIL("CtlHeapFreeImpl - lpvMem was found to be allocated in the heap passed in \
	    but HeapFree() failed.  Maybe the pointer was already freed.");
    }

  //  Remove instance from hash table
  if (fRet)
    DeleteInst(lpvMem);  

  //  Make sure this memory wasn't allocated in a global constructor
  else if (!g_flagConstructorAlloc)
    {
    FAIL("CtlHeapFreeImpl - could not find lpvMem in the instance table.  See debug \
          output for more info.");
    AnalyzeInst(lpvMem);
    }
  else
    fRet = TRUE;
  
  //  If called after PROCESS_DETATCH delete critical section and Check for leaks again
  //  NOTE:  Only the LAST Assert will have the exact leak information.  All previous
  //	     Asserts will not take into account a HeapFree which occurs after PROCESS_DETACH.
  //	     This only occurs in controls using global static destructors.
  if (!g_fInitCrit)
    {
    CheckForLeaks();
    DeleteCriticalSection(&g_csHeap);
    }

  return fRet;
} //  CtlHeapFreeImpl



//=--------------------------------------------------------------------------=
//  CheckForLeaks:
//    We are calling PROCESS_DETATCH so check if hash table is empty.  If not
//    dump info on memory that has been leaked.
//=--------------------------------------------------------------------------=
VOID CheckForLeaks(VOID)
{
  CAddressNode * pn = EnumReset();
  BOOL IsEmpty = (pn == NULL);	  //  FALSE if there are leaks

  //  First check for memory trashing of any leaked memory
  HeapCheck();
  
  if (!IsEmpty)
    {

    //	First find out which OCX/DLL is leaking
    TCHAR lpCtlName[128];
    DWORD nSize = 128;
    DWORD fValidPath;
    fValidPath = GetModuleFileName(g_hInstance, (LPTSTR)lpCtlName, nSize);

    LPSTR lpLeaks;
    // Allocate some memory to hold the data but use GlobalAlloc since we
    // don't want to use the vb memory stuff since it will muck things up.
    lpLeaks = (LPSTR)GlobalLock(GlobalAlloc(GMEM_MOVEABLE,128));

    lstrcpy(lpLeaks, lpCtlName);
    lstrcat(lpLeaks, " has leaked memory.\nUse PutBreakPointHere() in macros.cpp to debug.\r\n");

    //  Collect all leak info
    lpLeaks = DumpInstTable(lpLeaks);
    
    //  Dump output to file if "fOutputFile" switch is on
    if (FSWITCH(fOutputFile))
      OutputToFile(lpLeaks);

    //  Dump output to an assert as long as "fNoLeakAsserts" is off
    else if (!FSWITCH(fNoLeakAsserts))
      {
      //  Truncate output so it fits into DisplayAssert (512 Max)
      if (lstrlen(lpLeaks) > 500)
	{
	lstrcpyn(lpLeaks, lpLeaks, 500);
	lstrcat(lpLeaks, "\nMore...");
	}
      DisplayAssert(lpLeaks, "FAIL", NULL, 0);
      }

    //	Release memory used to store leak info
    GlobalUnlock((HGLOBAL)GlobalHandle(lpLeaks)), 
	      (BOOL)GlobalFree((HGLOBAL)GlobalHandle(lpLeaks));

    }
  return;
} //  CheckForLeaks



//=--------------------------------------------------------------------------=
//  AddInst:
//    A heap allocation occured so here we add the allocation information to 
//    the instance table.  To debug memory leaks where you need to use pass 
//    counts, set a passcount breakpoint in this function using the passcount 
//    value given in the debug output.
//=--------------------------------------------------------------------------=
VOID AddInst(
	     VOID * pv, 
	     DWORD dwBytes, 
	     LPSZ szFile, 
	     UINT uLine
	    )
{
  UINT uHash;
  CAddressNode * pn = new CAddressNode();
  ASSERT(pn,"");
  
  EnterCriticalSection(&g_csHeap);

  m_cGlobalPassCount++;

  pn->m_pv = pv;                        //  Memory address of allocation
  pn->m_cb = dwBytes;                   //  Bytes requested to be allocated
  pn->m_cAlloc = m_cGlobalPassCount;    //  This is the pass count value in debug output.
  pn->m_szFile = szFile;                //  Source file the allocation call was made
  pn->m_uLine = uLine;                  //  Line number in source file.

  PutBreakPointHere(pv, dwBytes, m_cGlobalPassCount, szFile, uLine);

  //  Add instance to proper position in table
  uHash = HashInst(pv);
  pn->m_pnNext = m_rInstTable[uHash];
  m_rInstTable[uHash] = pn;

  //  Copy header and trailer signatures.
  memcpy((char *)pv, g_szHeader, HEADERSIZE);
  memcpy((char *)pv + HEADERSIZE + dwBytes, g_szTrailer, TRAILERSIZE);

  LeaveCriticalSection(&g_csHeap);

  //  Track extra memory debug info
  DebugInst( dwBytes );
} //  AddInst



//=--------------------------------------------------------------------------=
//  DebugInst:
//    Updates the memory debug information
//=--------------------------------------------------------------------------=
VOID DebugInst(
	       ULONG cb
	      )
{
  EnterCriticalSection(&g_csHeap);

  ++m_cCurNumAllocs;
  ++m_cNumAllocs;
  ++m_OverallCurAlloc;
  m_cCurNumBytesAllocated+=cb;
  m_cNumBytesAllocated+=cb;
  m_OverallCurBytes+=cb;

  m_HWAllocs = (m_HWAllocs < m_cCurNumAllocs) ? m_cCurNumAllocs : m_HWAllocs;
  m_HWBytes = (m_HWBytes < m_cCurNumBytesAllocated) ? m_cCurNumBytesAllocated : m_HWBytes;
  m_OverallHWAlloc = (m_OverallHWAlloc < m_OverallCurAlloc) 
						    ? m_OverallCurAlloc : m_OverallHWAlloc;
  m_OverallHWBytes = (m_OverallHWBytes < m_OverallCurBytes) 
						    ? m_OverallCurBytes : m_OverallHWBytes;

  LeaveCriticalSection(&g_csHeap);

} //  DebugInst



//=--------------------------------------------------------------------------=
//  FindInst:
//    Give a pointer to an allocation, return a pointer to the debug 
//    allocation information.
//=--------------------------------------------------------------------------=
CAddressNode * FindInst(
			LPVOID pv
		       )
{
  CAddressNode * pn;

  EnterCriticalSection(&g_csHeap);

  pn = m_rInstTable[HashInst(pv)];
  while (pn && pn->m_pv != pv)
    pn = pn->m_pnNext;

  LeaveCriticalSection(&g_csHeap);
  return pn;

} //  FindInst



//=--------------------------------------------------------------------------=
//  AnalyzeInst:
//    Given a pointer try determine if it is a valid Read and Write pointer
//    and if it was allocated.
//=--------------------------------------------------------------------------=
VOID AnalyzeInst(
		 LPVOID pv
		)
{
  LPTSTR lpTypeofAlloc = "Bad lpvMem ";
  CAddressNode * pn = NULL;

  //  Either we have a bad pointer or the pointer does not point to any
  //  known heap allocations.   Here we check if it points to readable or 
  //  writable memory.
  BOOL fBadPointer = (IsBadReadPtr(pv, 4) || IsBadWritePtr(pv, 4));
    
  // Report what we know about the memory address
  if (fBadPointer)
    DebugPrintf("AnalyzeInst found that pointer pv=0x%lX is not writable\n\r" \
		"or readable.  The allocation is either outside the addressable range\n\r" \
		"for this operating system or the allocation was already freed.\n\r",pv);
  else
    DebugPrintf("AnalyzeInst found that pointer pv=0x%lX is readable and writable,\n\r"  \
		"so the allocation was made without being added to instance table\n\r" \
		"(prior to PROCESS_ATTATCH), or the memory was already freed.\n\r",pv);
    
} //  AnanlyzeInst



//=--------------------------------------------------------------------------=
//  DumpInst:
//    Dump instance information out to an assert window.
//=--------------------------------------------------------------------------=
VOID DumpInst(
	      CAddressNode * pn,
	      LPTSTR lpTypeofAlloc
	     )
{  
  char szOutput[255];

  //  Format output
  wsprintf(szOutput, "%s: %s(%u) Address=0x%lx  nAlloc=%ld  Bytes=%ld\r\n", lpTypeofAlloc,
	   pn->m_szFile, pn->m_uLine, (ULONG)pn->m_pv, (ULONG)pn->m_cAlloc, (ULONG)pn->m_cb);
  
  //  Dump output to file if switch is turned on
  if (FSWITCH(fOutputFile))
    OutputToFile(szOutput);
  else if (FSWITCH(fNoLeakAsserts))
    DebugPrintf(szOutput);
      
  //  Else display output in assert
  else
    DisplayAssert(szOutput, "FAIL", _szThisFile, __LINE__);;

} //  DumpInst



//=--------------------------------------------------------------------------=
//  DumpInstTable:
//    Memory leak has been detected so dump the entire instance table.
//=--------------------------------------------------------------------------=
LPSTR DumpInstTable(
		    LPSTR lpLeak
		   )
{
  CAddressNode * pn = EnumReset();
  DWORD sizeoflpLeak;
  LPSTR lpTemp;

  EnterCriticalSection(&g_csHeap);

  DebugPrintf(lpLeak);

  while (pn)
    {
    //	Format the leak info
    char szOut[250] = {NULL};
    wsprintf(szOut, "\t%s(%u) Address=0x%lx  nAlloc=%ld  Bytes=%ld\r\n", pn->m_szFile,
           pn->m_uLine, (ULONG)pn->m_pv, (ULONG)pn->m_cAlloc, (ULONG)pn->m_cb);

    DebugPrintf(szOut);
    
    //  Convert lpLeak to a handle and get its current allocation size
    sizeoflpLeak = GlobalSize(GlobalHandle(lpLeak));

    //	Reallocate memory to make space for more leak info
    lpTemp = (LPSTR) (GlobalUnlock((HGLOBAL)GlobalHandle(lpLeak)), 
	      GlobalLock(GlobalReAlloc((HGLOBAL)GlobalHandle(lpLeak), 
	      sizeoflpLeak + lstrlen(szOut) + 1, GMEM_MOVEABLE)));

    //	Add new leak info to lpLeak
    if(lpTemp)
      {
      lpLeak = lpTemp;
      lstrcat(lpLeak, szOut);
      }

    //	Get the next leak
    pn = EnumNext();
    }
  LeaveCriticalSection(&g_csHeap);
  return lpLeak;

} //  DumpInstTable



//=--------------------------------------------------------------------------=
//  DeleteInst:
//    A heap allocation got free or was reallocated so remove the
//    information from the instance table and check for memory trashing.
//=--------------------------------------------------------------------------=
VOID DeleteInst(
		LPVOID pv
	       )
{
  CAddressNode ** ppn, * pnDead;
  ppn = &m_rInstTable[HashInst(pv)];

  EnterCriticalSection(&g_csHeap);
  
  //  Find allocation instance 
  while (*ppn != NULL)
    {
    if ((*ppn)->m_pv == pv)
      {
      pnDead = *ppn;
      *ppn = (*ppn)->m_pnNext;

      //  Correct memory debug info
      --m_cCurNumAllocs;
      m_cCurNumBytesAllocated -= pnDead->m_cb;
      --m_OverallCurAlloc;
      m_OverallCurBytes -= pnDead->m_cb;

      //  Remove instance
      delete pnDead;
		  LeaveCriticalSection(&g_csHeap);
      return;
      }	//  if

    ppn = &((*ppn)->m_pnNext);
    } //  while

    FAIL("DeleteInst - memory instance not found");
} //  DeleteInst



//=--------------------------------------------------------------------------=
//  VerifyHeaderTrailer:
//    Inspect allocation for header and trailer signature overwrites
//=--------------------------------------------------------------------------=
VOID VerifyHeaderTrailer(
			 CAddressNode * pn
			)
{
  LPTSTR lpTypeofAlloc = "Memory trashed ";

  //Verify the header
  if (memcmp((char *)pn->m_pv, g_szHeader, HEADERSIZE) != 0)
    {
    FAIL("Heap block header has been trashed.");
    DebugPrintf("Heap block header trashed.");
    DebugPrintf("\r\n");
    DumpInst(pn, lpTypeofAlloc);
    }

  //Verify the trailer
  if (memcmp((char *)pn->m_pv + pn->m_cb + HEADERSIZE, g_szTrailer, TRAILERSIZE) != 0)
    {
    FAIL("Heap block trailer has been trashed.");
    DebugPrintf("Heap block trailer trashed.");
    DebugPrintf("\r\n");
    DumpInst(pn, lpTypeofAlloc);
    }
  return;

} //  VerifyHeaderTrailer




//=--------------------------------------------------------------------------=
//  HeapCheck:
//    Inspect all of the allocations for header and trailer signature 
//    overwrites.
//=--------------------------------------------------------------------------=
VOID HeapCheck(VOID)
{
  ASSERT(HeapValidate(g_hHeap, 0, NULL) != 0, "OS Says heap is corrupt");

  CAddressNode * pn = EnumReset();
  while (pn)
    {
    VerifyHeaderTrailer(pn);
    pn = EnumNext();
    }
  return;
} //  HeapCheck



//=-------------------------------------------------------------------------=
//  For use with CAddresssNode
//=-------------------------------------------------------------------------=
#define MEM_cAddressNodes 128		  //  Nodes are block allocated
#define UNUSED(var)	  ((var) = (var)) //  Used to avoid warnings

//  The free list is common
CAddressNode * CAddressNode::m_pnFreeList = NULL;

//=--------------------------------------------------------------------------=
//  CAddressNode::operator new:
//    Returns a pointer to an allocated address node. If there are none on 
//    the free list then we allocate a block of address nodes, chain them 
//    together and add them to the free list.   These nodes are never 
//    actually freed so it is ok to allocate them in blocks.
//=--------------------------------------------------------------------------=
void * CAddressNode::operator new(
				  size_t cb
				 )
{
  CAddressNode * pn;
  UNUSED(cb);

  EnterCriticalSection(&g_csHeap); // needed for static m_pnFreeList

  if (m_pnFreeList == NULL)
    {
    UINT cbSize = sizeof(CAddressNode) * MEM_cAddressNodes;  //allocate a block
    pn = (CAddressNode *) HeapAlloc(g_hHeap, 0, cbSize);
    //chain all except the first node together.  the first node
    //is the one returned
    for (int i = 1; i < MEM_cAddressNodes - 1; ++i)
      pn[i].m_pnNext = &pn[i+1];
    pn[MEM_cAddressNodes - 1].m_pnNext = NULL;
    m_pnFreeList = &pn[1];
    }
  else
    {
    pn = m_pnFreeList;
    m_pnFreeList = pn->m_pnNext;
    }

  LeaveCriticalSection(&g_csHeap);
  return pn;
} //  CAddressNode::operator new



//=--------------------------------------------------------------------------=
//  CAddressNode::operator delete
//    Return the address node to the free list.  We never actually free
//    the node since nodes are allocated in blocks.
//=--------------------------------------------------------------------------=
void CAddressNode::operator delete(
				   void * pv
				  )
{
  EnterCriticalSection(&g_csHeap); // needed for static m_pnFreeList

  CAddressNode * pn = (CAddressNode *) pv;
  pn->m_pnNext = m_pnFreeList;
  m_pnFreeList = pn;

  LeaveCriticalSection(&g_csHeap);
} //  CAddressNode::operator delete



//=--------------------------------------------------------------------------=
//  EnumReset:
//    Reset the enumerator and return the first node.  NULL if empty.
//=--------------------------------------------------------------------------=
CAddressNode * EnumReset()
{
  m_pnEnumNode = NULL;
  for (m_uEnumIndex = 0; m_uEnumIndex < NUM_INST_TABLE_ENTRIES; ++m_uEnumIndex)
    {
    m_pnEnumNode = m_rInstTable[m_uEnumIndex];
    if (m_pnEnumNode != NULL)
      return m_pnEnumNode;
    }
  return NULL;  //Instance table is empty
} //  EnumReset



//=--------------------------------------------------------------------------=
//  EnumNext:
//    Return the next node in the enumeration.  m_pnEnumNode points to the last
//    node returned.  It is NULL if no more left.
//=--------------------------------------------------------------------------=
CAddressNode * EnumNext()
{
  ASSERT(m_uEnumIndex <= NUM_INST_TABLE_ENTRIES, "");

  if (m_pnEnumNode == NULL)
    return NULL;    //end of enumeration

  m_pnEnumNode = m_pnEnumNode->m_pnNext;
  if (m_pnEnumNode == NULL)
    {
    //at end of this linked list so search for next list
    m_uEnumIndex++;
    while (m_uEnumIndex < NUM_INST_TABLE_ENTRIES && m_rInstTable[m_uEnumIndex] == NULL)
      m_uEnumIndex++;
    if (m_uEnumIndex < NUM_INST_TABLE_ENTRIES)
      m_pnEnumNode = m_rInstTable[m_uEnumIndex];
    }
  return m_pnEnumNode;
} //  EnumNext



//=---------------------------------------------------------------------------=
//  OutputToFile:
//    Dumps output to file "ctldebug.log"
//=---------------------------------------------------------------------------=
VOID OutputToFile
(
    LPSTR szOutput
)
{
    DWORD nPathSize;
    DWORD nDirPathSize = 128;
    TCHAR lpFilePath[128];
    LPCTSTR lpFileName = "\\CtlDebug.log";
    HANDLE hFile;
    BOOL fWritten, fClosed = FALSE;
    DWORD nBytesWritten;

    //	Create path to output file
    nPathSize = GetCurrentDirectory(nDirPathSize, (LPTSTR)lpFilePath);
    if (nPathSize == 0)
      FAIL("Unable to get current directory...");
    lstrcat(lpFilePath, lpFileName);

    //	Open and write to file
    hFile = CreateFile((LPCTSTR)lpFilePath, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 
			                                FILE_ATTRIBUTE_NORMAL, NULL);
    DWORD SetPtr = SetFilePointer(hFile, NULL, NULL, FILE_END);
    fWritten = WriteFile(hFile, (LPCVOID)szOutput, (DWORD)strlen(szOutput), 
			                                        &nBytesWritten, NULL); 
    if (!fWritten)
      FAIL("Unable to write output to file...");

    //	Close file handle
    fClosed = CloseHandle(hFile);
    if (!fClosed)
      FAIL("Unable to close output file...");

} //  OutputToFile


//
//  End of Debug Memory Leak implemntation
//
//=--------------------------------------------------------------------------=


//=--------------------------------------------------------------------------=
// This routine outputs through DebugPrintf some information if the 
// given hr fails to succeed.  This is used by RRETURN to output where
// a function that returns a failing error code.
//=--------------------------------------------------------------------------=
HRESULT HrDebugTraceReturn
(
  HRESULT hr,
  char *pszFile,
  int iLine
)
{
  // We only output information if the hr fails.
  if (FAILED(hr))
    {
    char szMessageError[128];
    szMessageError[0] = '\0';
    BOOL fMessage;

#if RBY_MAC
    fMessage = FALSE; // FormatMessage not available on the mac
#else
    // Get the message from the system
    // CONSIDER, t-tshort 10/95: Getting some messages from us instead 
    //                           of the system?
    fMessage = FormatMessage(FORMAT_MESSAGE_MAX_WIDTH_MASK
			      | FORMAT_MESSAGE_FROM_SYSTEM,
			     NULL, hr,
			     MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),
			     szMessageError, sizeof(szMessageError), NULL);
#endif

    // Erps didn't get a message.
    if(!fMessage)
      lstrcpy(szMessageError,"Unknown Hresult");

    // Output the information that we want.
    DebugPrintf("FAILED RETURN: %s(%d) : 0x%08lx, %s\n", 
                pszFile, iLine, hr, szMessageError);
    }

  return hr;
}

//---------------------------------------------------------------------
// The following is a common output formatting buffer shared by several
// of the following debug routines.
//---------------------------------------------------------------------
char s_rgchOutput[2048]; // pretty big...


//=--------------------------------------------------------------------------=
// Emit debugging information
//=--------------------------------------------------------------------------=
void _DebugOutput(char* pszOutput)
{
  OutputDebugString(pszOutput);
}


//=--------------------------------------------------------------------------=
// Emit a formatted debugging string to the location specified in
// the debug options dialog.
//=--------------------------------------------------------------------------=
void _DebugPrintf(char* pszFmt, ...) 
{
  va_list  args;

  va_start(args, pszFmt);
  wvsprintf(s_rgchOutput, pszFmt, args);
  va_end(args);

  // sqwak if we overrun the formatting buffer!
  ASSERT(strlen(s_rgchOutput) < sizeof(s_rgchOutput), "");

  _DebugOutput(s_rgchOutput);
}

//=--------------------------------------------------------------------------=
// Conditional form of DebugPrintf
//=--------------------------------------------------------------------------=
void _DebugPrintIf(BOOL fPrint, char* pszFmt, ...)
{
  va_list  args;

  if (!fPrint)
    return;

  va_start(args, pszFmt);
  wvsprintf(s_rgchOutput, pszFmt, args);
  va_end(args);

  // sqwak if we overrun the formatting buffer!
  ASSERT(strlen(s_rgchOutput) < sizeof(s_rgchOutput), "");

  _DebugOutput(s_rgchOutput);
}


#endif // DEBUG