402 lines
11 KiB
C
402 lines
11 KiB
C
/* Copyright (c) 1998-1999 Microsoft Corporation */
|
|
/* @doc DMusic16
|
|
*
|
|
* @module Alloc.c - Memory allocation routines |
|
|
*
|
|
* This module provides memory allocation routines for DMusic16.DLL. It allows the MIDI input and
|
|
* output modules to allocated and free <c EVENT> structures.
|
|
*
|
|
* The allocated recognizes two types of events by size. If an event is create with 4 or less bytes
|
|
* of data, then it is allocated as a channel message. Channel message events are allocated one
|
|
* page at a time and kept in a free list.
|
|
*
|
|
* If the event size is greater than 4 bytes, then the event is a system exclusive message (or long
|
|
* data in the legacy API nomenclature). These events are allocated individually, one per page.
|
|
*
|
|
* All allocated memory is preceded with a <c SEGHDR>, which is used to identify the size and type
|
|
* of the segment and to keep it in a list. Since all events will be accessed at event time (in
|
|
* either a MIDI input callback or a timeSetEvent callback), all memory is automatically page
|
|
* locked.
|
|
*
|
|
* @globalv WORD | gsSegList |Selector of first segment in allocated list
|
|
* @globalv LPEVENT | glpFreeEventList | List of free 4-byte events
|
|
* @globalv LPEVENT | glpFreeBigEventList | List of free 4-byte events
|
|
*/
|
|
#include <windows.h>
|
|
#include <mmsystem.h>
|
|
#include <memory.h>
|
|
|
|
#include "dmusic16.h"
|
|
#include "debug.h"
|
|
|
|
STATIC WORD gsSegList;
|
|
STATIC LPEVENT glpFreeEventList;
|
|
STATIC LPEVENT glpFreeBigEventList;
|
|
|
|
/* Given a far pointer, get its selector.
|
|
*/
|
|
#define SEL_OF(lp) (WORD)((((DWORD)lp) >> 16) & 0xffff)
|
|
|
|
/* Given a far event pointer, get the far pointer to its segment headear.
|
|
*/
|
|
#define SEGHDR_OF(lp) ((LPSEGHDR)(((DWORD)lp) & 0xffff0000l))
|
|
|
|
STATIC BOOL RefillEventList(VOID);
|
|
STATIC LPSEGHDR AllocSeg(WORD cbSeg);
|
|
STATIC VOID FreeBigEvents(VOID);
|
|
STATIC VOID FreeSeg(LPSEGHDR lpSeg);
|
|
|
|
/* @func Called at DLL LibInit
|
|
*
|
|
* @comm
|
|
* Initializes all free lists to empty.
|
|
*
|
|
*/
|
|
VOID PASCAL
|
|
AllocOnLoad(VOID)
|
|
{
|
|
gsSegList = 0;
|
|
glpFreeEventList = NULL;
|
|
glpFreeBigEventList = NULL;
|
|
}
|
|
|
|
/* @func Called at DLL LibExit
|
|
*
|
|
* @comm
|
|
* Unlock and free all of the memory allocated.
|
|
*
|
|
* AllocOnUnload jettisons all memory the allocator has ever allocated.
|
|
* It assumes that all pointers to events will no longer ever be touched (i.e. all callbacks must
|
|
* have already been disabled by this point).
|
|
*/
|
|
VOID PASCAL
|
|
AllocOnExit(VOID)
|
|
{
|
|
WORD sSel;
|
|
WORD sSelNext;
|
|
LPSEGHDR lpSeg;
|
|
|
|
sSel = gsSegList;
|
|
|
|
while (sSel)
|
|
{
|
|
lpSeg = (LPSEGHDR)(((DWORD)sSel) << 16);
|
|
sSelNext = lpSeg->selNext;
|
|
|
|
FreeSeg(lpSeg);
|
|
|
|
sSel = sSelNext;
|
|
}
|
|
|
|
/* This just invalidated both free lists as well as the segment list
|
|
*/
|
|
gsSegList = 0;
|
|
glpFreeEventList = NULL;
|
|
glpFreeBigEventList = NULL;
|
|
}
|
|
|
|
/* @func Allocate an event of a given size
|
|
*
|
|
* @rdesc Returns a far pointer to the event or NULL if memory could not be allocated.
|
|
*
|
|
* @comm
|
|
*
|
|
* This function is not callable at interrupt time.
|
|
*
|
|
* This function is called to allocate a single event. The event will be allocated from
|
|
* page-locked memory and filled with the given event data.
|
|
*
|
|
* Events are classified as normal events, which contain channel messages, and big events,
|
|
* which contain SysEx data. The two are distinguished by their size: any event containing
|
|
* a DWORD of data or less is a normal event.
|
|
*
|
|
* Since channel messages comprise most of the MIDI stream, allocation of these events is optimized.
|
|
* A segment is allocated containing approximately one page worth (4k) of 4-byte events. These
|
|
* events are doled out of a free pool, which only occasionally needs to be refilled from system
|
|
* memory.
|
|
*
|
|
* Big events are allocated on an as-needed basis. When they have been free'd by a call to FreeEvent,
|
|
* they are placed on a special free list. This list is used to find memory for future big events,
|
|
* and is occasionally free'd back to Windows on a call to AllocEvent in order to minimize the
|
|
* amount of page-locked memory in use.
|
|
*/
|
|
LPEVENT PASCAL
|
|
AllocEvent(
|
|
DWORD msTime, /* @parm The absolute time based on timeGetTime() of the event */
|
|
QUADWORD rtTime, /* @parm The absolute time based on the IRferenceClock in 100ns units */
|
|
WORD cbEvent) /* @parm The number of bytes of event data in pbData */
|
|
{
|
|
LPEVENT lpEvent;
|
|
LPEVENT lpEventPrev;
|
|
LPEVENT lpEventCurr;
|
|
LPSEGHDR lpSeg;
|
|
|
|
/* Check for big event first (Sysex)
|
|
*/
|
|
if (cbEvent > sizeof(DWORD))
|
|
{
|
|
/* First see if we have an event that will work already
|
|
*/
|
|
lpEventPrev = NULL;
|
|
lpEventCurr = glpFreeBigEventList;
|
|
|
|
while (lpEventCurr)
|
|
{
|
|
if (SEGHDR_OF(lpEventCurr)->cbSeg >= sizeof(EVENT) + cbEvent)
|
|
{
|
|
break;
|
|
}
|
|
lpEventPrev = lpEventCurr;
|
|
lpEventCurr = lpEventCurr->lpNext;
|
|
}
|
|
|
|
if (lpEventCurr)
|
|
{
|
|
/* Remove this event from the list and use it
|
|
*/
|
|
if (lpEventPrev)
|
|
{
|
|
lpEventPrev->lpNext = lpEventCurr->lpNext;
|
|
}
|
|
else
|
|
{
|
|
glpFreeBigEventList = lpEventCurr->lpNext;
|
|
}
|
|
|
|
lpEventCurr->lpNext = NULL;
|
|
}
|
|
else
|
|
{
|
|
/* Nope, need to allocate one
|
|
*/
|
|
lpSeg = AllocSeg(sizeof(EVENT) + cbEvent);
|
|
if (NULL == lpSeg)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
lpEventCurr = (LPEVENT)(lpSeg + 1);
|
|
}
|
|
|
|
lpEventCurr->msTime = msTime;
|
|
lpEventCurr->rtTime = rtTime;
|
|
lpEventCurr->wFlags = 0;
|
|
lpEventCurr->cbEvent = cbEvent;
|
|
|
|
return lpEventCurr;
|
|
}
|
|
|
|
/* BUGBUG How often???
|
|
*/
|
|
FreeBigEvents();
|
|
|
|
/* Normal event. Pull it off the free list (refill if needed) and fill it in.
|
|
*/
|
|
if (NULL == glpFreeEventList)
|
|
{
|
|
if (!RefillEventList())
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
lpEvent = glpFreeEventList;
|
|
glpFreeEventList = lpEvent->lpNext;
|
|
|
|
lpEvent->msTime = msTime;
|
|
lpEvent->rtTime = rtTime;
|
|
lpEvent->wFlags = 0;
|
|
lpEvent->cbEvent = cbEvent;
|
|
|
|
return lpEvent;
|
|
}
|
|
|
|
/* @func Free an event back to its appropriate free list
|
|
*
|
|
* @comm
|
|
*
|
|
* FreeEvent makes no system calls; it simply places the given event back on the correct
|
|
* free list. If the event needs to be actually free'd, that will be done at a later time
|
|
* in user mode.
|
|
*/
|
|
VOID PASCAL
|
|
FreeEvent(
|
|
LPEVENT lpEvent) /* @parm The event to free */
|
|
{
|
|
LPSEGHDR lpSeg;
|
|
|
|
lpSeg = SEGHDR_OF(lpEvent);
|
|
if (lpSeg->wFlags & SEG_F_4BYTE_EVENTS)
|
|
{
|
|
lpEvent->lpNext = glpFreeEventList;
|
|
glpFreeEventList = lpEvent;
|
|
}
|
|
else
|
|
{
|
|
lpEvent->lpNext = glpFreeBigEventList;
|
|
glpFreeBigEventList = lpEvent;
|
|
}
|
|
}
|
|
|
|
/* @func Refill the free list of normal events
|
|
*
|
|
* @rdesc Returns TRUE if the list was refilled or FALSE if there was no memory.
|
|
*
|
|
* @comm
|
|
*
|
|
* This routine is not callable from interrupt time.
|
|
*
|
|
* Allocate one page-sized segment of normal events and add them to the free list.
|
|
*
|
|
*/
|
|
STATIC BOOL
|
|
RefillEventList(VOID)
|
|
{
|
|
LPSEGHDR lpSeg;
|
|
LPEVENT lpEvent;
|
|
UINT cbEvent;
|
|
UINT idx;
|
|
|
|
cbEvent = sizeof(EVENT) + sizeof(DWORD);
|
|
lpSeg = AllocSeg(C_PER_SEG * cbEvent);
|
|
if (NULL == lpSeg)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
lpSeg->wFlags = SEG_F_4BYTE_EVENTS;
|
|
|
|
/* Put the events into the free pool
|
|
*/
|
|
lpEvent = (LPEVENT)(lpSeg + 1);
|
|
|
|
for (idx = C_PER_SEG - 1; idx; --idx)
|
|
{
|
|
lpEvent->lpNext = (LPEVENT)(((LPBYTE)lpEvent) + cbEvent);
|
|
lpEvent = lpEvent->lpNext;
|
|
}
|
|
|
|
lpEvent->lpNext = glpFreeEventList;
|
|
glpFreeEventList = (LPEVENT)(lpSeg + 1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* @func Free all big events
|
|
*
|
|
* @comm
|
|
*
|
|
* This function is not callable at interrupt time.
|
|
*
|
|
* This function frees all big events on the free big event list. Free big events are those
|
|
* with event data sizes of more than one DWORD; they are allocated one event per segment
|
|
* as needed rather than being pooled like channel messages.
|
|
*
|
|
* This function is called every now and then as a side effect of AllocEvent in order to
|
|
* free up the page-locked memory associated with completed big events.
|
|
*
|
|
*/
|
|
STATIC VOID
|
|
FreeBigEvents(VOID)
|
|
{
|
|
LPEVENT lpEvent;
|
|
LPEVENT lpEventNext;
|
|
LPSEGHDR lpSeg;
|
|
|
|
lpEvent = glpFreeBigEventList;
|
|
while (lpEvent)
|
|
{
|
|
lpEventNext = lpEvent->lpNext;
|
|
|
|
lpSeg = SEGHDR_OF(lpEvent);
|
|
FreeSeg(lpSeg);
|
|
|
|
lpEvent = lpEventNext;
|
|
}
|
|
|
|
glpFreeBigEventList = NULL;
|
|
}
|
|
|
|
/* @func Allocate a segment and put it into the list of allocated segments.
|
|
*
|
|
* @rdesc A far pointer to the segment header or NULL if the memory could not be allocated.
|
|
*
|
|
* @comm
|
|
*
|
|
* This function is not callable at interrupt time.
|
|
*
|
|
* This is the lowest-level allocation routine which actually calls Windows to allocate the memory.
|
|
* The caller is responsible for carving the memory into one or more events.
|
|
*
|
|
* The data area of the segment will be filled with zeroes.
|
|
*
|
|
* Since events are accessed at interrupt time (timeSetEvent callback), the memory is allocated and
|
|
* page locked.
|
|
*
|
|
* This routine also inserts the segment into the global list of allocated segments for cleanup.
|
|
*/
|
|
STATIC LPSEGHDR
|
|
AllocSeg(
|
|
WORD cbSeg) /* @parm The size of data needed in the segment, excluding the segment header */
|
|
{
|
|
HANDLE hSeg;
|
|
WORD sSegHdr;
|
|
LPSEGHDR lpSeg;
|
|
|
|
/* Allocate and page-lock a segment
|
|
* NOTE: GPTR contains zero-init
|
|
*/
|
|
cbSeg += sizeof(SEGHDR);
|
|
hSeg = GlobalAlloc(GPTR | GMEM_SHARE, cbSeg);
|
|
if (0 == hSeg)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
lpSeg = (LPSEGHDR)GlobalLock(hSeg);
|
|
if (NULL == lpSeg)
|
|
{
|
|
GlobalFree(sSegHdr);
|
|
return NULL;
|
|
}
|
|
|
|
sSegHdr = SEL_OF(lpSeg);
|
|
if (!GlobalSmartPageLock(sSegHdr))
|
|
{
|
|
GlobalUnlock(sSegHdr);
|
|
GlobalFree(sSegHdr);
|
|
return NULL;
|
|
}
|
|
|
|
lpSeg->hSeg = hSeg;
|
|
lpSeg->cbSeg = cbSeg;
|
|
|
|
lpSeg->selNext = gsSegList;
|
|
gsSegList = sSegHdr;
|
|
|
|
return lpSeg;
|
|
}
|
|
|
|
/* @func Free a segment back to Windows
|
|
*
|
|
* @comm
|
|
*
|
|
* This function is not callable at interrupt time.
|
|
*
|
|
* Just unlock the segment and free it. The calling cleanup code is assumed to have removed
|
|
* the segment from the global list of allocated segments.
|
|
*
|
|
*/
|
|
STATIC VOID FreeSeg(
|
|
LPSEGHDR lpSeg) /* @parm The segment to free */
|
|
{
|
|
WORD sSel = SEL_OF(lpSeg);
|
|
HANDLE hSeg;
|
|
|
|
hSeg = lpSeg->hSeg;
|
|
|
|
GlobalSmartPageUnlock(sSel);
|
|
GlobalUnlock(hSeg);
|
|
GlobalFree(hSeg);
|
|
}
|