
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993 Microsoft Corporation.
*       All rights reserved. 
*       This source code is only intended as a supplement to 
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the 
*       Microsoft samples programs.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: GMEM.C
*
* Memory utility functions.
*
* Functions:
*
* gmem_panic()
* gmem_init()
* gmem_get()
* gmem_free()
* gmem_freeall()
*
* Comments:
*
* Global heap functions - allocate and free many small
* pieces of memory by calling global alloc for large pieces
* and breaking them up. A heap contains a critical section, so
* multiple simultaneous calls to gmem_get and gmem_free will be
* protected.
*
* gmem_freeall should not be called until all other users have finished
* with the heap.
*
* Out-of-memory is not something we regard as normal.
* If we cannot allocate memory - we put up an abort-retry-ignore
* error, and only return from the function if the user selects ignore.
*
****************************************************************************/

#include <windows.h>
#include <memory.h>

#include "gutils.h"

int gmem_panic(void);


/* ensure BLKSIZE is multiple of sizeof(DWORD) */
#define BLKSIZE         64               /* blk size in bytes */
#define ALLOCSIZE       32768
#define NBLKS           (ALLOCSIZE / BLKSIZE)
#define MAPSIZE         (NBLKS / 8)
#define MAPLONGS        (MAPSIZE / sizeof(DWORD))
#define TO_BLKS(x)      (((x) + BLKSIZE - 1) / BLKSIZE)

typedef struct seghdr {
        HANDLE hseg;
        CRITICAL_SECTION critsec;
        struct seghdr FAR * pnext;
        long nblocks;
        DWORD segmap[MAPLONGS];
} SEGHDR, FAR * SEGHDRP;


/* anything above this size, we alloc directly from global heap */
#define MAXGALLOC       20000


/***************************************************************************
 * Function: gmem_init
 *
 * Purpose:
 *
 * init heap - create first segment
 */
HANDLE APIENTRY
gmem_init(void)
{
        HANDLE hNew;
        SEGHDRP hp;

        /* retry all memory allocations after calling gmem_panic */
        do {
                hNew = GlobalAlloc(GHND, ALLOCSIZE);
                if (hNew == NULL) {
                        if (gmem_panic() == IDIGNORE) {
                                return(NULL);
                        }
                }
        } while  (hNew == NULL);

        hp = (SEGHDRP) GlobalLock(hNew);
        if (hp == NULL) {
                return(NULL);
        }
        hp->hseg = hNew;
        InitializeCriticalSection(&hp->critsec);
        hp->pnext = NULL;
        gbit_init(hp->segmap, NBLKS);
        gbit_alloc(hp->segmap, 1, TO_BLKS(sizeof(SEGHDR)));
        hp->nblocks = NBLKS - TO_BLKS(sizeof(SEGHDR));

        return(hNew);
}

/***************************************************************************
 * Function: gmem_get
 *
 * Purpose:
 *
 * Get memory from heap
 */
LPSTR APIENTRY
gmem_get(HANDLE hHeap, int len)
{
        SEGHDRP chainp;
        HANDLE hNew;
        SEGHDRP hp;
        LPSTR chp;
        long nblks;
        long start;
        long nfound;


        /* the heap is always locked (in gmem_init)- so having got the
         * pointer, we can always safely unlock it
         */
        chainp = (SEGHDRP) GlobalLock(hHeap);
        GlobalUnlock(hHeap);

        if (len < 1) {
                return(NULL);
        }

        /*
         * too big to be worth allocing from heap - get from globalalloc
         */
        if (len > MAXGALLOC) {
                /* retry all memory allocations after calling gmem_panic */
                do {
                        hNew = GlobalAlloc(GHND, len);
                        if (hNew == NULL) {
                                if (gmem_panic() == IDIGNORE) {
                                        return(NULL);
                                }
                        }
                } while  (hNew == NULL);

                chp = GlobalLock(hNew);
                if (chp == NULL) {
                        return(NULL);
                }
                return(chp);
        }


        /*
         * get critical section during all access to the heap itself
         */
        EnterCriticalSection(&chainp->critsec);

        nblks = TO_BLKS(len + sizeof(HANDLE));

        for (hp = chainp; hp !=NULL; hp = hp->pnext) {
                if (hp->nblocks >= nblks) {
                        nfound = gbit_findfree(hp->segmap, nblks,NBLKS, &start);
                        if (nfound >= nblks) {
                                gbit_alloc(hp->segmap, start, nblks);
                                hp->nblocks -= nblks;

                                /* convert blocknr to pointer
                                 * store seg handle in block
                                 */
                                chp = (LPSTR) hp;
                                chp = &chp[ (start-1) * BLKSIZE];
                                * ( (HANDLE FAR *) chp) = hp->hseg;
                                chp += sizeof(HANDLE);

                                break;
                        }
                }
        }
        if (hp == NULL) {
                /* retry all memory allocations after calling gmem_panic */
                do {
                        hNew = GlobalAlloc(GHND, ALLOCSIZE);
                        if (hNew == NULL) {
                                if (gmem_panic() == IDIGNORE) {
                                        LeaveCriticalSection(&chainp->critsec);
                                        return(NULL);
                                }
                        }
                } while  (hNew == NULL);

                hp = (SEGHDRP) GlobalLock(hNew);
                if (hp == NULL) {
                        LeaveCriticalSection(&chainp->critsec);
                        return(NULL);
                }
                hp->pnext = chainp->pnext;
                hp->hseg = hNew;
                chainp->pnext = hp;
                gbit_init(hp->segmap, NBLKS);
                gbit_alloc(hp->segmap, 1, TO_BLKS(sizeof(SEGHDR)));
                hp->nblocks = NBLKS - TO_BLKS(sizeof(SEGHDR));
                nfound = gbit_findfree(hp->segmap, nblks, NBLKS, &start);
                if (nfound >= nblks) {
                        gbit_alloc(hp->segmap, start, nblks);
                        hp->nblocks -= nblks;

                        /* convert block nr to pointer */
                        chp = (LPSTR) hp;
                        chp = &chp[ (start-1) * BLKSIZE];
                        /* add a handle into the block and skip past */
                        * ( (HANDLE FAR *) chp) = hp->hseg;
                        chp += sizeof(HANDLE);
                }
        }
        LeaveCriticalSection(&chainp->critsec);
        memset(chp, 0, len);
        return(chp);
}

