/* Copyright (c) Microsoft Corporation. All rights reserved. */ #include <mmlite.h> #include <stdio.h> #include <assert.h> #include <string.h> #include <heaps/dbg_heap.h> #include <util/list-double2.h> #include "heap.h" #define UNSAFE_THREAD_STATS #ifdef UNSAFE_THREAD_STATS #ifdef _OLDSCHED #include <base/thread.h> #include "../base/process.h" #else #include <base/thread2.h> #include <base/process2.h> #endif #endif #include <s_DebugHeap_v.c> #include <s_DebugHeapFactory_v.c> #define DPRINT(x) #define EPRINT(x) extern PIBASERTL pTheBaseRtl; /* Debug heap. * */ /* An in-use block contains: * - A preable, as defined below, of 8 bytes * - The usable memory block * - A sloop, to roundup the usable part to a 16bit word boundary * - A trailer of at least 8 bytes */ typedef struct _BLOCK_PREAMBLE { UINT32 BeginCookie; /* check we did not get clobbered */ #define PRE_COOKIE 0xbeefbeef #define POST_COOKIE 0xbaba ADDRESS pc, pc2; /* Where was the block alloced/etc from */ HEAP_FLAGS Flags; /* Flags this block was allocated with */ struct _BLOCK_PREAMBLE *BlockLink; /* List of blocks in this heap */ UINT32 UsableSize; /* how much can the user have of it */ UINT16 UserData[1]; /* variable size [NB: type known in code below!] */ } BLOCK_PREAMBLE, *PBLOCK_PREAMBLE; #define PREAMBLE_SIZE (UINT)offsetof(BLOCK_PREAMBLE,UserData) #define DEFAULT_TRAILER_SIZE 8 #define align16(_x_) ((((UINT)_x_)+1)&~1) /* Standard info */ typedef struct { ADDR_SIZE Reserve; /* amount of address space reserved for the heap */ ADDR_SIZE Used; /* of that, how much is actually allocated */ ADDR_SIZE MaxUsed; ADDR_SIZE Avail; /* ..and how much is left free. */ } HEAP_STATUS; /* Some more, look at with debugger */ typedef struct { UINT AllocCount; UINT ReallocCount; UINT FreeCount; UINT SizeCount; UINT ValidateCount; UINT ExtractCount; UINT StatusCount; /* BUGBUG do the IUNKNOWNs too ? nyaa */ } HEAP_STATUS2; /* Heap header */ typedef struct _DHEAPINFO { /* Per-heap information */ const struct IHeapVtbl *Vtbl; /* Reserved for IHEAP vtbl */ UINT Refs; /* Number of references */ UINT Flags; /* Passed to CreatHeap */ UINT TrailerSize; /* User option */ PIHEAP MasterHeap; /* Where to get/give memory blocks */ const _TCHAR *Name; /* The name of the heap, for debug print use */ PBLOCK_PREAMBLE BlockList; /* List of blocks in this heap */ MUTEX Mutex; /* For blocklist */ HEAP_STATUS Status; /* Reserve, Commit, etc fields */ HEAP_STATUS2 Status2; /* Perf fields */ UINT HeapCookie; /* Second check field, against corrupt */ #define HEAP_COOKIE 0xbabebabe } DHEAPINFO, *PDHEAPINFO; PRIVATE void DHPDumpContent(PDHEAPINFO phi, BOOL MustBeEmpty); /* Size accounting */ void IncUsedCnt(PDHEAPINFO phi, UINT size) { phi->Status.Used += size; phi->Status.Avail -= size; #if 0 /* Dump when maximum use seen */ if (!(phi->Flags & HEAP_NESTED_HEAP) && phi->Status.Used > phi->Status.MaxUsed && phi->Status.Used > 70000 ) DHPDumpContent(phi, FALSE); #endif } #define DecUsedCnt(_phi_,_size_) \ { \ (_phi_)->Status.Used -= (_size_); \ (_phi_)->Status.Avail += (_size_); \ } /* Perf accounting */ #define IncPerfCount(_phi_,_field_) {(_phi_)->Status2._field_##Count++;} /* Safely typecast */ PRIVATE PDHEAPINFO DHPCheckHeap(PIHEAP pHeap) { PDHEAPINFO phi = (PDHEAPINFO) pHeap; if ((phi->Vtbl != &DebugHeapVtbl) || (phi->HeapCookie != HEAP_COOKIE)) { DebugBreak(); return NULL; } return phi; } /* Safely typecast */ PRIVATE PBLOCK_PREAMBLE DHPCheckBlock(PDHEAPINFO phi, UINT8 *pMem, BOOL Crash) { BLOCK_PREAMBLE *pBlk = NULL; UINT Size, i; /* Sanity */ if (pMem == NULL) { if (Crash) DebugBreak(); return NULL; } /* Cast */ pBlk = (BLOCK_PREAMBLE *) (pMem - PREAMBLE_SIZE); /* Check header */ if (pBlk->BeginCookie != PRE_COOKIE) { if (Crash) DebugBreak(); return NULL; } /* Check trailer */ Size = pBlk->UsableSize; if (Size & 1) { if (((UINT8*)pBlk->UserData)[Size] != (UINT8) POST_COOKIE) { if (Crash) DebugBreak(); return NULL; } Size++; } for (i = Size; i < (Size + phi->TrailerSize); i += 2) if (pBlk->UserData[i>>1] != POST_COOKIE) { if (Crash) DebugBreak(); return NULL; } /* All is well */ return pBlk; } /* Setup our check info */ PRIVATE PTR DHPSetBlock(PDHEAPINFO phi, PBLOCK_PREAMBLE pBlk, UINT Size, ADDRESS pc, ADDRESS pc2, HEAP_FLAGS Flags) { UINT i; /* Administratrivia */ IncUsedCnt(phi,Size); if (phi->Status.Used > phi->Status.MaxUsed) phi->Status.MaxUsed = phi->Status.Used; /* Wrap usable memory */ pBlk->BeginCookie = PRE_COOKIE; pBlk->UsableSize = Size; pBlk->Flags = Flags; if (pc) { pBlk->pc = pc; pBlk->pc2 = pc2; } if (Size & 1) { ((UINT8*)pBlk->UserData)[Size] = (UINT8) POST_COOKIE; Size++; } for (i = Size; i < (Size + phi->TrailerSize); i += 2) pBlk->UserData[i>>1] = POST_COOKIE; Mutex_Lock(&phi->Mutex); /* Add block to list of blocks in this heap */ pBlk->BlockLink = phi->BlockList; phi->BlockList = pBlk; Mutex_Unlock(&phi->Mutex); return (PTR) &pBlk->UserData[0]; } PRIVATE void DHPUnsetBlock(PDHEAPINFO phi, PBLOCK_PREAMBLE pBlk) { /* Remove block from blocklist (walk entrire list until found) */ PBLOCK_PREAMBLE *pp; Mutex_Lock(&phi->Mutex); for (pp = &phi->BlockList; *pp; pp = &(*pp)->BlockLink) { if (*pp == pBlk) { *pp = pBlk->BlockLink; Mutex_Unlock(&phi->Mutex); return; } } Mutex_Unlock(&phi->Mutex); DBG(("DHPUnsetBlock: used block list doesn't have the block!\n")); DebugBreak(); } extern PTR pDelayedFree; PRIVATE void DHPDumpContent(PDHEAPINFO phi, BOOL MustBeEmpty) { HEAP_STATUS2 *st2 = &phi->Status2; PBLOCK_PREAMBLE *pp; UINT IgnoredSize = 0; PITHREAD myth = CurrentThread(); Mutex_Lock(&phi->Mutex); printf("-------------------------------------------\n"); printf("DHP%s(%s) Reserve=%d Used=%d MaxUsed=%d Avail=%d\n" " OP count: alloc=%d re=%d free=%d sz=%d val=%d ex=%d stat=%d\n", MustBeEmpty ? "Release" : "Status", phi->Name, phi->Status.Reserve, phi->Status.Used, phi->Status.MaxUsed, phi->Status.Avail, st2->AllocCount, st2->ReallocCount, st2->FreeCount, st2->SizeCount, st2->ValidateCount, st2->ExtractCount, st2->StatusCount); for (pp = &phi->BlockList; *pp; pp = &(*pp)->BlockLink) { char *descr = (char*)((ADDRESS)(*pp)->Flags >> HEAP_FLAGS_SHIFT); if ((UINT) descr < 0x1000) descr = 0; if ((*pp)->UserData == (PTR) myth) { /* Current thread can't be freed, don't barf about it */ IgnoredSize += (*pp)->UsableSize; } if ((*pp)->Flags & HEAP_PERMANENT) { /* Indicate permanent blocks with some stars */ printf("***"); } printf("BLOCK 0x%08x pc=0x%08x pc2=0x%08x size=%d fl=%x %s", (ADDRESS) (*pp)->UserData, (*pp)->pc, (*pp)->pc2, (*pp)->UsableSize, (*pp)->Flags /*& HEAP_FLAGS_MASK*/, descr ? descr : "?"); #ifdef UNSAFE_THREAD_STATS /* XXX. Extremely unsafe code */ if (descr && !strcmp("IThread", descr)) { /* More debug magic: Thread->Release prints stack stats */ PITHREAD th = (PITHREAD) (*pp)->UserData; /* XXX knows too much */ PIPROCESS prc, prcme; PIMODULE mod, modme; SCODE sc; /* There is no lock so there is a race w/ termination here XXX */ th->v->AddRef(th); prc = pTH(th)->StartedIn; sc = th->v->GetProcess(th, &prc); GetCurrentModule(&modme); modme->v->QueryInterface(modme, &IID_IProcess, (void **) &prcme); modme->v->Release(modme); /* Fish out the name of the process and print it */ if (prc->v == prcme->v) {/* make sure the process is valid */ sc = prc->v->QueryInterface(prc, &IID_IModule, (void **) &mod); if (SUCCEEDED(sc)) { _TCHAR buf[21]; mod->v->GetName(mod, buf, 20); buf[20] = '\0'; mod->v->Release(mod); printf(" %s ", buf); } } if (th == myth) printf("(current) "); prcme->v->Release(prcme); th->v->Release(th); } else if (descr && !strcmp("IModule", descr)) { PIMODULE mod = (PIMODULE) (*pp)->UserData; /* XXX */ printf(" %s\n", pMD(mod)->pName); } else if (descr && !strcmp("nsentry", descr)) { typedef struct _NAMEENTRY { PIUNKNOWN pObj; /* pointer to object */ NAME_SPACE_FLAGS Flags; /* Flags associated with object */ struct _LIST_NODE_LINK Link; UINT16 StrLen; /* length of name string */ _TCHAR Str[1]; /* first character of name string */ } *xxxne; xxxne ne = (xxxne) (*pp)->UserData; printf(" %s\n", ne->Str); } else if (descr && !strcmp("cobinfo", descr)) { typedef struct _COBINFO { const struct IUnknownVtbl *v; PIMODULE pIModule; PIUNKNOWN pICob; } *xxxce; xxxce ce = (xxxce) (*pp)->UserData; PMODULE mod = (PMODULE) ce->pIModule; if (mod == 0) printf(" 0\n"); // else if (mod == (void*) 0xcccccccc || mod == (void*) 0xbaadf00d) // printf(" ?\n"); else printf(" %s\n", mod->pName); } else #endif printf("\n"); } if (phi->Status.Used - IgnoredSize && MustBeEmpty) { printf("Heap \"%s\" leaked %d bytes\n", phi->Name, phi->Status.Used); DebugBreak(); } Mutex_Unlock(&phi->Mutex); } #ifdef i386 #define GetCallerPc(_pc_,_pc2_) __asm { __asm mov eax,4[ebp] __asm mov _pc_,eax __asm mov eax,0[ebp] __asm mov eax,4[eax] __asm mov _pc2_,eax __asm } #elif defined(arm) && defined(__GNUC__) #define GetCallerPc(_pc_,_pc2_) __asm ("mov %0, lr" : "=r" (_pc_) ) #else #define GetCallerPc(_pc_,_pc2_) 0 #endif /* *** DHPAlloc * * Allocates a heap block of at least Size bytes from the heap pointed * by pHeap. If Flags contains HEAP_ZERO_MEMORY also zero fills the new block. * Returns pointer to the block of memory, NULL if error. */ PRIVATE PTR MCT DHPAlloc(IHeap *pHeap, UINT Flags, UINT Size, UINT Alignment) { PDHEAPINFO phi = DHPCheckHeap(pHeap); PIHEAP MasterHeap; BLOCK_PREAMBLE *pBlk; UINT TrueSize; UINT pc = 0, pc2 = 0; GetCallerPc(pc, pc2); DPRINT(("DHPAlloc: pHeap %x, Flags %x, Size %x Align %x pc=%08x\n", (ADDRESS) pHeap, Flags, Size, Alignment, pc, pc2)); IncPerfCount(phi,Alloc); /* Adjust args and allocate */ MasterHeap = phi->MasterHeap; Flags |= phi->Flags; TrueSize = phi->TrailerSize + PREAMBLE_SIZE + align16(Size); pBlk = MasterHeap->v->Alloc(MasterHeap, (Flags&HEAP_FLAGS_MASK) | HEAP_NESTED_HEAP | HEAP_DEBUG_NAME(phi->Name), TrueSize, Alignment); if (pBlk == NULL) { DebugBreak(); return NULL; } MasterHeap->v->Validate(MasterHeap,0,pBlk); /* Setup our info and cookies, return proper pointer */ return DHPSetBlock(phi,pBlk,Size,pc, pc2, Flags); } /* *** DHPRealloc * * Reallocates the memory block pointed by pMem, in the heap pointed by * pHeap to contain at least NewSize bytes. If NewSize is greater then the * current size and Flags contain HEAP_ZERO_MEMORY zero fills new area. * If Flags doesn't contain HEAP_REALLOC_IN_PLACE_ONLY then the * block's address may change. * Returns pointer to the reallocated block, NULL if error. Note that in * case of failure the original pointer still point to the original block. * As per ANSI specs, if PTR is NULL it should do an Alloc. * As per ANSI specs, if PTR is non null and NewSize is 0 it should do a Free. */ PRIVATE PTR MCT DHPRealloc(IHeap *pHeap, UINT Flags, PTR pMem, UINT NewSize, UINT Alignment) { PIHEAP MasterHeap; PDHEAPINFO phi = DHPCheckHeap(pHeap); BLOCK_PREAMBLE *pBlk, *pBlkOld; UINT TrueSize, OldSize; SCODE sc; UINT pc = 0, pc2 = 0; GetCallerPc(pc, pc2); IncPerfCount(phi,Realloc); Flags |= phi->Flags; /* Special cases if special */ if (pMem == NULL) return pHeap->v->Alloc(pHeap,Flags,NewSize,Alignment); /* Do we have to free it */ if (NewSize == 0) { if (pHeap->v->Free(pHeap,Flags,pMem)) return NULL; return pMem; } /* Make sure its in use and its sane */ pBlk = DHPCheckBlock(phi,pMem,TRUE); if (pBlk == NULL) return NULL; /* I mean.. *really* make sure */ MasterHeap = phi->MasterHeap; sc = MasterHeap->v->Validate(MasterHeap,0,pBlk); if (FAILED(sc)) goto Bad; /* Zap old block */ pBlkOld = pBlk; OldSize = pBlk->UsableSize; DecUsedCnt(phi, OldSize); DHPUnsetBlock(phi, pBlk); /* Nope, truly realloc * BUGBUG could optimize a bit in some cases. */ TrueSize = phi->TrailerSize + PREAMBLE_SIZE + align16(NewSize); pBlk = MasterHeap->v->Realloc(MasterHeap, (Flags&HEAP_FLAGS_MASK) | HEAP_NESTED_HEAP | HEAP_DEBUG_NAME(phi->Name), pBlk, TrueSize, Alignment); if (pBlk == NULL) { /* Whoops, reactivate old block */ DHPSetBlock(phi,pBlkOld,OldSize, pc,pc2, Flags); goto Bad; } /* Setup our info and cookies, return proper pointer */ return DHPSetBlock(phi,pBlk,NewSize, pc,pc2, Flags); Bad: DebugBreak(); return NULL; } /* ** DHPFree * * Free the memory block pointed by pMem, back to the heap pointed * by pHeap. Returns TRUE, FALSE if error. */ PRIVATE SCODE MCT DHPFree(IHeap *pHeap, UINT Flags, PTR pMem) { PDHEAPINFO phi = DHPCheckHeap(pHeap); BLOCK_PREAMBLE *pBlk; UINT TrueSize; IncPerfCount(phi,Free); Flags |= phi->Flags; pBlk = DHPCheckBlock(phi,pMem,TRUE); if (pBlk != NULL) { /* Administratrivia */ DecUsedCnt(phi,pBlk->UsableSize); DHPUnsetBlock(phi, pBlk); /* If possible, stick an illegal instruction in there so that * if someone tries to execute freed code ..poof. */ TrueSize = align16(pBlk->UsableSize) + phi->TrailerSize + PREAMBLE_SIZE; #if defined(arm) { UINT i; UINT32 *p = (UINT32*) pBlk; for (i = 0; i < (TrueSize/sizeof(UINT32)); i++) *p++ = 0xe7ffdefe; /* undefined instruction */ } #else /* NB: This just so happens to be an "INT 3" on x86.. */ memset(pBlk,0xcc,TrueSize); #endif phi->MasterHeap->v->Validate(phi->MasterHeap,0,pBlk); return phi->MasterHeap->v->Free(phi->MasterHeap,Flags,pBlk); } return E_INVALID_PARAMETER; } /* *** DHPSize * * Returns size of chunk of memory pointed by pmem that has been allocated * out of the heap pointed by pHeap. Returns 0 if error. * Note that there is no need to lock the mutex in this call: it doesn't * modify the heap. (It is like using the memory vs eg reallocating). */ PRIVATE ADDR_SIZE MCT DHPSize(IHeap *pHeap, UINT Flags, PTR pMem) { PDHEAPINFO phi = DHPCheckHeap(pHeap); BLOCK_PREAMBLE *pBlk; UnusedParameter(Flags); IncPerfCount(phi,Size); pBlk = DHPCheckBlock(phi,pMem,TRUE); if (pBlk != NULL) return pBlk->UsableSize; return 0; } /* *** DHPValidate * * Validates the heap data structures. */ PRIVATE SCODE MCT DHPValidate(IHeap *pHeap, UINT Flags, PTR pMem) { PDHEAPINFO phi = DHPCheckHeap(pHeap); BLOCK_PREAMBLE *pBlk; IncPerfCount(phi,Validate); Flags |= phi->Flags; if (pMem == NULL) return phi->MasterHeap->v->Validate(phi->MasterHeap,Flags,pMem); pBlk = DHPCheckBlock(phi,pMem,FALSE); return (pBlk) ? S_OK : E_FAIL; } /* *** DHPExtract * * Allocates a memory block from the heap pointed by pHeap. This routine * is similar to Alloc(), but a fourth argument, pMem, has been added. * The allocated block must contain the Size bytes of memory that begin * at address pMem, but the block may contain additional bytes. If Flags * contains HEAP_ZERO_MEMORY, the routine also zero fills the new block. * If the function is successful, it returns a pointer to the block; * otherwise, it returns NULL to indicate an error. The returned pointer * may not always be precisely equal to pMem; it may be less than pMem by * some number of bytes. The block can later be freed by calling Free(). */ PRIVATE PTR MCT DHPExtract(IHeap *pHeap, UINT Flags, PTR pMem, UINT Size) { PDHEAPINFO phi = DHPCheckHeap(pHeap); BLOCK_PREAMBLE *pBlk; UINT pc = 0, pc2 = 0; GetCallerPc(pc,pc2); IncPerfCount(phi,Extract); Flags |= phi->Flags; /* Back the pointer enough to fit our stuff * Yes, this can screw them royally. */ pMem = ((UINT8*)pMem - PREAMBLE_SIZE); /* Grab block, if possible */ pBlk = phi->MasterHeap->v->Extract(phi->MasterHeap, Flags | HEAP_NESTED_HEAP | HEAP_DEBUG_NAME(phi->Name), pMem, Size + PREAMBLE_SIZE); if (pBlk == NULL) return NULL; /* Account for whatever extra fluff we might have triggered */ if (pBlk != pMem) Size += (ADDRESS)pMem - (ADDRESS)pBlk; /* Setup our info and cookies, return proper pointer */ return DHPSetBlock(phi,pBlk,Size, pc,pc2, Flags); } /* *** DHPStatus * * Returns status information about this heap. */ PRIVATE SCODE MCT DHPStatus(IHeap *pHeap, ADDR_SIZE *pReserve, ADDR_SIZE *pCommit, ADDR_SIZE *pUsed, ADDR_SIZE *pMaxUsed) { PDHEAPINFO phi = DHPCheckHeap(pHeap); IncPerfCount(phi,Status); /* Get the commit size from the real heap. */ phi->MasterHeap->v->Status(phi->MasterHeap, pReserve, pCommit, NULL, NULL); if (pReserve && phi->Status.Reserve) *pReserve = phi->Status.Reserve; if (pUsed) *pUsed = phi->Status.Used; if (pMaxUsed) { *pMaxUsed = phi->Status.MaxUsed; phi->Status.MaxUsed = phi->Status.Used; } /* If all params are NULL, dump some stats on the screen */ if (!pReserve && !pCommit && !pUsed && !pMaxUsed) DHPDumpContent(phi, FALSE); return NO_ERROR; } /* *** Constructor: CreateDebugHeap * * Creates heap from a block of memory. pmem points to a block of memory * in the appropriate address space to be used. InitSize is the number of * bytes already commited at the beginning of memory block. MaxSize is * total number of bytes in memory block. Zero is the biggest possible. * Flags passes HEAP_ZERO_MEMORY etc. * Returns pointer to the heap, NULL if failed. * */ SCODE DebugHeapFactoryCreateNested( PIHEAPFACTORY iThis, PIHEAP MasterHeap, UINT Flags, UINT MaxSize, UINT TrailerSize, PIHEAP *ppNewHeap) { PDHEAPINFO phi; SCODE sc = E_INVALID_PARAMETER; DBG(("Enter CreateDebugHeap MaxSize %08x, Flags %08x Hp %x\n", MaxSize,Flags, (UINT) MasterHeap)); if (MasterHeap == NULL) goto ErrorExit; /* Allocate&initialize DHEAPINFO structure (per-heap information). */ // XXX HEAP_NO_SERIALIZE required at boot time, should not be used later. phi = (PDHEAPINFO) MasterHeap->v->Alloc(MasterHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY | HEAP_DEBUG_NAME("dbg_heap"), sizeof *phi,0); if (phi == NULL) goto ErrorExit; phi->Vtbl = &DebugHeapVtbl; phi->Refs = 1; phi->Flags = Flags; phi->Name = _T("subheap"); phi->TrailerSize = (TrailerSize) ? align16(TrailerSize) : DEFAULT_TRAILER_SIZE; phi->MasterHeap = MasterHeap; phi->HeapCookie = HEAP_COOKIE; phi->Status.Reserve = MaxSize; phi->Status.Used = 0; phi->Status.MaxUsed = 0; phi->Status.Avail = MaxSize; Mutex_Init(&phi->Mutex); DPRINT(("Exit CreatHeap\n")); *ppNewHeap = (PIHEAP) phi; return S_OK; ErrorExit: EPRINT(("Error exit CreatHeap\n")); return sc; } SCODE DHPCreateHeap(PIHEAP pHeap, UINT Flags, UINT InitialSize, UINT MaxSize, PIHEAP *ppHeap) { #if 1 PDHEAPINFO phi = DHPCheckHeap(pHeap); return phi->MasterHeap->v->CreateHeap(phi->MasterHeap, Flags, InitialSize, MaxSize, ppHeap); #else /* with this heaptstx.exe gets into trouble. Investigate XXX */ return CreateDebugHeap("subheap", Flags, MaxSize, DEFAULT_TRAILER_SIZE); #endif } /* * Exported methods */ PRIVATE UINT MCT DHPAddRef(PIHEAP pThis) { PDHEAPINFO phi = DHPCheckHeap(pThis); return AtomicInc(&phi->Refs); } PRIVATE UINT MCT DHPRelease(PIHEAP pThis) { PDHEAPINFO phi = DHPCheckHeap(pThis); UINT newrefs; newrefs = AtomicDec(&phi->Refs); if (newrefs == 0) { PIHEAP MasterHeap = phi->MasterHeap; DHPDumpContent(phi, TRUE); (void) MasterHeap->v->Free(MasterHeap,0,pThis); } return newrefs; } PRIVATE SCODE MCT DHPQueryInterface(PIHEAP pThis, REFIID pIid, void* *ppUnk) { PDHEAPINFO phi = DHPCheckHeap(pThis); return GenericQueryInterface((IUnknown *)phi,pIid,ppUnk,&IID_IHeap); } SCODE DebugHeapFactoryQueryInterface ( /*in*/ PIHEAPFACTORY pThis, /*in*/ REFIID iid, /*out*/ void** ppObject ) { return GenericQueryInterface((PIUNKNOWN) pThis, iid, ppObject, &IID_IHeapFactory); } UINT DebugHeapFactoryAddRef ( /*in*/ PIHEAPFACTORY pThis ) { PHEAPCOB DbgHeapFactory = (PHEAPCOB) pThis; return AtomicInc(&DbgHeapFactory->RefCnt); } UINT DebugHeapFactoryRelease ( /*in*/ PIHEAPFACTORY pThis ) { PHEAPCOB DbgHeapFactory = (PHEAPCOB) pThis; UINT Refs; Refs = AtomicDec(&DbgHeapFactory->RefCnt); /* Static object, no free */ return Refs; } PRIVATE struct HEAPCOB DbgHeapCobObject = { &DebugHeapFactoryVtbl, 1 }; PIUNKNOWN DbgHeapCobMain(void) { return (PIUNKNOWN) &DbgHeapCobObject; }