windows-nt/Source/XPSP1/NT/enduser/stuff/itircl/common/util/blockmgr.c
2020-09-26 16:20:57 +08:00

662 lines
23 KiB
C

/*************************************************************************
* *
* 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 <mvopsys.h>
#include <misc.h>
#include <memory.h> // For _fmemcpy
#include <mem.h>
#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;
}