//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 1993.
//
//  File:       stdalloc.cxx
//
//  Contents:   16-bit OLE allocator
//
//  Classes:
//
//  Functions:
//
//  History:    3-07-94   kevinro   Ported from ole2.01 (16-bit)
//
//----------------------------------------------------------------------------

#include <headers.cxx>
#pragma hdrstop

#include <ole2sp.h>

/****** Standard Task/Shared Allocator **********************************/

#define NULLSAB ((__segment)0)

typedef __segment SAB;

// amount of space windows reserves at start of segment
#define cbWinRes	16

// amount of space LocalInit takes (somewhat empirical)
#define cbWinOH		(6+10+46)

// total overhead per global block
#define cbTotalOH	(cbWinRes + sizeof(StdAllocHdr) + cbWinOH + 32)

// maximum sized object in a SAB (fudged to include space for per-block
// overhead and most anything else we missed).
#define cbMaxSAB (0xfffe - cbTotalOH)

//
// 4chan: fix for missing __fpurecall? (still not sure what requires this)
//
extern "C" int __cdecl _fpurecall() {return 0;};

//+---------------------------------------------------------------------------
//
//  Class:      CStdMalloc ()
//
//  Purpose:    Standard task allocator
//
//  History:    3-04-94   kevinro   Created
//
//  Notes:
//
//----------------------------------------------------------------------------
class FAR CStdMalloc : public IMalloc
{
public:
	CStdMalloc(DWORD memctx)
	{
		m_refs = 1;
		m_pStdAllocHead = NULL;
		m_flags = (memctx == MEMCTX_TASK)? GMEM_MOVEABLE :
						   GMEM_MOVEABLE|GMEM_SHARE;
	}

	STDMETHOD(QueryInterface)(REFIID iid, void FAR* FAR* ppvObj)
	{
                VDATEPTROUT( ppvObj, LPVOID );
                *ppvObj = NULL;
                VDATEIID( iid );

		if (iid == IID_IUnknown || iid == IID_IMalloc) {
			*ppvObj = this;
			++m_refs;
			return NOERROR;
		} else {
			*ppvObj = NULL;
			return ReportResult(0, E_NOINTERFACE, 0, 0);
		}
	}
	
	STDMETHOD_(ULONG,AddRef)(void) { return ++m_refs; }
		
	STDMETHOD_(ULONG,Release)(void)
	{
		if (--m_refs == 0) {
			// free all memory (includes the memory for this class)
			FreeAllMem();
			return 0;
		} else
			return m_refs;
	}
		
    STDMETHOD_(void FAR*, Alloc) (ULONG cb);
    STDMETHOD_(void FAR*, Realloc) (void FAR* pv, ULONG cb);
    STDMETHOD_(void, Free) (void FAR* pv);
    STDMETHOD_(ULONG, GetSize) (void FAR* pv);
    STDMETHOD_(int, DidAlloc) (void FAR* pv);
    STDMETHOD_(void, HeapMinimize) ();

private:
	ULONG m_refs;
	UINT m_flags;

	#define STDALLOC_SIG 0x4D445453		// 'STDM'

	struct StdAllocHdr
	{
		ULONG m_Signature;
		HTASK m_hTask;					// task which owns this block
		StdAllocHdr FAR* m_pStdAllocNext;
	};

	StdAllocHdr FAR* m_pStdAllocHead;

	INTERNAL_(StdAllocHdr FAR*) MapBlockToSA(SAB sab) { return (StdAllocHdr FAR*)MAKELONG(cbWinRes, sab); }
	INTERNAL_(SAB) MapSAToBlock(StdAllocHdr FAR* pSA) { return (__segment)pSA; }
	INTERNAL_(void FAR*) AllocInBlock(SAB seg, UINT cb);
	INTERNAL_(SAB) AllocNewBlock(UINT cb);
	INTERNAL_(SAB) MapPtrToBlock(void FAR* pv);

	INTERNAL_(CStdMalloc FAR*) MoveSelf(LPVOID lpv);
	friend HRESULT STDAPICALLTYPE CoCreateStandardMalloc(DWORD memctx, IMalloc FAR* FAR* ppMalloc);


	INTERNAL_(void) FreeAllMem(void);
};


INTERNAL_(void FAR*) CStdMalloc::AllocInBlock(SAB sab, UINT cb)
{
#ifdef _DEBUG
	WINDEBUGINFO Olddebuginfo, debuginfo;
	
	//get initial debug state
	
	GetWinDebugInfo(&debuginfo, WDI_OPTIONS);
	Olddebuginfo = debuginfo;
	
	//turn off alerts (see bug 3502)
	debuginfo.dwOptions |= DBO_SILENT;
	SetWinDebugInfo(&debuginfo);
	
#endif	// _DEBUG

	// must make alloc of 0 mean alloc something; LocalAlloc fails on cb == 0
	if (0==cb)
		cb = 2;

	_asm push DS;
	_asm mov DS, sab;
	void NEAR *npv = (void NEAR*)LocalAlloc(LMEM_FIXED, cb);
	_asm pop DS;

#ifdef _DEBUG
	//restore Debug state
		SetWinDebugInfo(&Olddebuginfo);
#endif	// _DEBUG

    if (npv == NULL) // npv is near pointer
        return NULL; // returned value is far pointer

	return (void FAR*)MAKELONG(npv, sab);
}


