// // Copyright (c) Microsoft Corporation 1993-1995 // // mem.c // // This file contains memory management and dynamic // array functions. // // History: // 09-27-94 ScottH Taken from commctrl // 04-29-95 ScottH Taken from briefcase and cleaned up // #include "proj.h" #include "common.h" #ifndef NOMEM ////////////////////////////////////////////////////////////////// #ifndef WIN32 // // Subsegment Allocation for 16-bit // #define MAX_WORD 0xffff DECLARE_HANDLE(HHEAP); typedef struct { // maps to the bottom of a 16bit DS WORD reserved[8]; WORD cAlloc; WORD cbAllocFailed; HHEAP hhpFirst; HHEAP hhpNext; } HEAP; #define PHEAP(hhp) ((HEAP FAR*)MAKELP(hhp, 0)) #define MAKEHP(sel, off) ((void _huge*)MAKELP((sel), (off))) #define CBSUBALLOCMAX 0x0000f000L HHEAP g_hhpFirst = NULL; BOOL NEAR DestroyHeap(HHEAP hhp); void Mem_Terminate() { while (g_hhpFirst) DestroyHeap(g_hhpFirst); } BOOL NEAR CreateHeap(WORD cbInitial) { HHEAP hhp; if (cbInitial < 1024) cbInitial = 1024; hhp = (HHEAP)GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT | GMEM_SHARE, cbInitial); if (!hhp) return FALSE; if (!LocalInit((WORD)hhp, sizeof(HEAP), cbInitial - 1)) { GlobalFree(hhp); return FALSE; } PHEAP(hhp)->cAlloc = 0; PHEAP(hhp)->cbAllocFailed = MAX_WORD; PHEAP(hhp)->hhpNext = g_hhpFirst; g_hhpFirst = hhp; DebugMsg(DM_TRACE, "CreateHeap: added new local heap %x", hhp); return TRUE; } #pragma optimize("o", off) // linked list removals don't optimize correctly BOOL NEAR DestroyHeap(HHEAP hhp) { ASSERT(hhp); ASSERT(g_hhpFirst); if (g_hhpFirst == hhp) { g_hhpFirst = PHEAP(hhp)->hhpNext; } else { HHEAP hhpT = g_hhpFirst; while (PHEAP(hhpT)->hhpNext != hhp) { hhpT = PHEAP(hhpT)->hhpNext; if (!hhpT) return FALSE; } PHEAP(hhpT)->hhpNext = PHEAP(hhp)->hhpNext; } if (GlobalFree((HGLOBAL)hhp) != NULL) return FALSE; return TRUE; } #pragma optimize("", on) // back to default optimizations #pragma optimize("lge", off) // Suppress warnings associated with use of _asm... void NEAR* NEAR HeapAlloc(HHEAP hhp, WORD cb) { void NEAR* pb; _asm { push ds mov ds,hhp } pb = (void NEAR*)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, cb); if (pb) ((HEAP NEAR*)0)->cAlloc++; _asm { pop ds } return pb; } #pragma optimize("o", off) // linked list removals don't optimize correctly void _huge* WINAPI SharedAlloc(long cb) { void NEAR* pb; HHEAP hhp; HHEAP hhpPrev; // If this is a big allocation, just do a global alloc. // if (cb > CBSUBALLOCMAX) { void FAR* lpb = MAKEHP(GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT | GMEM_SHARE, cb), 0); if (!lpb) DebugMsg(DM_ERROR, "Alloc: out of memory"); return lpb; } hhp = g_hhpFirst; while (TRUE) { if (hhp == NULL) { if (!CreateHeap(0)) { DebugMsg(DM_ERROR, "Alloc: out of memory"); return NULL; } hhp = g_hhpFirst; } pb = HeapAlloc(hhp, (WORD)cb); if (pb) return MAKEHP(hhp, pb); // Record the size of the allocation that failed. // Later attempts to allocate more than this amount // will not succeed. This gets reset anytime anything // is freed in the heap. // PHEAP(hhp)->cbAllocFailed = (WORD)cb; // First heap is full... see if there's room in any other heap... // for (hhpPrev = hhp; hhp = PHEAP(hhp)->hhpNext; hhpPrev = hhp) { // If the last allocation to fail in this heap // is not larger than cb, don't even try an allocation. // if ((WORD)cb >= PHEAP(hhp)->cbAllocFailed) continue; pb = HeapAlloc(hhp, (WORD)cb); if (pb) { // This heap had room: move it to the front... // PHEAP(hhpPrev)->hhpNext = PHEAP(hhp)->hhpNext; PHEAP(hhp)->hhpNext = g_hhpFirst; g_hhpFirst = hhp; return MAKEHP(hhp, pb); } else { // The alloc failed. Set cbAllocFailed... // PHEAP(hhp)->cbAllocFailed = (WORD)cb; } } } } #pragma optimize("", on) // back to default optimizations #pragma optimize("lge", off) // Suppress warnings associated with use of _asm... void _huge* WINAPI SharedReAlloc(void _huge* pb, long cb) { void NEAR* pbNew; void _huge* lpbNew; UINT cbOld; // does not work with cb > 64k if (!pb) return SharedAlloc(cb); if (OFFSETOF(pb) == 0) return MAKEHP(GlobalReAlloc((HGLOBAL)SELECTOROF(pb), cb, GMEM_MOVEABLE | GMEM_ZEROINIT), 0); _asm { push ds mov ds,word ptr [pb+2] } pbNew = (void NEAR*)LocalReAlloc((HLOCAL)OFFSETOF(pb), (int)cb, LMEM_MOVEABLE | LMEM_ZEROINIT); if (!pbNew) cbOld = LocalSize((HLOCAL)OFFSETOF(pb)); _asm { pop ds } if (pbNew) return MAKEHP(SELECTOROF(pb), pbNew); lpbNew = SharedAlloc(cb); if (lpbNew) { hmemcpy((void FAR*)lpbNew, (void FAR*)pb, cbOld); Free(pb); } else { DebugMsg(DM_ERROR, "ReAlloc: out of memory"); } return lpbNew; } BOOL WINAPI SharedFree(void _huge* FAR * ppb) { BOOL fSuccess; UINT cAlloc; void _huge * pb = *ppb; if (!pb) return FALSE; *ppb = 0; if (OFFSETOF(pb) == 0) return (GlobalFree((HGLOBAL)SELECTOROF(pb)) == NULL); _asm { push ds mov ds,word ptr [pb+2] } fSuccess = (LocalFree((HLOCAL)OFFSETOF(pb)) ? FALSE : TRUE); cAlloc = 1; if (fSuccess) { cAlloc = --((HEAP NEAR*)0)->cAlloc; ((HEAP NEAR*)0)->cbAllocFailed = MAX_WORD; } _asm { pop ds } if (cAlloc == 0) DestroyHeap((HHEAP)SELECTOROF(pb)); return fSuccess; } DWORD WINAPI SharedGetSize(void _huge* pb) { WORD wSize; if (OFFSETOF(pb) == 0) return GlobalSize((HGLOBAL)SELECTOROF(pb)); _asm { push ds mov ds,word ptr [pb+2] } wSize = LocalSize((HLOCAL)OFFSETOF(pb)); _asm { pop ds } return (DWORD)wSize; } #pragma optimize("", on) ////////////////////////////////////////////////////////////////// #else // WIN32 // // Win32 memory management wrappers // // Define a Global Shared Heap that we use to allocate memory // out of that we need to share between multiple instances. // static HANDLE g_hSharedHeap = NULL; #define MAXHEAPSIZE 2097152 #define HEAP_SHARED 0x04000000 /* put heap in shared memory */ /*---------------------------------------------------------- Purpose: Clean up heap. This function should be called at the program's termination. Returns: -- Cond: -- */ void PUBLIC Mem_Terminate() { // Assuming that everything else has exited // if (g_hSharedHeap != NULL) HeapDestroy(g_hSharedHeap); g_hSharedHeap = NULL; } /*---------------------------------------------------------- Purpose: Copies psz into *ppszBuf. Will alloc or realloc *ppszBuf accordingly. If psz is NULL, this function frees *ppszBuf. This is the preferred method of freeing the allocated buffer. Returns: TRUE on success Cond: -- */ BOOL PUBLIC GSetString( LPSTR * ppszBuf, LPCSTR psz) // NULL to free *ppszBuf { BOOL bRet = FALSE; ASSERT(ppszBuf); // Free the buffer? if (!psz) { // Yes if (ppszBuf) { GFree(*ppszBuf); *ppszBuf = NULL; } bRet = TRUE; } else { // No; (re)allocate and set buffer DWORD cb = CbFromCch(lstrlen(psz)+CCH_NUL); if (*ppszBuf) { // Need to reallocate? if (cb > GGetSize(*ppszBuf)) { // Yes LPSTR pszT = GReAlloc(*ppszBuf, cb); if (pszT) { *ppszBuf = pszT; bRet = TRUE; } } else { // No bRet = TRUE; } } else { *ppszBuf = (LPSTR)GAlloc(cb); if (*ppszBuf) { bRet = TRUE; } } if (bRet) { ASSERT(*ppszBuf); lstrcpy(*ppszBuf, psz); } } return bRet; } /*---------------------------------------------------------- Purpose: Concatenates psz onto *ppszBuf. Will alloc or realloc *ppszBuf accordingly. Returns: TRUE on success Cond: -- */ BOOL PUBLIC GCatString( LPSTR * ppszBuf, LPCSTR psz) { BOOL bRet = FALSE; DWORD cb; ASSERT(ppszBuf); ASSERT(psz); cb = CbFromCch(lstrlen(psz)+CCH_NUL); if (*ppszBuf) { // (Don't need to count nul because it is already counted in cb) DWORD cbExisting = CbFromCch(lstrlen(*ppszBuf)); // Need to reallocate? if ((cb+cbExisting) > GGetSize(*ppszBuf)) { // Yes; realloc at least MAX_BUF to cut down on the amount // of calls in the future LPSTR pszT = GReAlloc(*ppszBuf, cbExisting+max(cb, MAX_BUF)); if (pszT) { *ppszBuf = pszT; bRet = TRUE; } } else { // No bRet = TRUE; } } else { *ppszBuf = (LPSTR)GAlloc(max(cb, MAX_BUF)); if (*ppszBuf) { bRet = TRUE; } } if (bRet) { ASSERT(*ppszBuf); lstrcat(*ppszBuf, psz); } return bRet; } // // Shared heap memory management // #ifndef NOSHAREDHEAP /*---------------------------------------------------------- Purpose: Allocate out of shared heap Returns: Pointer to allocate memory Cond: -- */ void * PUBLIC SharedAlloc( DWORD cb) { // I will assume that this is the only one that needs the checks to // see if the heap has been previously created or not if (g_hSharedHeap == NULL) { ENTER_EXCLUSIVE() { if (g_hSharedHeap == NULL) { g_hSharedHeap = HeapCreate(HEAP_SHARED, 1, MAXHEAPSIZE); } } LEAVE_EXCLUSIVE() // If still NULL we have problems! if (g_hSharedHeap == NULL) return(NULL); } return HeapAlloc(g_hSharedHeap, HEAP_ZERO_MEMORY, cb); } /*---------------------------------------------------------- Purpose: Realloc out of shared heap. Returns: Possibly new pointer to resized block Cond: -- */ void * PUBLIC SharedReAlloc( PVOID pv, DWORD cb) { if (NULL == pv) { return SharedAlloc(cb); } return HeapReAlloc(g_hSharedHeap, HEAP_ZERO_MEMORY, pv, cb); } /*---------------------------------------------------------- Purpose: Free shared memory Returns: -- Cond: -- */ void PUBLIC _SharedFree( PVOID pv) { ASSERT(pv); if (pv) { HeapFree(g_hSharedHeap, 0, pv); } } /*---------------------------------------------------------- Purpose: Returns the allocated size of a block Returns: see above Cond: -- */ DWORD PUBLIC SharedGetSize( PVOID pv) { return HeapSize(g_hSharedHeap, 0, pv); } /*---------------------------------------------------------- Purpose: Copies psz into *ppszBuf. Will alloc or realloc *ppszBuf accordingly. If psz is NULL, this function frees *ppszBuf. This is the preferred method of freeing the allocated buffer. Returns: TRUE on success Cond: -- */ BOOL PUBLIC SharedSetString( LPSTR * ppszBuf, LPCSTR psz) // NULL to free *ppszBuf { BOOL bRet; ASSERT(ppszBuf); // Free the buffer? if (!psz) { // Yes if (ppszBuf) { SharedFree(*ppszBuf); *ppszBuf = NULL; } bRet = TRUE; } else { // No; (re)allocate and set buffer DWORD cb = CbFromCch(lstrlen(psz)+CCH_NUL); LPSTR pszT = SharedReAlloc(*ppszBuf, cb); if (pszT) { *ppszBuf = pszT; lstrcpy(*ppszBuf, psz); bRet = TRUE; } else bRet = FALSE; } return bRet; } #endif // NOSHAREDHEAP // // Memory tracking functions // #ifdef DEBUG typedef struct _HEAPTRACE { DWORD cAlloc; DWORD cFailure; DWORD cReAlloc; DWORD cbMaxTotal; DWORD cCurAlloc; DWORD cbCurTotal; } HEAPTRACE; HEAPTRACE g_htSync = {0}; // Start of zero... #endif // DEBUG /*---------------------------------------------------------- Purpose: Allocate from a heap. Returns: pointer to block of memory NULL (if out of memory) Cond: -- */ LPVOID PUBLIC MemAlloc( HANDLE hheap, DWORD cb) { LPVOID lp; if (hheap) { lp = HeapAlloc(hheap, HEAP_ZERO_MEMORY, cb); } else { lp = GAlloc(cb); } if (lp == NULL) { DEBUG_CODE( g_htSync.cFailure++; ) return NULL; } #ifdef DEBUG // Update counts. g_htSync.cAlloc++; g_htSync.cCurAlloc++; g_htSync.cbCurTotal += cb; if (g_htSync.cbCurTotal > g_htSync.cbMaxTotal) g_htSync.cbMaxTotal = g_htSync.cbCurTotal; #endif return lp; } /*---------------------------------------------------------- Purpose: Reallocate a block of memory in a given heap. Returns: Pointer to reallocated block NULL (if out of memory) Cond: -- */ LPVOID PUBLIC MemReAlloc( HANDLE hheap, LPVOID pb, DWORD cb) { LPVOID lp; DEBUG_CODE( DWORD cbOld; ) if (hheap) { DEBUG_CODE( cbOld = HeapSize(hheap, 0, pb); ) lp = HeapReAlloc(hheap, HEAP_ZERO_MEMORY, pb, cb); } else { if (pb) { DEBUG_CODE( cbOld = GGetSize(pb); ) lp = GReAlloc(pb, cb); } else { DEBUG_CODE( cbOld = 0; ) lp = GAlloc(cb); } } if (lp == NULL) { DEBUG_CODE( g_htSync.cFailure++; ) return NULL; } #ifdef DEBUG // Update counts. g_htSync.cReAlloc++; g_htSync.cbCurTotal += cb - cbOld; if (g_htSync.cbCurTotal > g_htSync.cbMaxTotal) g_htSync.cbMaxTotal = g_htSync.cbCurTotal; #endif return lp; } /*---------------------------------------------------------- Purpose: Free block of memory in heap. Returns: TRUE FALSE (if failure) Cond: -- */ BOOL PUBLIC MemFree( HANDLE hheap, LPVOID pb) { BOOL fRet; DEBUG_CODE( DWORD cbOld; ) if (hheap) { DEBUG_CODE( cbOld = HeapSize(hheap, 0, pb); ) fRet = HeapFree(hheap, 0, pb); } else { DEBUG_CODE( cbOld = GGetSize(pb); ) GFree(pb); fRet = TRUE; } #ifdef DEBUG if (fRet) { // Update counts. g_htSync.cCurAlloc--; g_htSync.cbCurTotal -= cbOld; } #endif return fRet; } /*---------------------------------------------------------- Purpose: Returns the size of the given block. Returns: size in bytes Cond: -- */ DWORD PUBLIC MemSize( HANDLE hheap, LPVOID pb) { if (hheap) return (DWORD)HeapSize(hheap, 0, pb); else return (DWORD)GGetSize(pb); } #endif // WIN32 ////////////////////////////////////////////////////////////////// #ifndef NODA /*---------------------------------------------------------- Purpose: Private alloc for pointer array functions. Returns: pointer to block of memory NULL (if out of memory) Cond: -- */ LPVOID PRIVATE PrvAlloc( DWORD dwFlags, // PAF_* flags HANDLE hheap, DWORD cb) { LPVOID lp; ASSERT(PAF_SHARED == SAF_SHARED); if (IsFlagSet(dwFlags, PAF_SHARED)) { lp = SharedAlloc(cb); } else { lp = MemAlloc(hheap, cb); } return lp; } // Heapsort is a bit slower, but it doesn't use any stack or memory... // Mergesort takes a bit of memory (O(n)) and stack (O(log(n)), but very fast... // #ifdef WIN32 #define MERGESORT #else #define USEHEAPSORT #endif #ifdef DEBUG #define SA_MAGIC ('S' | ('A' << 256)) #define IsSA(psa) ((psa) && (psa)->magic == SA_MAGIC) #define PA_MAGIC ('P' | ('A' << 256)) #define IsPA(ppa) ((ppa) && (ppa)->magic == PA_MAGIC) #else #define IsSA(psa) #define IsPA(ppa) #endif typedef struct { PVOID * pp; PFNPACOMPARE pfnCmp; LPARAM lParam; int cp; #ifdef MERGESORT PVOID * ppT; #endif } SORTPARAMS; // // Structure Array // typedef struct _SA { // NOTE: The following field MUST be defined at the beginning of the // structure in order for SAGetCount() to work. DWORD cItem; // number of elements in sa PVOID aItem; // memory for elements DWORD cItemAlloc; // number items which fit in aItem DWORD cbItem; // size of each item DWORD cItemGrow; // number items to grow cItemAlloc by DWORD dwFlags; HANDLE hheap; #ifdef DEBUG UINT magic; #endif } SA; #define SA_PITEM(psa, index) ((PVOID)(((char FAR*)(psa)->aItem) + ((index) * (psa)->cbItem))) /*---------------------------------------------------------- Purpose: Create a structure array. Returns: TRUE FALSE (if out of memory or invalid parameters) Cond: -- */ BOOL PUBLIC SACreateEx( PHSA phsa, DWORD cbItem, DWORD cItemGrow, HANDLE hheap, // Must be non-NULL if SAF_HEAP set DWORD dwFlags) { HSA psa; ASSERT(phsa); ASSERT(0 < cbItem); psa = PrvAlloc(dwFlags, hheap, sizeof(SA)); if (IsFlagSet(dwFlags, PAF_SHARED)) hheap = g_hSharedHeap; if (psa) { psa->cItem = 0; psa->cItemAlloc = 0; psa->cbItem = cbItem; psa->cItemGrow = (0 == cItemGrow ? 1 : cItemGrow); psa->aItem = NULL; psa->dwFlags = dwFlags; psa->hheap = hheap; #ifdef DEBUG psa->magic = SA_MAGIC; #endif } *phsa = psa; return NULL != psa; } /*---------------------------------------------------------- Purpose: Destroys a structure array. Returns: Cond: -- */ BOOL PUBLIC SADestroyEx( HSA psa, PFNSAFREE pfnFree, LPARAM lParam) { ASSERT(IsSA(psa)); if (psa == NULL) // allow NULL for low memory cases, still assert return TRUE; if (psa->aItem) { if (pfnFree) { DWORD i = SAGetCount(psa); while (0 < i) { i--; // Caller should not free the actual pointer being // passed, only the contents! pfnFree(SA_PITEM(psa, i), lParam); } } if (!MemFree(psa->hheap, psa->aItem)) return FALSE; } #ifdef DEBUG psa->cItem = 0; psa->cItemAlloc = 0; psa->cbItem = 0; psa->magic = 0; #endif return MemFree(psa->hheap, psa); } /*---------------------------------------------------------- Purpose: Copy structure at index into buffer. Returns: TRUE FALSE Cond: -- */ BOOL PUBLIC SAGetItem( HSA psa, DWORD index, PVOID pitem) { ASSERT(IsSA(psa)); ASSERT(pitem); if (SA_ERR == index || index >= psa->cItem) { TRACE_MSG(TF_ERROR, "SA: Invalid index: %lu", index); return FALSE; } hmemcpy(pitem, SA_PITEM(psa, index), psa->cbItem); return TRUE; } /*---------------------------------------------------------- Purpose: Get pointer to structure in array Returns: TRUE (if the index is within range) Cond: -- */ BOOL PUBLIC SAGetItemPtr( HSA psa, DWORD index, LPVOID * ppv) { BOOL bRet; ASSERT(IsSA(psa)); ASSERT(ppv); bRet = !(SA_ERR == index || index >= psa->cItem); if (bRet) { *ppv = SA_PITEM(psa, index); } else { TRACE_MSG(TF_ERROR, "SA: Invalid index: %lu", index); *ppv = NULL; } return bRet; } /*---------------------------------------------------------- Purpose: Set item Returns: Cond: -- */ BOOL PUBLIC SASetItem( HSA psa, DWORD index, PVOID pitem) { ASSERT(pitem); ASSERT(IsSA(psa)); if (SA_ERR == index) { TRACE_MSG(TF_ERROR, "SA: Invalid index: %lu", index); return FALSE; } if (index >= psa->cItem) { if (index + 1 > psa->cItemAlloc) { int cItemAlloc = (((index + 1) + psa->cItemGrow - 1) / psa->cItemGrow) * psa->cItemGrow; PVOID aItemNew = MemReAlloc(psa->hheap, psa->aItem, cItemAlloc * psa->cbItem); if (!aItemNew) return FALSE; psa->aItem = aItemNew; psa->cItemAlloc = cItemAlloc; } psa->cItem = index + 1; } hmemcpy(SA_PITEM(psa, index), pitem, psa->cbItem); return TRUE; } /*---------------------------------------------------------- Purpose: Inserts the given item. If *piIndex is greater than the current size of the array, the item is appended to the end. Otherwise, the item is inserted at *piIndex. If piIndex is NULL, the item is appended to the end. Use SASetItem to place an item at a specified index, regardless of the array size. When this function completes successfully, it sets *piIndex to the index that the item really gets inserted at. Otherwise, it sets *piIndex to SA_ERR. Returns: TRUE (on successful insertion) FALSE Cond: -- */ BOOL PUBLIC SAInsertItem( HSA psa, LPDWORD pindex, // May be NULL PVOID pitem) { BOOL bRet = TRUE; // assume success ASSERT(pitem); ASSERT(IsSA(psa)); if (pindex && SA_ERR == *pindex) { TRACE_MSG(TF_ERROR, "SA: Invalid index: %lu", *pindex); bRet = FALSE; } else { DWORD index; if (NULL == pindex || *pindex > psa->cItem) index = psa->cItem; else index = *pindex; if (psa->cItem + 1 > psa->cItemAlloc) { PVOID aItemNew = MemReAlloc(psa->hheap, psa->aItem, (psa->cItemAlloc + psa->cItemGrow) * psa->cbItem); if (!aItemNew) bRet = FALSE; else { psa->aItem = aItemNew; psa->cItemAlloc += psa->cItemGrow; } } if (bRet) { // If we are inserting, we need to slide everybody up if (index < psa->cItem) { hmemcpy(SA_PITEM(psa, index + 1), SA_PITEM(psa, index), (psa->cItem - index) * psa->cbItem); } psa->cItem++; hmemcpy(SA_PITEM(psa, index), pitem, psa->cbItem); if (pindex) *pindex = index; } else if (pindex) { *pindex = SA_ERR; } } return bRet; } /*---------------------------------------------------------- Purpose: Returns: Cond: -- */ BOOL PUBLIC SADeleteItem( HSA psa, DWORD index) { ASSERT(IsSA(psa)); if (SA_ERR == index || index >= psa->cItem) { TRACE_MSG(TF_ERROR, "SA: Invalid index: %lu", index); return FALSE; } if (index < psa->cItem - 1) { hmemcpy(SA_PITEM(psa, index), SA_PITEM(psa, index + 1), (psa->cItem - (index + 1)) * psa->cbItem); } psa->cItem--; if (psa->cItemAlloc - psa->cItem > psa->cItemGrow) { PVOID aItemNew = MemReAlloc(psa->hheap, psa->aItem, (psa->cItemAlloc - psa->cItemGrow) * psa->cbItem); ASSERT(aItemNew); psa->aItem = aItemNew; psa->cItemAlloc -= psa->cItemGrow; } return TRUE; } /*---------------------------------------------------------- Purpose: Returns: Cond: -- */ BOOL PUBLIC SADeleteAllItems( HSA psa) { ASSERT(IsSA(psa)); if (psa->aItem) { MemFree(psa->hheap, psa->aItem); } psa->aItem = NULL; psa->cItem = psa->cItemAlloc = 0; return TRUE; } //================== Dynamic pointer array implementation =========== typedef struct _PA { // NOTE: The following two fields MUST be defined in this order, at // the beginning of the structure in order for the macro APIs to work. // DWORD cp; DWORD dwAlignPad; PVOID * pp; HANDLE hheap; // Heap to allocate from if NULL use shared DWORD cpAlloc; DWORD cpGrow; DWORD dwFlags; #ifdef DEBUG UINT magic; #endif } PA; /*---------------------------------------------------------- Purpose: Creates a pointer array. Returns: TRUE FALSE (if out of memory) Cond: -- */ BOOL PUBLIC PACreateEx( PHPA phpa, DWORD cpGrow, HANDLE hheap, // Must be non-null if PAF_HEAP set DWORD dwFlags) // PAF_* { HPA ppa; ASSERT(phpa); ppa = PrvAlloc(dwFlags, hheap, sizeof(PA)); if (IsFlagSet(dwFlags, PAF_SHARED)) hheap = g_hSharedHeap; if (ppa) { ppa->dwFlags = dwFlags; ppa->cp = 0; ppa->cpAlloc = 0; ppa->cpGrow = (cpGrow < 8 ? 8 : cpGrow); ppa->pp = NULL; #ifdef WIN32 ppa->hheap = hheap; #else ppa->hheap = NULL; #endif #ifdef DEBUG ppa->magic = PA_MAGIC; #endif } *phpa = ppa; return NULL != ppa; } /*---------------------------------------------------------- Purpose: Destroy a pointer array, and call the given pfnFree function for each element in the array. Returns: TRUE FALSE (on failure) Cond: -- */ BOOL PUBLIC PADestroyEx( HPA ppa, PFNPAFREE pfnFree, LPARAM lParam) { ASSERT(IsPA(ppa)); if (ppa == NULL) // allow NULL for low memory cases, still assert return TRUE; if (ppa->pp) { if (pfnFree) { DWORD i = PAGetCount(ppa); while (0 < i) { i--; pfnFree(PAFastGetPtr(ppa, i), lParam); } } if (!MemFree(ppa->hheap, ppa->pp)) return FALSE; } #ifdef DEBUG ppa->cp = 0; ppa->cpAlloc = 0; ppa->magic = 0; #endif return MemFree(ppa->hheap, ppa); } /*---------------------------------------------------------- Purpose: Clone a pointer array. If *phpa was previously allocated, this function simply grows the array to the appropriate size before copying the contents of the array. Returns: TRUE FALSE (if out of memory) Cond: -- */ BOOL PUBLIC PAClone( PHPA phpa, HPA ppa) { BOOL bRet; HPA ppaNew; ASSERT(phpa); if (NULL == *phpa) { bRet = PACreateEx(&ppaNew, ppa->cpGrow, ppa->hheap, ppa->dwFlags); } else { ppaNew = *phpa; bRet = TRUE; } if (bRet) { bRet = PAGrow(ppaNew, ppa->cpAlloc); if (!bRet) { if (NULL == *phpa) PADestroy(ppaNew); } else { ppaNew->cp = ppa->cp; hmemcpy(ppaNew->pp, ppa->pp, ppa->cp * sizeof(PVOID)); *phpa = ppaNew; } } return bRet; } /*---------------------------------------------------------- Purpose: Get a pointer stored in index Returns: TRUE FALSE (if index out of range) Cond: -- */ BOOL PUBLIC PAGetPtr( HPA ppa, DWORD index, LPVOID * ppv) { BOOL bRet; ASSERT(IsPA(ppa)); ASSERT(ppv); bRet = !(PA_ERR == index || index >= ppa->cp); if (bRet) { *ppv = ppa->pp[index]; } else { *ppv = NULL; } return bRet; } /*---------------------------------------------------------- Purpose: Gets the index that pointer p is stored at Returns: index Cond: -- */ BOOL PUBLIC PAGetPtrIndex( HPA ppa, PVOID p, LPDWORD pindex) { BOOL bRet = FALSE; PVOID * pp; PVOID * ppMax; ASSERT(IsPA(ppa)); ASSERT(pindex); if (ppa->pp) { pp = ppa->pp; ppMax = pp + ppa->cp; for ( ; pp < ppMax; pp++) { if (*pp == p) { *pindex = (DWORD)(pp - ppa->pp); bRet = TRUE; break; } } } if (!bRet) *pindex = PA_ERR; return bRet; } /*---------------------------------------------------------- Purpose: Grow the pointer array Returns: Cond: -- */ BOOL PUBLIC PAGrow( HPA ppa, DWORD cpAlloc) { ASSERT(IsPA(ppa)); if (cpAlloc > ppa->cpAlloc) { PVOID * ppNew; cpAlloc = ((cpAlloc + ppa->cpGrow - 1) / ppa->cpGrow) * ppa->cpGrow; if (ppa->pp) ppNew = (PVOID *)MemReAlloc(ppa->hheap, ppa->pp, cpAlloc * sizeof(PVOID)); else ppNew = (PVOID *)PrvAlloc(ppa->dwFlags, ppa->hheap, cpAlloc * sizeof(PVOID)); if (!ppNew) return FALSE; ppa->pp = ppNew; ppa->cpAlloc = cpAlloc; } return TRUE; } /*---------------------------------------------------------- Purpose: Store a pointer at index. Grows the array accordingly. Returns: TRUE FALSE (if out of memory) Cond: -- */ BOOL PUBLIC PASetPtr( HPA ppa, DWORD index, PVOID p) { ASSERT(IsPA(ppa)); if (PA_ERR == index) { TRACE_MSG(TF_ERROR, "PA: Invalid index: %lu", index); return FALSE; } if (index >= ppa->cp) { if (!PAGrow(ppa, index + 1)) return FALSE; ppa->cp = index + 1; } ppa->pp[index] = p; return TRUE; } /*---------------------------------------------------------- Purpose: Inserts the given item. If *piIndex is greater than the current size of the array, the item is appended to the end. Otherwise, the item is inserted at *piIndex. If piIndex is NULL, the item is appended to the end. Use SASetItem to place an item at a specified index, regardless of the array size. When this function completes successfully, it sets *piIndex to the index that the item really gets inserted at. Otherwise, it sets *piIndex to SA_ERR. Returns: TRUE (on successful insertion) FALSE Cond: -- */ BOOL PUBLIC PAInsertPtr( HPA ppa, LPDWORD pindex, // May be NULL PVOID p) { BOOL bRet; ASSERT(IsPA(ppa)); if (pindex && PA_ERR == *pindex) { TRACE_MSG(TF_ERROR, "PA: Invalid index: %lu", *pindex); bRet = FALSE; } else { DWORD index; bRet = TRUE; // assume success if (NULL == pindex || *pindex > ppa->cp) index = ppa->cp; else index = *pindex; // Make sure we have room for one more item // if (ppa->cp + 1 > ppa->cpAlloc) { bRet = PAGrow(ppa, ppa->cp + 1); } if (bRet) { // If we are inserting, we need to slide everybody up if (index < ppa->cp) { hmemcpy(&ppa->pp[index + 1], &ppa->pp[index], (ppa->cp - index) * sizeof(PVOID)); } ppa->pp[index] = p; ppa->cp++; if (pindex) *pindex = index; } else if (pindex) { *pindex = PA_ERR; } } return bRet; } /*---------------------------------------------------------- Purpose: Delete a pointer from index. Returns: the deleted pointer NULL (if index is out of range) Cond: -- */ PVOID PUBLIC PADeletePtr( HPA ppa, DWORD index) { PVOID p; ASSERT(IsPA(ppa)); if (PA_ERR == index || index >= ppa->cp) { TRACE_MSG(TF_ERROR, "PA: Invalid index: %lu", index); return NULL; } p = ppa->pp[index]; if (index < ppa->cp - 1) { hmemcpy(&ppa->pp[index], &ppa->pp[index + 1], (ppa->cp - (index + 1)) * sizeof(PVOID)); } ppa->cp--; if (ppa->cpAlloc - ppa->cp > ppa->cpGrow) { PVOID * ppNew; ppNew = MemReAlloc(ppa->hheap, ppa->pp, (ppa->cpAlloc - ppa->cpGrow) * sizeof(PVOID)); ASSERT(ppNew); ppa->pp = ppNew; ppa->cpAlloc -= ppa->cpGrow; } return p; } /*---------------------------------------------------------- Purpose: Delete all the pointers in the array. If pfnFree is non-NULL, this function will free each of the pointer elements in this array using pfnFree. Returns: TRUE FALSE Cond: -- */ BOOL PUBLIC PADeleteAllPtrsEx( HPA ppa, PFNPAFREE pfnFree, LPARAM lParam) { ASSERT(IsPA(ppa)); if (ppa->pp) { if (pfnFree) { int i = PAGetCount(ppa); while (0 < i) { i--; pfnFree(PAFastGetPtr(ppa, i), lParam); } } if (!MemFree(ppa->hheap, ppa->pp)) return FALSE; } ppa->pp = NULL; ppa->cp = ppa->cpAlloc = 0; return TRUE; } #ifdef USEQUICKSORT BOOL NEAR PAQuickSort2( DWORD i, DWORD j, SORTPARAMS FAR* psp) { PVOID * pp = psp->pp; LPARAM lParam = psp->lParam; PFNPACOMPARE pfnCmp = psp->pfnCmp; DWORD iPivot; PVOID pFirst; DWORD k; int result; iPivot = PA_ERR; pFirst = pp[i]; for (k = i + 1; k <= j; k++) { result = (*pfnCmp)(pp[k], pFirst, lParam); if (result > 0) { iPivot = k; break; } else if (result < 0) { iPivot = i; break; } } if (iPivot != PA_ERR) { DWORD l = i; DWORD r = j; PVOID pivot = pp[iPivot]; do { PVOID p; p = pp[l]; pp[l] = pp[r]; pp[r] = p; while ((*pfnCmp)(pp[l], pivot, lParam) < 0) l++; while ((*pfnCmp)(pp[r], pivot, lParam) >= 0) r--; } while (l <= r); if (l - 1 > i) PAQuickSort2(i, l - 1, psp); if (j > l) PAQuickSort2(l, j, psp); } return TRUE; } BOOL NEAR PAQuickSort( SORTPARAMS FAR* psp) { return PAQuickSort2(0, psp->cp - 1, psp); } #endif // USEQUICKSORT #ifdef USEHEAPSORT void NEAR PAHeapSortPushDown( DWORD first, DWORD last, SORTPARAMS FAR* psp) { PVOID * pp = psp->pp; LPARAM lParam = psp->lParam; PFNPACOMPARE pfnCmp = psp->pfnCmp; DWORD r; DWORD r2; r = first; while (r <= last / 2) { int wRTo2R; r2 = r * 2; wRTo2R = (*pfnCmp)(pp[r-1], pp[r2-1], lParam); if (r2 == last) { if (wRTo2R < 0) { Swap(pp[r-1], pp[r2-1]); } break; } else { int wR2toR21 = (*pfnCmp)(pp[r2-1], pp[r2+1-1], lParam); if (wRTo2R < 0 && wR2toR21 >= 0) { Swap(pp[r-1], pp[r2-1]); r = r2; } else if ((*pfnCmp)(pp[r-1], pp[r2+1-1], lParam) < 0 && wR2toR21 < 0) { Swap(pp[r-1], pp[r2+1-1]); r = r2 + 1; } else { break; } } } } BOOL NEAR PAHeapSort(SORTPARAMS FAR* psp) { PVOID * pp = psp->pp; DWORD c = psp->cp; DWORD i; for (i = c / 2; i >= 1; i--) PAHeapSortPushDown(i, c, psp); for (i = c; i >= 2; i--) { Swap(pp[0], pp[i-1]); PAHeapSortPushDown(1, i - 1, psp); } return TRUE; } #endif // USEHEAPSORT #if defined(MERGESORT) && defined(WIN32) #define SortCompare(psp, pp1, i1, pp2, i2) \ (psp->pfnCmp(pp1[i1], pp2[i2], psp->lParam)) // // This function merges two sorted lists and makes one sorted list. // psp->pp[iFirst, iFirst+cItes/2-1], psp->pp[iFirst+cItems/2, iFirst+cItems-1] // void NEAR PAMergeThem( SORTPARAMS FAR* psp, DWORD iFirst, DWORD cItems) { // // Notes: // This function is separated from PAMergeSort2() to avoid comsuming // stack variables. Never inline this. // DWORD cHalf = cItems/2; DWORD iIn1, iIn2, iOut; LPVOID * ppvSrc = &psp->pp[iFirst]; // Copy the first part to temp storage so we can write directly into // the final buffer. Note that this takes at most psp->cp/2 DWORD's hmemcpy(psp->ppT, ppvSrc, cHalf*sizeof(LPVOID)); for (iIn1=0, iIn2=cHalf, iOut=0;;) { if (SortCompare(psp, psp->ppT, iIn1, ppvSrc, iIn2) <= 0) { ppvSrc[iOut++] = psp->ppT[iIn1++]; if (iIn1==cHalf) { // We used up the first half; the rest of the second half // should already be in place break; } } else { ppvSrc[iOut++] = ppvSrc[iIn2++]; if (iIn2==cItems) { // We used up the second half; copy the rest of the first half // into place hmemcpy(&ppvSrc[iOut], &psp->ppT[iIn1], (cItems-iOut)*sizeof(LPVOID)); break; } } } } // // This function sorts a give list (psp->pp[iFirst,iFirst-cItems-1]). // void NEAR PAMergeSort2( SORTPARAMS FAR* psp, DWORD iFirst, DWORD cItems) { // // Notes: // This function is recursively called. Therefore, we should minimize // the number of local variables and parameters. At this point, we // use one local variable and three parameters. // DWORD cHalf; switch(cItems) { case 1: return; case 2: // Swap them, if they are out of order. if (SortCompare(psp, psp->pp, iFirst, psp->pp, iFirst+1) > 0) { psp->ppT[0] = psp->pp[iFirst]; psp->pp[iFirst] = psp->pp[iFirst+1]; psp->pp[iFirst+1] = psp->ppT[0]; } break; default: cHalf = cItems/2; // Sort each half PAMergeSort2(psp, iFirst, cHalf); PAMergeSort2(psp, iFirst+cHalf, cItems-cHalf); // Then, merge them. PAMergeThem(psp, iFirst, cItems); break; } } BOOL NEAR PAMergeSort( SORTPARAMS FAR* psp) { if (psp->cp == 0) return TRUE; // Note that we divide by 2 below; we want to round down psp->ppT = GAlloc(psp->cp/2 * sizeof(LPVOID)); if (!psp->ppT) return FALSE; PAMergeSort2(psp, 0, psp->cp); GFree(psp->ppT); return TRUE; } #endif // MERGESORT /*---------------------------------------------------------- Purpose: Sort the array. Returns: Cond: -- */ BOOL PUBLIC PASort( HPA ppa, PFNPACOMPARE pfnCmp, LPARAM lParam) { SORTPARAMS sp; sp.cp = ppa->cp; sp.pp = ppa->pp; sp.pfnCmp = pfnCmp; sp.lParam = lParam; #ifdef USEQUICKSORT return PAQuickSort(&sp); #endif #ifdef USEHEAPSORT return PAHeapSort(&sp); #endif #ifdef MERGESORT return PAMergeSort(&sp); #endif } /*---------------------------------------------------------- Purpose: Search for pFind in array. Returns: Cond: -- */ DWORD PUBLIC PASearch( HPA ppa, PVOID pFind, DWORD iStart, PFNPACOMPARE pfnCompare, LPARAM lParam, UINT options) { DWORD cp = PAGetCount(ppa); ASSERT(pfnCompare); ASSERT(PA_ERR != iStart); // Only allow these wierd flags if the list is sorted ASSERT((options & PAS_SORTED) || !(options & (PAS_INSERTBEFORE | PAS_INSERTAFTER))); if (!(options & PAS_SORTED)) { // Not sorted: do linear search. DWORD i; for (i = iStart; i < cp; i++) { if (0 == pfnCompare(pFind, PAFastGetPtr(ppa, i), lParam)) return i; } return PA_ERR; } else { // Search the array using binary search. If several adjacent // elements match the target element, the index of the first // matching element is returned. DWORD iRet = PA_ERR; // assume no match BOOL bFound = FALSE; int nCmp = 0; DWORD iLow = 0; // Don't bother using iStart for binary search DWORD iMid = 0; if (0 < cp) { DWORD iHigh = cp - 1; // (OK for cp == 0) while (iLow <= iHigh) { iMid = (iLow + iHigh) / 2; nCmp = pfnCompare(pFind, PAFastGetPtr(ppa, iMid), lParam); if (0 > nCmp) { // Account for the fact we are working with // unsigned values if (0 == iMid) break; iHigh = iMid - 1; // First is smaller } else if (0 < nCmp) iLow = iMid + 1; // First is larger else { // Match; search back for first match bFound = TRUE; while (0 < iMid) { if (0 != pfnCompare(pFind, PAFastGetPtr(ppa, iMid-1), lParam)) break; else iMid--; } break; } } } if (bFound) { ASSERT(0 <= iMid); iRet = iMid; } // Did the search fail AND // is one of the strange search flags set? if (!bFound && (options & (PAS_INSERTAFTER | PAS_INSERTBEFORE))) { // Yes; return the index where the target should be inserted // if not found if (0 < nCmp) // First is larger iRet = iLow; else iRet = iMid; // (We don't distinguish between the two flags anymore) } else if ( !(options & (PAS_INSERTAFTER | PAS_INSERTBEFORE)) ) { // Sanity check with linear search ASSERT(PASearch(ppa, pFind, iStart, pfnCompare, lParam, options & ~PAS_SORTED) == iRet); } return iRet; } } #endif // NODA #endif // NOMEM