/************************************************************************* * * * BLOCKMGR.C * * * * Copyright (C) Microsoft Corporation 1990-1994 * * All Rights reserved. * * * ************************************************************************** * * * Module Intent * * Memory block management. * * Consider the case of creating a string table. * * 1/ First we have to allocate a block of memory * * 2/ Then we copy all the strings into the block, until we run out * * space. * * 3/ Either we will increase the size of the memory block (using * * _frealloc(), which requires the block's size < 64K, or _halloc * * which require huge pointers, or we allocate another block of * * memory, and connected the 2nd block to the 1st block * * * * The purpose of this module is to providesimple interface when * * handling memory block in the second scenario. * * * * The block manager will allocate one initiale memory block, and as * * memory need arises, more block of memory are allocated transparently * * An example of how to use the memory block manager would be: * * * * lpBlock = BlockInitiate (BlockSize, wElemSize); * * for (i = 0; condition; i++) { * * if ((Array[i] = BlockCopy (lpBlock, Buffer, BufLen)) * * == NULL) * * Error(); * * } * * * * Advantages: * * - Caller doesn't have to worry about how much room is left * * - We can use the maximum memory space if needed to * * - We don't have to use huge pointers * * * * Comments: * * This scheme doesn't assume how memory are used/referenced. To * * satisfy all the needs, the memory blocks have to be locked * * permanently. This may cause OOM problems when the memory is * * extremely fragmented, and garbage collection is hampered by not * * being able to move the block around. The problem is minimized if * * the block's size is large (ie. minimize fragmentation), which is * * usually the case * * Anyway, loking and unlocking problem should go away on 32-bit * * and above system * ************************************************************************** * * * Written By : Binh Nguyen * * Current Owner: Binh Nguyen * * * **************************************************************************/ #include #include #include // For _fmemcpy #include #include <_mvutil.h> #ifdef _DEBUG static BYTE s_aszModule[] = __FILE__; // Used by error return functions. #endif /* Stamp to do some limited validation checking */ #define BLOCK_STAMP 1234 /************************************************************************* * * INTERNAL PRIVATE FUNCTIONS * All of them should be declared near *************************************************************************/ PRIVATE int PASCAL NEAR BlockInitialize (LPBLK, BLOCK FAR *); /************************************************************************* * * INTERNAL PUBLIC FUNCTIONS * All of them should be declared far, and included in some include file *************************************************************************/ PUBLIC LPB PASCAL FAR BlockReset (LPV); PUBLIC VOID PASCAL FAR BlockFree (LPV); PUBLIC LPV PASCAL FAR BlockInitiate (DWORD, WORD, WORD, int); PUBLIC LPV PASCAL FAR BlockCopy (LPV, LPB, DWORD, WORD); PUBLIC LPV PASCAL FAR BlockGetElement(LPV); PUBLIC int PASCAL FAR BlockGrowth (LPV); PUBLIC LPB PASCAL FAR BlockGetLinkedList(LPV); PUBLIC LPV PASCAL FAR GlobalLockedStructMemAlloc (DWORD); PUBLIC VOID PASCAL FAR GlobalLockedStructMemFree (HANDLE FAR *); /************************************************************************* * @doc INTERNAL RETRIEVAL * * @func VOID PASCAL NEAR | BlockThreadElement | * This function will thread all the elements of a memory block * into a linked list * * @parm LPB | lpbBuf | * Pointer to memory buffer * * @parm DWORD | BufSize | * Total buffer's size * * @parm WORD | wElemSize | * Element's size *************************************************************************/ PRIVATE VOID PASCAL NEAR BlockThreadElement (LPB lpbBuf, DWORD BufSize, WORD wElemSize) { register DWORD cElem; if (wElemSize == 0) return; for (cElem = BufSize / wElemSize; cElem > 1; cElem --) { *(LPB UNALIGNED *UNALIGNED)lpbBuf = lpbBuf + wElemSize; lpbBuf += wElemSize; } *(LPB UNALIGNED *UNALIGNED)lpbBuf = NULL; } /************************************************************************* * @doc INTERNAL RETRIEVAL * * @func int PASCAL FAR | BlockGrowth | * Create another memory block and link it into the block list * * @parm LPBLK | lpBlockHead | * Pointer to block manager node * * @rdesc S_OK, or E_OUTOFMEMORY, or E_INVALIDARG * * @comm This function should be called externally only in the case * of running out of threaded data. *************************************************************************/ PUBLIC int PASCAL FAR BlockGrowth (LPBLK lpBlockHead) { BLOCK FAR *lpBlock; DWORD BlockSize; if (lpBlockHead == NULL) return E_INVALIDARG; BlockSize = lpBlockHead->cBytePerBlock; /* Check to see if we already allocate the block. This happens * after calling BlockReset(), all the blocks are still there * unused and linked together */ if (lpBlockHead->lpCur->lpNext == NULL) { /* Ensure that we did not pass the limit number of blocks allowed */ if (lpBlockHead->cCurBlockCnt >= lpBlockHead->cMaxBlock) return E_OUTOFMEMORY; lpBlockHead->cCurBlockCnt ++; if (lpBlockHead->fFlag & USE_VIRTUAL_MEMORY) { #ifndef _MAC // {_MAC DWORD size = (DWORD)(BlockSize + sizeof(BLOCK)); if ((lpBlock = _VIRTUALALLOC(NULL, size, MEM_COMMIT, PAGE_READWRITE)) == NULL) { return E_OUTOFMEMORY; } _VIRTUALLOCK(lpBlock, size); #endif // } _MAC } else { /* Allocate a new block */ if ((lpBlock = GlobalLockedStructMemAlloc ((DWORD)(BlockSize + sizeof(BLOCK)))) == NULL) { return E_OUTOFMEMORY; } } lpBlock->wStamp = BLOCK_STAMP; } else lpBlock = lpBlockHead->lpCur->lpNext; /* Link the block into the list */ lpBlockHead->lpCur->lpNext = lpBlock; if (lpBlockHead->fFlag & THREADED_ELEMENT) { BlockThreadElement ((LPB)lpBlock + sizeof(BLOCK), BlockSize, lpBlockHead->wElemSize); } /* Update all information */ return BlockInitialize (lpBlockHead, lpBlock); } /************************************************************************* * @doc INTERNAL RETRIEVAL * * @func int PASCAL NEAR | BlockInitialize | * Iniitalize the fields of the block manager structure * * @parm LPBLK | lpBlockHead | * Pointer to the block manager structure * * @parm BLOCK FAR * | lpBlock | * Pointer to the block of memory * * @rdesc S_OK if succeeded, else other errors *************************************************************************/ PRIVATE int PASCAL NEAR BlockInitialize (LPBLK lpBlockHead, BLOCK FAR *lpBlock) { /* Validity check */ if (lpBlockHead == NULL || lpBlock == NULL) return E_INVALIDARG; /* Update all information */ lpBlockHead->lpCur = lpBlock; lpBlockHead->lpbCurLoc = (LPB)lpBlock + sizeof(BLOCK); lpBlockHead->lTotalSize += lpBlockHead->cBytePerBlock + sizeof(BLOCK); /* If the memory block is threaded, then set cByteLeft = 0. This * to ensure that whoever use threaded blocks have to manage their * own linked list blocks, and ensure that calls to BlockGetElement() * will ultimately fail */ lpBlockHead->cByteLeft = (lpBlockHead->fFlag & THREADED_ELEMENT) ? 0 : lpBlockHead->cBytePerBlock; return S_OK; } /************************************************************************* * @doc INTERNAL RETRIEVAL * * @func LPB PASCAL FAR | BlockReset | * Reset the block manager, ie. just start from beginning without * releasing the memory blocks * * @parm LPBLK | lpBlockHead | * Pointer to the block manager structure * * @rdesc Pointer to start of the buffer, or NULL if errors. This is to * ensure that if the block is threaded, we returned the pointer of * the first element in the list *************************************************************************/ PUBLIC LPB PASCAL FAR BlockReset (LPBLK lpBlockHead) { /* Check for block validity */ if (lpBlockHead == NULL || lpBlockHead->wStamp != BLOCK_STAMP) return NULL; /* Update all information, start from the beginning of the list */ lpBlockHead->lpCur = lpBlockHead->lpHead; lpBlockHead->lpbCurLoc = (LPB)lpBlockHead->lpCur + sizeof(BLOCK); lpBlockHead->lTotalSize = lpBlockHead->cBytePerBlock + sizeof(BLOCK); /* Do the threading if necessary */ if ((lpBlockHead->fFlag & THREADED_ELEMENT)) { BlockThreadElement(lpBlockHead->lpbCurLoc, lpBlockHead->cBytePerBlock, lpBlockHead->wElemSize); /* Ensure that BlockGetElement() will fail, ie. the user must * handle the linked list of elements himself. */ lpBlockHead->cByteLeft = 0; } else lpBlockHead->cByteLeft = lpBlockHead->cBytePerBlock; return lpBlockHead->lpbCurLoc; } /************************************************************************* * @doc INTERNAL RETRIEVAL * * @func VOID PASCAL FAR | BlockFree | * Free the block manager strucutre and all the memory blocks * associated with it * * @parm LPBLK | lpBlockHead | * Pointer to block manager structure *************************************************************************/ PUBLIC VOID PASCAL FAR BlockFree (LPBLK lpBlockHead) { BLOCK FAR *lpBlock; BLOCK FAR *lpNextBlock; /* Check for block validity */ if (lpBlockHead == NULL || lpBlockHead->wStamp != BLOCK_STAMP) return; /* Free all memory block associated with the block manager */ for (lpBlock = lpBlockHead->lpHead; lpBlock; lpBlock = lpNextBlock) { lpNextBlock = lpBlock->lpNext; /* Only free the block if it is valid */ if (lpBlock->wStamp == BLOCK_STAMP) { if (lpBlockHead->fFlag & USE_VIRTUAL_MEMORY) { #ifndef _MAC // { _MAC _VIRTUALUNLOCK (lpBlock, lpBlockHead->cBytePerBlock + sizeof(BLOCK)); _VIRTUALFREE (lpBlock, 0L, (MEM_DECOMMIT | MEM_RELEASE)); #endif // } _MAC } else GlobalLockedStructMemFree ((LPV)lpBlock); } } /* Free the block manager */ GlobalLockedStructMemFree((LPV)lpBlockHead); } /************************************************************************* * @doc INTERNAL RETRIEVAL * * @func LPV PASCAL FAR | BlockInitiate | * Initiate and allocate memory block for block management * * @parm DWORD | BlockSize | * Block's size. The block size should less than 0xffff - 16 - * sizeof(BLOCK) for 16-bit build * * @parm WORD | wElemSize | * Size of an element, useful for fixed size data structure * * @parm WORD | cMaxBlock * Maximum number of blocks that can be allocated. 0 means 64K * * @parm int | flag | * - THREADED_ELEMENT : if the elements are to be threaded together into a * linked list * - USE_VIRTUAL_MEMORY : if virtual memory is to be used * * @rdesc Return pointer to a block management structure, or NULL * if OOM *************************************************************************/ PUBLIC LPV PASCAL FAR BlockInitiate (DWORD BlockSize, WORD wElemSize, WORD cMaxBlock, int flag) { LPBLK lpBlockHead; BLOCK FAR *lpBlock; /* Check for valid size. We add * - sizeof(BLOCK) * - 16 bytes to ensure that we never cross the 64K limit boundary */ #ifndef _32BIT if (BlockSize >= (unsigned)0xffff - sizeof(BLOCK) - 16) return NULL; #endif /* Allocate a block head. All fields are zero's except when * initialized */ if ((lpBlockHead = GlobalLockedStructMemAlloc(sizeof(BLOCK_MGR))) == NULL) return NULL; /* Allocate a memory block */ if (flag & USE_VIRTUAL_MEMORY) { #ifndef _MAC // { _MAC DWORD size = (DWORD)(BlockSize + sizeof(BLOCK)); if ((lpBlockHead->lpHead = lpBlock = _VIRTUALALLOC(NULL, size, MEM_COMMIT, PAGE_READWRITE)) == NULL) { GlobalLockedStructMemFree((LPV)lpBlockHead); return NULL; } if (_VIRTUALLOCK(lpBlock, size) == 0) GetLastError(); #endif // } _MAC } else { if ((lpBlockHead->lpHead = lpBlock = GlobalLockedStructMemAlloc((DWORD)(BlockSize + sizeof(BLOCK)))) == NULL) { GlobalLockedStructMemFree((LPV)lpBlockHead); return NULL; } } lpBlock->wStamp = BLOCK_STAMP; /* Initialization block manager structure */ lpBlockHead->wStamp = BLOCK_STAMP; lpBlockHead->lpCur = lpBlock; lpBlockHead->cByteLeft = lpBlockHead->cBytePerBlock = BlockSize; lpBlockHead->lpbCurLoc = (LPB)lpBlock + sizeof(BLOCK); lpBlockHead->wElemSize = wElemSize; lpBlockHead->lTotalSize = BlockSize + sizeof(BLOCK); if (cMaxBlock == 0) lpBlockHead->cMaxBlock = 0xffff; else lpBlockHead->cMaxBlock = cMaxBlock; lpBlockHead->cCurBlockCnt = 1; /* We have 1 block in the list */ if ((lpBlockHead->fFlag = (WORD)flag) & THREADED_ELEMENT) { BlockThreadElement (lpBlockHead->lpbCurLoc, BlockSize, wElemSize); } return lpBlockHead; } /************************************************************************* * @doc INTERNAL RETRIEVAL * * @func LPV PASCAL FAR | BlockCopy | * Copy a buffer into the memory block. The function will allocate * more memory if needed * * @parm LPBLK | lpBlockHead | * Pointer to manager block * * @parm LPB | Buffer | * Buffer to be copied: if this is NULL, the alloc is done, but * no copy is performed. * * @parm DWORD | BufSize | * Size of the buffer * * @parm WORD | wStartOffset | * Offset of the start of the buffer. Extra memory is needed to * accomodate the offset. This is needed because we use have * {structure+buffer} structure. The starting offset would be * the size of the structure * * @rdesc * Return pointer to the {structure+buffer} memory block, or NULL * if OOM or bad argument *************************************************************************/ PUBLIC LPV PASCAL FAR BlockCopy (LPBLK lpBlockHead, LPB Buffer, DWORD BufSize, WORD wStartOffset) { LPB lpbRetBuf; DWORD wTotalLength = BufSize + wStartOffset; #ifdef _DEBUG if (lpBlockHead == NULL || lpBlockHead->wStamp != BLOCK_STAMP || BufSize == 0) return NULL; #endif // Block 4-byte alignement wTotalLength = (wTotalLength + 3) & (~3); /* Check for room */ if (wTotalLength > lpBlockHead->cByteLeft) { if (BlockGrowth (lpBlockHead) != S_OK || (wTotalLength > lpBlockHead->cByteLeft)) return NULL; } lpbRetBuf = lpBlockHead->lpbCurLoc; /* Do the copy */ if (Buffer) MEMCPY (lpbRetBuf + wStartOffset, Buffer, BufSize); /* Update the pointer and the number of bytes left */ lpBlockHead->lpbCurLoc += wTotalLength; lpBlockHead->cByteLeft -= wTotalLength; return (LPV)lpbRetBuf; } /************************************************************************* * @doc INTERNAL RETRIEVAL * * @func LPB PASCAL FAR | BlockGetLinkedList | * Return the pointer to the linked list of the element * * @parm LPBLK | lpBlockHead | * Pointer to block manager structure * * @rdesc NULL if bad argument, else pointer to the linked list. One * possible bad argument is that the block is not marked as threaded * in BlockInitiate() *************************************************************************/ PUBLIC LPB PASCAL FAR BlockGetLinkedList(LPBLK lpBlockHead) { if (lpBlockHead == NULL || lpBlockHead->wStamp != BLOCK_STAMP || (lpBlockHead->fFlag & THREADED_ELEMENT) == 0) return NULL; return lpBlockHead->lpbCurLoc; } /************************************************************************* * @doc INTERNAL RETRIEVAL * * @func LPV PASCAL FAR | BlockGetElement | * The function returns the pointer to the current element in the * buffer * * @parm LPBLK | lpBlockHead | * Pointer to block manager structure * * @rdesc pointer to current element, or NULL if OOM * * @comm After the call, all offsets/pointers are updated preparing * for the next call *************************************************************************/ PUBLIC LPV PASCAL FAR BlockGetElement(LPBLK lpBlockHead) { LPB lpbRetBuf; WORD wElemSize; #ifdef _DEBUG if (lpBlockHead == NULL || lpBlockHead->wStamp != BLOCK_STAMP) return NULL; #endif if ((wElemSize = lpBlockHead->wElemSize) == 0) return NULL; /* Check for room */ if (wElemSize > lpBlockHead->cByteLeft) { if ((BlockGrowth (lpBlockHead) != S_OK) || wElemSize > lpBlockHead->cByteLeft) return NULL; } /* Get the returned pointer */ lpbRetBuf = lpBlockHead->lpbCurLoc; /* Update the current pointer and the number of bytes left */ lpBlockHead->lpbCurLoc += wElemSize; lpBlockHead->cByteLeft -= wElemSize; return (LPV)lpbRetBuf; } /************************************************************************* * @doc INTERNAL INDEX RETRIEVAL * * @func LPV PASCAL FAR | GlobalLockedStructMemAlloc | * This function allocates and return a pointer to a block of * memory. The first element of the structure must be the handle * to this block of memory * * @parm WORD | size | * Size of the structure block. * * @rdesc NULL if OOM, or pointer to the structure *************************************************************************/ PUBLIC LPV PASCAL FAR GlobalLockedStructMemAlloc (DWORD size) { HANDLE hMem; HANDLE FAR *lpMem; if ((hMem = _GLOBALALLOC(DLLGMEM_ZEROINIT, (DWORD)size)) == 0) return NULL; lpMem = (HANDLE FAR *)_GLOBALLOCK(hMem); *lpMem = hMem; return (LPV)lpMem; } /************************************************************************* * @doc INTERNAL INDEX RETRIEVAL * * @func LPV PASCAL FAR | GlobalLockedStructMemFree | * This function free the block of memory pointed by lpMem. The * assumption here is that the 1st field of the block is the * handle to the block of memory. * * @parm WORD FAR * | lpMem | * Pointer to the block of memory to be freed *************************************************************************/ PUBLIC VOID PASCAL FAR GlobalLockedStructMemFree (HANDLE FAR *lpMem) { HANDLE hMem; if (lpMem == NULL || (hMem = (HANDLE)*lpMem) == 0) return; _GLOBALUNLOCK(hMem); _GLOBALFREE(hMem); } /************************************************************************* * @doc INTERNAL RETRIEVAL * * @func int PASCAL FAR | BlockGetOrdinalBlock | * Retrieve pointer to the start of i-th block that was allocated (starting at zero) * * @parm LPBLK | lpBlockHead | * Pointer to block manager node * * @rdesc Pointer to first elt in block if successful, NULL otherwise * * @comm * This is used to get fast random access to the i-th elt in * an append-only linked list. *************************************************************************/ PUBLIC LPB PASCAL FAR BlockGetOrdinalBlock (LPBLK lpBlockHead, WORD iBlock) { LPBLOCK lpBlock; if (lpBlockHead == NULL || lpBlockHead->wStamp != BLOCK_STAMP) return NULL; for (lpBlock = lpBlockHead->lpHead; iBlock && lpBlock; lpBlock = lpBlock->lpNext, iBlock--) ; return ((LPB)lpBlock + sizeof(BLOCK)); } PUBLIC LPVOID PASCAL FAR BlockGetBlock (LPBLK lpBlockHead, DWORD dwSize) { LPB lpbRetBuf; #ifdef _DEBUG if (lpBlockHead == NULL || lpBlockHead->wStamp != BLOCK_STAMP) return NULL; #endif // 4-byte alignment dwSize = (dwSize + 3) & (~3); /* Check for room */ if (dwSize > lpBlockHead->cByteLeft) { if ((BlockGrowth (lpBlockHead) != S_OK) || dwSize > lpBlockHead->cByteLeft) return NULL; } /* Get the returned pointer */ lpbRetBuf = lpBlockHead->lpbCurLoc; /* Update the current pointer and the number of bytes left */ lpBlockHead->lpbCurLoc += dwSize; lpBlockHead->cByteLeft -= dwSize; return (LPV)lpbRetBuf; } VOID PASCAL FAR SetBlockCount (LPBLK lpBlock, WORD count) { lpBlock->cMaxBlock = count; }