INTERNAL_(SAB) CStdMalloc::AllocNewBlock(UINT cb)
{
	if (cb > cbMaxSAB)
		// overflow
		return NULLSAB;

	if (cb < 4096 - cbTotalOH)
		// minimum is 4k global block
		cb = 4096;
	else
		// size will be larger than 4K; must include total overhead
		cb += cbTotalOH;

	// allocate block and get segment value
	HGLOBAL h;
	if ((h = GlobalAlloc(m_flags, cb)) == NULL)
		return NULLSAB;

	UINT segment;
	segment = HIWORD(GlobalHandle(h));

	// init windows local heap
	if (!LocalInit(segment, cbWinRes + sizeof(StdAllocHdr), cb)) {
		GlobalFree(h);
		return NULLSAB;
	}

	// init block and put it on front of list
	StdAllocHdr FAR* pSA;
	pSA = MapBlockToSA((SAB)segment);
	pSA->m_Signature = STDALLOC_SIG;
	pSA->m_hTask = (m_flags & GMEM_SHARE) != 0 ? NULL : GetCurrentTask();
	pSA->m_pStdAllocNext = m_pStdAllocHead;
	m_pStdAllocHead = pSA;

	return (SAB)segment;
}


INTERNAL_(SAB) CStdMalloc::MapPtrToBlock(void FAR* pv)
{
	if (pv == NULL)
		return NULLSAB;

	StdAllocHdr FAR* pSA;
	pSA = MapBlockToSA((SAB)pv);
	if (pSA->m_Signature != STDALLOC_SIG)
		return NULLSAB;
	if (m_flags & GMEM_SHARE) {
		if (pSA->m_hTask != NULL)
			// owned by task; not shared memory
			return NULLSAB;
	} else {
		if (pSA->m_hTask != GetCurrentTask()) {
			// if not same task, not this allocator; if different task,
			// something is wrong.
			AssertSz(pSA->m_hTask == NULL, "pointer used in wrong task");
			return NULLSAB;
		}
	}

	return MapSAToBlock(pSA);
}


STDMETHODIMP_(void FAR*) CStdMalloc::Alloc(ULONG cb)
{
	if (cb > cbMaxSAB)
		// can't deal with objects larger than 64k
		return NULL;

	// try all sab in order (newest allocated sab on front)
	StdAllocHdr FAR* pSA = m_pStdAllocHead;
	while (pSA != NULL) {
		// try allocating the memory; if successful, return

		void FAR* pv;
		if ((pv = AllocInBlock(MapSAToBlock(pSA), (UINT)cb)) != NULL)
			return pv;

		pSA = pSA->m_pStdAllocNext;
	}

	SAB sab;
	if ((sab = AllocNewBlock((UINT)cb)) == NULLSAB)
		return NULL;

	// this should really succeed (i.e., an assert would be better)
	return AllocInBlock(sab, (UINT)cb);
}


STDMETHODIMP_(void FAR*) CStdMalloc::Realloc(void FAR* pv, ULONG cb)
{


	if (cb > cbMaxSAB)
		// can't deal with objects larger than 64k
		return NULL;

	SAB sab;
	if (pv == NULL)
		// same as allocating a new pointer
		return Alloc(cb);
	
	//VDATEPTRIN rejects NULL (changed by alexgo 8/3/93)	
	GEN_VDATEPTRIN( pv, int, (LPVOID)NULL );	
	if ((sab = MapPtrToBlock(pv)) == NULLSAB)
		// attempt to realloc a pointer from some other allocator
		return NULL;
	else if (cb == 0) {
		// Realloc(pv, 0) -> frees and returns NULL; this is C library behavior
		Free(pv);
		return NULL;
	}

	void NEAR* npv;
	npv = (void NEAR*)(ULONG)pv;

	// first try realloc within same sab
	Assert(sab != NULLSAB);
	{
		void NEAR* npvT;

		_asm push DS;
		_asm mov DS, sab;
		npvT = (void NEAR*)LocalReAlloc((HLOCAL)npv, (UINT)cb, LMEM_MOVEABLE);
		_asm pop DS;

		if (npvT != NULL)
			return (void FAR*)MAKELONG(npvT, sab);
	}

	// now try allocating new sab and copying
	void FAR* pvT;
	if ((pvT = Alloc(cb)) == NULL)
		return NULL;

	// only copy the smaller of the old and new size
	_asm push ds;
	_asm mov ds,sab;
	UINT cbCopy = LocalSize((HLOCAL)npv);
	_asm pop ds;

	if ((UINT)cb < cbCopy)
		// new size smaller
		cbCopy = (UINT)cb;

	_fmemcpy(pvT, pv, cbCopy);
	

	Free(pv);

	return pvT;
}


