home *** CD-ROM | disk | FTP | other *** search
/ Programming Languages Suite / ProgLangD.iso / C++-7 / DISK10 / MFC / SRC / MEMORY.CP$ / memory
Encoding:
Text File  |  1992-01-15  |  16.7 KB  |  630 lines

  1. // This is a part of the Microsoft Foundation Classes C++ library. 
  2. // Copyright (C) 1992 Microsoft Corporation 
  3. // All rights reserved. 
  4. //  
  5. // This source code is only intended as a supplement to the 
  6. // Microsoft Foundation Classes Reference and Microsoft 
  7. // QuickHelp documentation provided with the library. 
  8. // See these sources for detailed information regarding the 
  9. // Microsoft Foundation Classes product. 
  10.  
  11.  
  12. #include "afx.h"
  13. #pragma hdrstop
  14.  
  15. #include <limits.h>
  16. #define SIZE_T_MAX  UINT_MAX
  17.  
  18. #ifdef AFX_CORE_SEG
  19. #pragma code_seg(AFX_CORE_SEG)
  20. #endif
  21.  
  22. #ifdef _DEBUG       // entire file for debugging
  23.  
  24. #undef THIS_FILE
  25. static char BASED_CODE THIS_FILE[] = __FILE__;
  26.  
  27. /////////////////////////////////////////////////////////////////////////////
  28. // forward
  29.  
  30. static void* AllocMemoryDebug(size_t nSize, BOOL bIsObject, 
  31.     const char FAR* pFileName, int nLine);
  32. static void FreeMemoryDebug(void* pbData, BOOL bIsObject);
  33.  
  34. /////////////////////////////////////////////////////////////////////////////
  35. // test allocation routines
  36.  
  37. extern "C" int afxMemDF = allocMemDF;
  38.  
  39. void* operator new(size_t nSize)
  40. {
  41.     // memory corrupt before global new
  42.     if (afxMemDF & checkAlwaysMemDF)
  43.         ASSERT(AfxCheckMemory()); 
  44.  
  45.     void* p = AllocMemoryDebug(nSize, FALSE, NULL, 0);
  46.  
  47.     if (p == NULL)
  48.     {
  49.         TRACE("::operator new(%u) failed - throwing exception\n", nSize);
  50.         AfxThrowMemoryException();
  51.     }
  52.     
  53.     return p;
  54. }
  55.  
  56. void* operator new(size_t nSize, const char FAR* pFileName, int nLine)
  57. {
  58.     // memory corrupt before global new
  59.     if (afxMemDF & checkAlwaysMemDF)
  60.         ASSERT(AfxCheckMemory()); 
  61.  
  62.     void* p = AllocMemoryDebug(nSize, FALSE, pFileName, nLine);
  63.     
  64.     if (p == NULL)
  65.     {
  66.         TRACE("::operator new(%u) failed - throwing exception\n", nSize);
  67.         AfxThrowMemoryException();
  68.     }
  69.  
  70.     return p;
  71. }
  72.  
  73. void operator delete(void* pbData)
  74. {
  75.     // memory corrupt before global delete
  76.     if (afxMemDF & checkAlwaysMemDF)
  77.         ASSERT(AfxCheckMemory()); 
  78.  
  79.     FreeMemoryDebug(pbData, FALSE);
  80. }
  81.  
  82. void* CObject::operator new(size_t nSize)
  83. {
  84.     // memory corrupt before global new
  85.     if (afxMemDF & checkAlwaysMemDF)
  86.         ASSERT(AfxCheckMemory()); 
  87.  
  88.     void* p = AllocMemoryDebug(nSize, TRUE, NULL, 0);
  89.     
  90.     if (p == NULL)
  91.     {
  92.         TRACE("CObject::operator new(%u) failed - throwing exception\n", nSize);
  93.         AfxThrowMemoryException();
  94.     }
  95.  
  96.     return p;
  97. }
  98.  
  99. void* 
  100. CObject::operator new(size_t nSize, const char FAR* pFileName, int nLine)
  101. {
  102.     // memory corrupt before 'CObject::new'
  103.     if (afxMemDF & checkAlwaysMemDF)
  104.         ASSERT(AfxCheckMemory()); 
  105.  
  106.     void* p = AllocMemoryDebug(nSize, TRUE, pFileName, nLine);
  107.     
  108.     if (p == NULL)
  109.     {
  110.         TRACE("CObject::operator new(%u) failed - throwing exception\n", nSize);
  111.         AfxThrowMemoryException();
  112.     }
  113.  
  114.     return p;
  115. }
  116.  
  117. void CObject::operator delete(void* pbData)
  118. {
  119.     // memory corrupt before 'CObject::delete'
  120.     if (afxMemDF & checkAlwaysMemDF)
  121.         ASSERT(AfxCheckMemory()); 
  122.  
  123.     FreeMemoryDebug(pbData, TRUE);
  124. }
  125.  
  126. /////////////////////////////////////////////////////////////////////////////
  127. // allocation failure hook, tracking turn on
  128.  
  129. static BOOL bTrackingOn = TRUE;
  130. static BOOL PASCAL DefaultAllocHook(size_t, BOOL, LONG)
  131.     { return TRUE; }
  132.  
  133. static AFX_ALLOC_HOOK pfnAllocHook = DefaultAllocHook;
  134.  
  135. AFX_ALLOC_HOOK AfxSetAllocHook(AFX_ALLOC_HOOK pfnNewHook)
  136. {
  137.     AFX_ALLOC_HOOK pfnOldHook = pfnAllocHook;
  138.     pfnAllocHook = pfnNewHook;
  139.     return pfnOldHook;
  140. }
  141.  
  142. BOOL PASCAL AfxEnableMemoryTracking(BOOL bNewTrackingOn)
  143. {
  144.     BOOL bOldTrackingOn = bTrackingOn;
  145.     bTrackingOn = bNewTrackingOn;
  146.     return bOldTrackingOn;
  147. }
  148.  
  149. /////////////////////////////////////////////////////////////////////////////
  150. // stop on a specific memory request
  151.  
  152. static LONG lStopRequest = 0;
  153. static AFX_ALLOC_HOOK pfnOldStopHook = NULL;
  154.  
  155. #pragma optimize("q", off)
  156. extern "C" void PASCAL AfxStop()
  157. {
  158.     // set a breakpoint on this routine from debugger
  159.     TRACE("AfxStop() stopping under the debugger\n");
  160.     _asm { int 3 };
  161.     TRACE("AfxStop() continues\n");
  162. }
  163. #pragma optimize("", on)
  164.  
  165. static BOOL PASCAL AfxTestAllocStop(size_t nSize, BOOL bIsObject, LONG lRequest)
  166. {
  167.     if (lRequest == lStopRequest)
  168.     {
  169.         TRACE("Allocating block # %ld\n", lRequest);
  170.         AfxStop();
  171.     }
  172.  
  173.     // otherwise just pass on to other hook
  174.     return (*pfnOldStopHook)(nSize, bIsObject, lRequest);
  175. }
  176.  
  177. void 
  178. PASCAL AfxSetAllocStop(LONG lRequestNumber)
  179. {
  180.     if (pfnOldStopHook == NULL)
  181.         pfnOldStopHook = AfxSetAllocHook(AfxTestAllocStop);
  182.  
  183.     lStopRequest = lRequestNumber;
  184. }
  185.  
  186. /////////////////////////////////////////////////////////////////////////////
  187. // AFX Memory Management diagnostics - malloc-like
  188. //
  189.  
  190. // we keep statistics on what memory is/was used
  191. static LONG lTotalAlloc;// total bytes of memory allocated 
  192. static LONG lCurAlloc;  // current bytes of memory allocated 
  193. static LONG lMaxAlloc;  // maximum bytes of memory allocated at any one time 
  194.  
  195. // we keep a request count to use in replaying memory consumption
  196. static LONG lRequestLast = 0;
  197. #define lNotTracked 0       // if not tracked 
  198.  
  199. // for diagnostic purpose, blocks are allocated with extra information and
  200. //  stored in a doubly-linked list.  This makes all blocks registered with
  201. //  how big they are, when they were allocated and what they are used for.
  202.  
  203. static struct CBlockHeader* pFirstBlock = NULL; // add in reverse order
  204.  
  205. //  A no-mans-land area is allocated before and after the actual data:
  206. //      ---------
  207. //          start of CBlockHeader pFirstBlocker (linkage and statistical info)
  208. //          no man's land before actual data
  209. //          app pointer-> actual data
  210. //          no man's land after actual data
  211. //      ---------
  212.  
  213. #define nNoMansLandSize     4       // # of bytes 
  214.  
  215. // The following values are non-zero, constant, odd, large, and atypical
  216. //    Non-zero values help find bugs assuming zero filled data.
  217. //    Constant values are good so that memory filling is deterministic
  218. //        (to help make bugs reproducable).  Of course it is bad if
  219. //        the contant filling of weird values masks a bug.
  220. //    Mathematically odd numbers are good for finding bugs assuming a cleared
  221. //        lower bit.
  222. //    Large numbers (byte values at least) are less typical, and are good
  223. //        at finding bad addresses.
  224. //    Atypical values (i.e. not too often) are good since they typically
  225. //        cause early detection in code.
  226. //    For the case of no-man's land and free blocks, if you store to any
  227. //         of these locations, the memory integrity checker will detect it.
  228.  
  229. #define bNoMansLandFill     0xFD    // fill no-man's land with this 
  230. #define bDeadLandFill       0xDD    // fill free objects with this 
  231. #define bCleanLandFill      0xCD    // fill new objects with this 
  232.  
  233. // three uses for registered blocks
  234. static char* blockUseName[CMemoryState::nBlockUseMax] =
  235.     { "Free", "Object", "Non-Object" };
  236.  
  237. struct CBlockHeader 
  238. {
  239.     struct CBlockHeader* pBlockHeaderNext;
  240.     struct CBlockHeader* pBlockHeaderPrev;
  241.     const char FAR*     pFileName;
  242.     int                 nLine;
  243.     size_t              nDataSize;
  244.     enum CMemoryState::blockUsage use;
  245.     LONG                lRequest;
  246.     BYTE                gap[nNoMansLandSize];
  247.     // followed by:
  248.     //  BYTE            data[nDataSize];
  249.     //  BYTE            anotherGap[nNoMansLandSize];
  250.     BYTE* pbData()
  251.         { return (BYTE*) (this + 1); }
  252. };
  253.  
  254. static void* 
  255. AllocMemoryDebug(size_t nSize, BOOL bIsObject, const char FAR* pFileName, int nLine)
  256. // Allocate a memory block of the specific nSize with extra diagnostic
  257. //      support (padding on either nSize of block + linkage)
  258. // Mark it either as object (stores a non-primitive object) or just bits
  259. {
  260.     ASSERT(nSize > 0);
  261.  
  262.     LONG    lRequest;
  263.     lRequest = bTrackingOn ? ++lRequestLast : lNotTracked;
  264.  
  265.     // forced failure
  266.     if (!(*pfnAllocHook)(nSize, bIsObject, lRequest))
  267.         return NULL;
  268.  
  269.     if (!(afxMemDF & allocMemDF))
  270.         return malloc(nSize);
  271.  
  272.     // Diagnostic memory allocation from this point on
  273.     if (nSize > (size_t)SIZE_T_MAX - nNoMansLandSize - sizeof(CBlockHeader))
  274.     {
  275.         TRACE("Error: memory allocation: tried to allocate %u bytes\n", nSize);
  276.         TRACE("  object too large or negative size\n");
  277.         AfxThrowMemoryException();
  278.     }
  279.  
  280.     // keep track of total amount of memory allocated
  281.     lTotalAlloc += nSize;
  282.     lCurAlloc += nSize;
  283.  
  284.     if (lCurAlloc > lMaxAlloc)
  285.         lMaxAlloc = lCurAlloc;
  286.             
  287.     register struct CBlockHeader* p = (struct CBlockHeader*)
  288.        malloc(sizeof(CBlockHeader) + nSize + nNoMansLandSize);
  289.  
  290.     if (p == NULL)
  291.         return NULL;
  292.  
  293.     if (pFirstBlock)
  294.         pFirstBlock->pBlockHeaderPrev = p;
  295.  
  296.     p->pBlockHeaderNext = pFirstBlock;
  297.     p->pBlockHeaderPrev = NULL;
  298.     p->pFileName = pFileName;
  299.     p->nLine = nLine;
  300.     p->nDataSize = nSize;
  301.     p->use = bIsObject ? CMemoryState::objectBlock : CMemoryState::bitBlock;
  302.     p->lRequest = lRequest;
  303.  
  304.     // fill in gap before and after real block 
  305.     memset(p->gap, bNoMansLandFill, nNoMansLandSize);
  306.     memset(p->pbData() + nSize, bNoMansLandFill, nNoMansLandSize);
  307.  
  308.     // fill data with silly value (but non-zero) 
  309.     memset(p->pbData(), bCleanLandFill, nSize);
  310.  
  311.     // link blocks together
  312.     pFirstBlock = p;
  313.     return (void*)p->pbData();
  314. }
  315.  
  316.  
  317. // debugging free
  318. static void
  319. FreeMemoryDebug(void* pbData, BOOL bIsObject)
  320. {
  321.     if (pbData == NULL)
  322.         return;
  323.  
  324.     if (!(afxMemDF & allocMemDF))
  325.     {
  326.         free(pbData);
  327.         return;
  328.     }
  329.  
  330.     register struct CBlockHeader* p = ((struct CBlockHeader*) pbData)-1;
  331.  
  332.     // make sure we are freeing what we think we are:
  333.     ASSERT(p->use == (bIsObject ? CMemoryState::objectBlock 
  334.         : CMemoryState::bitBlock));
  335.         // error if freeing incorrect memory type
  336.  
  337.     // keep track of total amount of memory allocated
  338.     lCurAlloc -= p->nDataSize;
  339.     
  340.     p->use = CMemoryState::freeBlock;
  341.     // keep memory around as dead space
  342.     memset(p->pbData(), bDeadLandFill, p->nDataSize);
  343.  
  344.     // optionally reclaim memory
  345.     if (!(afxMemDF & delayFreeMemDF))
  346.     {
  347.         // remove from the linked list
  348.         if (p->pBlockHeaderNext)
  349.             p->pBlockHeaderNext->pBlockHeaderPrev = p->pBlockHeaderPrev;
  350.         
  351.         if (p->pBlockHeaderPrev)
  352.         {
  353.             p->pBlockHeaderPrev->pBlockHeaderNext = p->pBlockHeaderNext;
  354.         }
  355.         else
  356.         {
  357.             ASSERT(pFirstBlock == p);
  358.             pFirstBlock = p->pBlockHeaderNext;
  359.         }
  360.  
  361.         free(p);
  362.     }
  363. }
  364.  
  365. static BOOL
  366. CheckBytes(register BYTE* pb, register WORD bCheck, register size_t nSize)
  367. {
  368.     BOOL bOkay = TRUE;
  369.     while (nSize--)
  370.     {
  371.         if (*pb++ != bCheck)
  372.         {
  373.             TRACE("memory check error at $%08lX = $%02X, should be $%02X\n",
  374.                 (BYTE FAR*) (pb-1),*(pb-1), bCheck);
  375.             bOkay = FALSE;
  376.         }
  377.     }
  378.     return bOkay;
  379. }
  380.  
  381.  
  382. BOOL PASCAL
  383. AfxCheckMemory()
  384.   // check all of memory (look for memory tromps)
  385. {
  386.     if (!(afxMemDF & allocMemDF))
  387.         return TRUE;        // can't do any checking
  388.  
  389.     BOOL    allOkay = TRUE;
  390.  
  391.     // check all allocated blocks
  392.     register struct CBlockHeader* p;
  393.     for (p = pFirstBlock; p != NULL; p = p->pBlockHeaderNext)
  394.     {
  395.         BOOL    okay = TRUE;        // this block okay ?
  396.         char*   blockUse;
  397.  
  398.         if (p->use >= 0 && p->use < CMemoryState::nBlockUseMax)
  399.             blockUse = blockUseName[p->use];
  400.         else
  401.             blockUse = "Damage";
  402.  
  403.         // first check no-mans-land gaps
  404.         if (!CheckBytes(p->gap, bNoMansLandFill, nNoMansLandSize))
  405.         {
  406.             TRACE("DAMAGE: before %s block at $%08lX\n", blockUse,
  407.                 (BYTE FAR*) p->pbData());
  408.             okay = FALSE;
  409.         }
  410.  
  411.         if (!CheckBytes(p->pbData() + p->nDataSize, bNoMansLandFill,
  412.           nNoMansLandSize))
  413.         {
  414.             TRACE("DAMAGE: after %s block at $%08lX\n", blockUse,
  415.                 (BYTE FAR*) p->pbData());
  416.             okay = FALSE;
  417.         }
  418.  
  419.         // free blocks should remain undisturbed
  420.         if (p->use == CMemoryState::freeBlock &&
  421.           !CheckBytes(p->pbData(), bDeadLandFill, p->nDataSize))
  422.         {
  423.             TRACE("DAMAGE: on top of Free block at $%08lX\n",
  424.                 (BYTE FAR*) p->pbData());
  425.             okay = FALSE;
  426.         }
  427.  
  428.         if (!okay)
  429.         {
  430.             // report some more statistics about the broken object
  431.  
  432.             if (p->pFileName != NULL)
  433.                 TRACE("%s allocated at file %Fs(%d)\n", blockUse, 
  434.                     p->pFileName, p->nLine);
  435.  
  436.             TRACE("%s located at $%08lX is %u bytes long\n", blockUse,
  437.                 (BYTE FAR*) p->pbData(), p->nDataSize);
  438.  
  439.             allOkay = FALSE;
  440.         }
  441.     }
  442.     return allOkay;
  443. }
  444.  
  445.  
  446. // -- true if block of exact size, allocated on the heap
  447. // -- set *plRequestNumber to request number (or 0)
  448. BOOL 
  449. PASCAL AfxIsMemoryBlock(const void* pData, UINT nSize,
  450.         LONG* plRequestNumber)
  451. {
  452.  
  453.     if (!(afxMemDF & allocMemDF))
  454.     {
  455.         // no tracking memory allocator
  456.         if (plRequestNumber != NULL)
  457.             *plRequestNumber = 0;
  458.         return AfxIsValidAddress(pData, nSize); // the best we can do
  459.     }
  460.  
  461.     // otherwise we can check to make sure this was allocated with tracking
  462.     register struct CBlockHeader* p = ((struct CBlockHeader*) pData) - 1;
  463.  
  464.     if (AfxIsValidAddress(p, sizeof(CBlockHeader)) &&
  465.         (p->use == CMemoryState::objectBlock ||
  466.             p->use == CMemoryState::bitBlock) &&
  467.         AfxIsValidAddress(pData, nSize) &&
  468.         p->nDataSize == nSize)
  469.     {
  470.         if (plRequestNumber != NULL)
  471.             *plRequestNumber = p->lRequest;
  472.         return TRUE;
  473.     }
  474.  
  475.     return FALSE;
  476. }
  477.  
  478. // fills 'this' with the difference, returns TRUE if significant
  479. BOOL CMemoryState::Difference(const CMemoryState& oldState,
  480.         const CMemoryState& newState)
  481. {
  482.     BOOL bSignificantDifference = FALSE;
  483.     for (int use = 0; use < CMemoryState::nBlockUseMax; use++)
  484.     {
  485.         m_lSizes[use] = newState.m_lSizes[use] - oldState.m_lSizes[use];
  486.         m_lCounts[use] = newState.m_lCounts[use] - oldState.m_lCounts[use];
  487.  
  488.         if ((m_lSizes[use] != 0 || m_lCounts[use] != 0) &&
  489.           use != CMemoryState::freeBlock)
  490.             bSignificantDifference = TRUE;
  491.     }
  492.     m_lHighWaterCount = newState.m_lHighWaterCount - oldState.m_lHighWaterCount;
  493.     m_lTotalCount = newState.m_lTotalCount - oldState.m_lTotalCount;
  494.  
  495.     return bSignificantDifference;
  496. }
  497.  
  498.  
  499. void CMemoryState::DumpStatistics() const
  500. {
  501.     for (int use = 0; use < CMemoryState::nBlockUseMax; use++)
  502.     {
  503.         TRACE("%ld bytes in %ld %s Blocks\n", m_lSizes[use],
  504.             m_lCounts[use], blockUseName[use]);
  505.     }
  506.  
  507.     TRACE("Largest number used: %ld bytes\n", m_lHighWaterCount);
  508.     TRACE("Total allocations: %ld bytes\n", m_lTotalCount);
  509. }
  510.  
  511. // -- fill with current memory state
  512. void CMemoryState::Checkpoint()
  513. {
  514.     if (!(afxMemDF & allocMemDF))
  515.         return;     // can't do anything
  516.  
  517.     m_pBlockHeader = pFirstBlock;
  518.     for (int use = 0; use < CMemoryState::nBlockUseMax; use++)
  519.         m_lCounts[use] = m_lSizes[use] = 0;
  520.  
  521.     register struct CBlockHeader* p;
  522.     for (p = pFirstBlock; p != NULL; p = p->pBlockHeaderNext)
  523.     {
  524.         if (p->lRequest == lNotTracked)
  525.         {
  526.             // ignore it for statistics
  527.         }
  528.         else if (p->use >= 0 && p->use < CMemoryState::nBlockUseMax)
  529.         {
  530.             m_lCounts[p->use]++;
  531.             m_lSizes[p->use] += p->nDataSize;
  532.         }
  533.         else
  534.         {
  535.             TRACE("Bad memory block found at $%08lX\n", (BYTE FAR*) p);
  536.         }
  537.     }
  538.  
  539.     m_lHighWaterCount = lMaxAlloc;
  540.     m_lTotalCount = lTotalAlloc;
  541. }
  542.  
  543. // Dump objects created after this memory state was checkpointed
  544. // Will dump all objects if this memory state wasn't checkpointed
  545. // Dump all objects, report about non-objects also
  546. // List request number in {}, {0} => not tracked
  547. void CMemoryState::DumpAllObjectsSince() const
  548. {
  549.     if (!(afxMemDF & allocMemDF))
  550.     {
  551.         TRACE("Debugging allocator turned off, can't dump objects\n");
  552.         return;
  553.     }
  554.  
  555.     register struct CBlockHeader* pBlockStop;
  556.  
  557.     TRACE("Dumping objects ->\n");
  558.     pBlockStop = m_pBlockHeader;
  559.  
  560.     register struct CBlockHeader* p;
  561.     for (p = pFirstBlock; p != NULL && p != pBlockStop;
  562.         p = p->pBlockHeaderNext)
  563.     {
  564.         char sz[255];
  565.  
  566.         if (p->lRequest == lNotTracked)
  567.         {
  568.             // ignore it for dumping
  569.         }
  570.         else if (p->use == CMemoryState::objectBlock)
  571.         {
  572.             CObject* pObject = (CObject*) p->pbData();
  573.  
  574.             TRACE("{%ld} ", p->lRequest);
  575.             if (p->pFileName != NULL)
  576.             {
  577.                 sprintf(sz, "%Fs(%d) : ", p->pFileName, p->nLine);
  578.                 afxDump << (const char FAR*)sz;
  579.             }
  580.  
  581.  
  582.             pObject->Dump(afxDump);
  583.             afxDump << "\n";
  584.         }
  585.         else if (p->use == CMemoryState::bitBlock)
  586.         {
  587.             TRACE("{%ld} ", p->lRequest);
  588.             if (p->pFileName != NULL)
  589.             {
  590.                 sprintf(sz, "%Fs(%d) : ", p->pFileName, p->nLine);
  591.                 afxDump << (const char FAR*)sz;
  592.             }
  593.  
  594.             sprintf(sz, "non-object block at $%08lX, %u bytes long\n",
  595.                 (BYTE FAR*) p->pbData(), p->nDataSize);
  596.             afxDump << (const char FAR*)sz;
  597.         }
  598.     }
  599.     TRACE("Object dump complete.\n");
  600. }
  601.  
  602. /////////////////////////////////////////////////////////////////////////////
  603. // Enumerate all objects allocated in the diagnostic memory heap
  604.  
  605. void 
  606. PASCAL AfxDoForAllObjects(void (*pfn)(CObject*, void*), void* pContext)
  607. {
  608.     if (!(afxMemDF & allocMemDF))
  609.         return;         // sorry not enabled
  610.  
  611.     register struct CBlockHeader* p;
  612.     for (p = pFirstBlock; p != NULL; p = p->pBlockHeaderNext)
  613.     {
  614.         if (p->lRequest == lNotTracked)
  615.         {
  616.             // ignore it for iteration
  617.         }
  618.         else if (p->use == CMemoryState::objectBlock)
  619.         {
  620.             CObject* pObject = (CObject*) p->pbData();
  621.             (*pfn)(pObject, pContext);
  622.         }
  623.     }
  624. }
  625.  
  626. /////////////////////////////////////////////////////////////////////////////
  627.  
  628.  
  629. #endif //_DEBUG (entire file)
  630.