/***************************************************************************
 * Function: gmem_free
 *
 * Purpose:
 *
 * Free memory alloced
 */
void APIENTRY
gmem_free(HANDLE hHeap, LPSTR ptr, int len)
{
        SEGHDRP chainp;
        SEGHDRP hp;
        HANDLE hmem;
        long nblks, blknr;
        LPSTR chp;

        if (len < 1) {
                return;
        }

        /*
         * allocs greater than MAXGALLOC are too big to be worth
         * allocing from the heap - they will have been allocated
         * directly from globalalloc
         */
        if (len > MAXGALLOC) {
                hmem = GlobalHandle( (LPSTR) ptr);
                GlobalUnlock(hmem);
                GlobalFree(hmem);
                return;
        }

        chainp = (SEGHDRP) GlobalLock(hHeap);
        EnterCriticalSection(&chainp->critsec);


        /* just before the ptr we gave the user, is the handle to
         * the block
         */
        chp = (LPSTR) ptr;
        chp -= sizeof(HANDLE);
        hmem = * ((HANDLE FAR *) chp);
        hp = (SEGHDRP) GlobalLock(hmem);

        nblks = TO_BLKS(len + sizeof(HANDLE));

        /* convert ptr to block nr */
        blknr = TO_BLKS( (unsigned) (chp - (LPSTR) hp) ) + 1;

        gbit_free(hp->segmap, blknr, nblks);
        hp->nblocks += nblks;

        GlobalUnlock(hmem);

   LeaveCriticalSection(&chainp->critsec);
        GlobalUnlock(hHeap);

}

/***************************************************************************
 * Function: gmem_freeall
 *
 * Purpose:
 *
 * Free heap
 */
void APIENTRY
gmem_freeall(HANDLE hHeap)
{
        SEGHDRP chainp;
        HANDLE hSeg;

        chainp = (SEGHDRP) GlobalLock(hHeap);
        /* this segment is always locked - so we need to unlock
         * it here as well as below
         */
        GlobalUnlock(hHeap);

        /* finished with the critical section  -
         * caller must ensure that at this point there is no
         * longer any contention
         */
        DeleteCriticalSection(&chainp->critsec);

        while (chainp != NULL) {
                hSeg = chainp->hseg;
                chainp = chainp->pnext;
                GlobalUnlock(hSeg);
                GlobalFree(hSeg);
        }
}

/***************************************************************************
 * Function: gmem_panic
 *
 * Purpose:
 *
 * A memory allocation attempt has failed. Return IDIGNORE to ignore the
 * error and return NULL to the caller, and IDRETRY to retry the allocation
 * attempt.
 */
int
gmem_panic(void)
{
        int code;

        code = MessageBox(NULL, "Memory allocation failed", "Out Of Memory",
                        MB_ICONSTOP|MB_ABORTRETRYIGNORE);
        if (code == IDABORT) {
                /* abort this whole process */
                ExitProcess(1);
        } else {
                return(code);
        }
}