STDMETHODIMP_(void) CStdMalloc::Free(void FAR* pv)
{
	SAB sab;

	if (pv == NULL)
		return;

	//VDATEPTRIN rejects NULL (changed by alexgo 8/3/93)
 	VOID_VDATEPTRIN( pv, int );
 	
	if ((sab = MapPtrToBlock(pv)) == NULLSAB) {
#ifdef _DEBUG
		AssertSz(FALSE, "Pointer freed by wrong allocator; filling with 0xcc");

		// we don't own block; this is an error; fill it with 0xcccc anyway
		UINT cb = (UINT)GlobalSize((HGLOBAL)GlobalHandle((__segment)pv));
		cb -= (UINT)(ULONG)pv;

		_fmemset(pv, 0xcc, (size_t)cb);
#endif
	} else {
#ifdef _DEBUG
		// we own block; fill it with 0xcccc
		_fmemset(pv, 0xcc, (size_t)GetSize(pv));
#endif
		_asm push ds;
		_asm mov ds,sab;
		LocalFree((HLOCAL)(void NEAR*)(ULONG)pv);
		_asm pop ds;
	}
}


STDMETHODIMP_(ULONG) CStdMalloc::GetSize(void FAR* pv)
{
	ULONG size = 0;
	SAB sab;

	//VDATEPTRIN rejects NULL (added by alexgo 8/3/93)	
	if( pv == NULL )
		return -1;
		
	GEN_VDATEPTRIN( pv , int, 0 );

	if ((sab = MapPtrToBlock(pv)) != NULLSAB) {
		_asm push ds;
		_asm mov ds,sab;
		size = LocalSize((HLOCAL)(void NEAR*)(ULONG)pv);
		_asm pop ds;
	}

	return size;
}


STDMETHODIMP_(int) CStdMalloc::DidAlloc(void FAR* pv)
{


	if (pv == NULL)
		return -1;

 	//VDATEPTRIN rejects NULL (added by alexgo 8/3/93)
	GEN_VDATEPTRIN( pv , int, 0 );
	
	// returns 1 if we allocated, 0 if not; this impl never returns -1.
	return MapPtrToBlock(pv) != NULLSAB;
}


STDMETHODIMP_(void) CStdMalloc::HeapMinimize()
{
	// LATER : could do local compact here
}


// move this instance of stdmalloc to lpv (which must be large enough)
INTERNAL_(CStdMalloc FAR*) CStdMalloc::MoveSelf(LPVOID lpv)
{
	_fmemcpy(lpv, this, sizeof(*this));
	m_pStdAllocHead = NULL;
	return (CStdMalloc FAR*)lpv;
}


INTERNAL_(void) CStdMalloc::FreeAllMem(void)
{
	// get/null out head of list; null now in case we are freeing self.
	StdAllocHdr FAR* pSA = m_pStdAllocHead;
	m_pStdAllocHead = NULL;

	// free all blocks
	while (pSA != NULL) {
		StdAllocHdr FAR* pSANext = pSA->m_pStdAllocNext;
		pSA->m_pStdAllocNext = NULL;		// to prevent incorrect compiler opt

		GlobalFree(LOWORD(GlobalHandle((UINT)MapSAToBlock(pSA))));

		pSA = pSANext;
	}
}


/****** Global API for creating *****************************************/


// create and return an impl of IMalloc of given memctx
//+---------------------------------------------------------------------------
//
//  Function:   CoCreateStandardMalloc, Local
//
//  Synopsis:
//
//  Effects:
//
//  Arguments:  [memctx] --
//		[ppMalloc] --
//
//  Requires:
//
//  Returns:
//
//  Signals:
//
//  Modifies:
//
//  Algorithm:
//
//  History:    2-28-94   kevinro   Created
//
//  Notes:
//
//----------------------------------------------------------------------------
STDAPI CoCreateStandardMalloc(DWORD memctx, IMalloc FAR* FAR* ppMalloc)
{
	thkDebugOut((DEB_ITRACE, " CoCreateStandardMalloc\n"));
	*ppMalloc = NULL;

	switch (memctx) {

	case MEMCTX_TASK:
	case MEMCTX_SHARED:
		{
		CStdMalloc sm(memctx);			// local one

		void FAR* lpv;
		if ((lpv = sm.Alloc(sizeof(CStdMalloc))) == NULL)
			return ResultFromScode(E_OUTOFMEMORY);

		*ppMalloc = sm.MoveSelf(lpv);		// move to newly allocated memory
		return NOERROR;
		}
	default:
		return ResultFromScode(E_INVALIDARG);
	}
}
