************************************************** *-- Class: heap *-- ParentClass: custom *-- BaseClass: custom * * Another in the family of relatively undocuemnted sample classes I've inflicted on others * Warning - there's no error handling in here, so be careful to check for null returns and * invalid pointers. Unless you get frisky, or you're resource-tight, it should work well. * * It doesn't seem to bleed, either, at the moment. * * Overwheming guilt hit early this morning; maybe I should explain the concept of the Heap class * and give an example of how to use it, in conjunction with the add-on functions that follow in * this procedure library here. * * Windows allocates memory from several places; it also provides a way to define your own small corner * of the universe where you can allocate and deallocate blocks of memory for your own purposes. These * public or private memory areas are referred to commonly as heaps. * * VFP is great in most cases; it provides flexible allocation and alteration of variables on the fly * in a program. This makes most programming tasks easy. However, in exchange for VFP's flexibility in * memory variable allocation, we give up several things, the most annoying of which are not knowing the * exact location of a VFP variable in memory, and not being able to define new kinds of memory structures * within VFP to manipulate as a structure directly. * * Enter Heap. Heap creates a growable, private Heap, from which you can allocate blocks of memory that * have a known location and size in your memory address space. It also provides a way of transferring * data to and from these allocated blocks. * * Heap does its work using a number of Win32 API functions; HeapCreate(), which sets up a private heap * and assigns it a handle, is invoked in the Init method. This sets up the heap that all future allocations * for the class will be constructed from. I set up the heap to use a base allocation size of twice the size * of a swap file 'page' in the x86 world (8K), and made the heap able to grow; it adds 8K chunks of memory * to itself as it grows. There's no fixed limit (other than available -virtual- memory) on the size of the * heap constructed; just realize that huge allocations are likely to bump heads with VFP's own desire * for mondo RAM. * * Once the Heap is established, we can allocate blocks of any size we want in the heap, outside of VFP's * memory, but within the virtual addrss space owned by VFP. Blocks are allocated by HeapAlloc(), and a * pointer to the block is returned as an integer. KEEP THE POINTER RETURNED BY THE Alloc METHOD, it's the * key to doing things with the block in the future. * * Periodically, we need to load things into the block we've created. Thanks to work done by Christof Lange, * George Tasker and others, we found a Win32API call that will do transfers between memory locations, called * RtlMoveMemory(). RtlMoveMemory() acts like the Win32API MoveMemory() call; it takes two pointers * (destination and source) and a length. In order to make life easy, at times we DECLARE the pointers as * INTEGER (we pass a number, which is treated as a DWORD (32 bit unsigned integer) whose content is the * address to use, and at other times as STRING @, which passes the physical address of a VFP string variable * contents, allowing RtlMoveMemory() to read and write VFP strings without knowing how to manipulate VFP's * internal variable structures. RtlMoveMemory() is used by both the CopyFrom and CopyTo methods. * * At some point, we're finished with a block of memory. We can free up that memory via HeapFree(), which * releases a previously-allocated block on the heap. It does not compact or rearrange the heap allocations * but simply makes the memory allocated no long valid, and the addess could be reused by annother Alloc * operation. We track the active state of allocations in a member array iaAllocs[] which has 3 members * per row; the pointer, which is used as a key, the actual size of the allocation (sometimes HeapAlloc() * gives you a larger block than requested; we can see it here. This is the property returned by the * SizeOfBlock method) and whether or not it's active and available. * * When we're done, we need to release the allocations and the heap itself. HeapDestroy() releases the * entire heap back to the Windows memory pool. This is invoked in the Destroy method of the class to * ensure that it gets explcitly released, since it remains alive until released. I put this in the * Destroy method to ensure that the heap went away when the heap object went out of scope. * * The class methods are: * * Init Creates the heap for use * Alloc(nSize) Allocates a block of nSize bytes, returns an nPtr to it. nPtr is NULL if fail * DeAlloc(nPtr) Releases the block whose base address is nPtr. Returns T/F * CopyTo(nPtr,cSrc) Copies the Content of cStr to the buffer at nPtr, up to the smaller of LEN(cSrc) * or the length of the block (we look in the iaAllocs[] array). Returns T/F * CopyFrom(nPtr) Copys the content of the block at nPtr (size is from iaAllocs[]) and returns * it as a VFP string. Returns a string, or NULL if fails * SizeOfBlock(nPtr) Returns the actual allocated size of the block pointed to by nPtr * Destroy DeAllocs anything still active, and then frees the heap. * * The ancillary goodies in the procedure library are there to make life easier for people working with structures; * they are not optimal and infinitely complete, but they do the things that are commonly needed when dealing with * stuff in structures. The functions are of two types; converters, which convert standard C structures to an * equivalent VFP numeric, or make a string whose value is equivalent to a C data type from a number, so that you * can embed integers, pointers, etc. in the strings used to assemble a structure which you load up with CopyTo, * or pull out pointers and integers that come back embedded in a structure you've grabbed with CopyFrom. * * The second type of functions provided are memory copiers. The CopyFrom and CopyTo methods are set up to work * with our heap, and nPtrs must take on the values of block addresses grabbed from our heap. There will be * lots of times where you need to get the content of memory not necessarily on our heap, so SetMem, GetMem and * GetMemString go to work for us here. SetMem copies the content of a string into the absolute memory block * at nPtr, for the length of the string, using RtlMoveMemory(). BE AWARE THAT MISUSE CAN (and most likely will) * RESULT IN C0000005 ERRORS, memory access violations, or similar OPERATING SYSTEM level errors that will smash * VFP like an empty beer can in a trash compactor. * * There are two functions to copy things from a known address back to the VFP world. If you know the size of the * block to grab, GetMem(nPtr,nSize) will copy nSize bytes from the address nPtr and return it as a VFP string. See * the caveat above. GetMemString(nPtr) uses a different API call, lstrcpyn(), to copy a null terminated string * from the address specified by nPtr. You can hurt yourself with this one, too. * * Functions in the procedure library not a part of the class: * * GetMem(nPtr,nSize) Copy nSize bytes at address nPtr into a VFP string * SetMem(nPtr,cSource) Copy the string in cSource to the block beginning at nPtr * GetMemString(nPtr) Get the null-terminated string (up to 512 bytes) from the address at nPtr * * DWORDToNum(cString) Convert the first 4 bytes of cString as a DWORD to a VFP numeric (0 to 2^32) * SHORTToNum(cString) Convert the first 2 bytes of cString as a SHORT to a VFP numeric (-32768 to 32767) * WORDToNum(cString) Convert the first 2 bytes of cString as a WORD to a VFP numeric (0 to 65535) * NumToDWORD(nInteger) Converts nInteger into a string equivalent to a C DWORD (4 byte unsigned) * NumToWORD(nInteger) Converts nInteger into a string equivalent to a C WORD (2 byte unsigned) * NumToSHORT(nInteger) Converts nInteger into a string equivalent to a C SHORT ( 2 byte signed) * * That's it for the docs to date; more stuff to come. The code below is copyright Ed Rauh, 1999; you may * use it without royalties in your own code as you see fit, as long as the code is attributed to me. * * This is provided as-is, with no implied warranty. Be aware that you can hurt yourself with this code, most * easily when using the SetMem(), GetMem() and GetMemString() functions. I will continue to add features and * functions to this periodically. If you find a bug, please notify me. It does no good to tell me that "It * doesn't work the way I think it should..WAAAAH!" I need to know exactly how things fail to work with the * code I supplied. A small code snippet that can be used to test the failure would be most helpful in trying * to track down miscues. I'm not going to run through hundreds or thousands of lines of code to try to track * down where exactly something broke. * * Please post questions regarding this code on Universal Thread; I go out there regularly and will respond * pretty quickly. In addition, other API gurus and FoxWizards frequent UT, and they may well be able to * help, in many cases better than I could. Posting questions on UT helps not only with getting support * from the VFP community at large, it also makes the information about the problem and its solution available * to others who might have the same or similar problems. * * If you do request help other than by UT, especially if you have to send files to help diagnose the problem, * send them to me at edrauh@earthlink.net or erauh@weatherhill.com, preferably the earthlink.net account. * * If you have questions about this code, you can ask. If you have questions about using it with API calls and * the like, you can ask. If you have enhancements that you'd like to see added to the code, you can ask. * Flames will be ignored. I'll try to answer promptly, but realize that support and enhancements for this are * done in my own spare time. If you need specific support that goes beyond what I feel is reasonable, I'll * tell you. * * Do not call me at home or work for support. Period. <Mumble><something about ripping out internal organs><Grr> * * Feel free to modify this code to fit your specific needs. Since I'm not providing any warranty with this * in any case, if you change it and it breaks, you own both pieces. * DEFINE CLASS heap AS custom PROTECTED inHandle inHandle = NULL PROTECTED inNumAllocsActive inNumAllocsActive = 0 Name = "heap" * I've been debating whether this should be protected; for now, it isn't * It probably should be. DIMENSION iaAllocs[1,3] PROCEDURE Alloc * Allocate a block, returning a pointer to it LPARAMETER nSize DECLARE INTEGER HeapAlloc IN WIN32API AS HAlloc; INTEGER hHeap, ; INTEGER dwFlags, ; INTEGER dwBytes LOCAL nPtr WITH this nPtr = HAlloc(.inHandle, 0, @nSize) * Bump the allocation array .inNumAllocsActive = .inNumAllocsActive + 1 DIMENSION .iaAllocs[.inNumAllocsActive,3] * Pointer .iaAllocs[.inNumAllocsActive,1] = nPtr * Size actually allocated .iaAllocs[.inNumAllocsActive,2] = nSize * It's alive...alive I tell you! .iaAllocs[.inNumAllocsActive,3] = .T. ENDWITH RETURN nPtr ENDPROC PROCEDURE DeAlloc * Discard a previous Allocated block LPARAMETER nPtr DECLARE INTEGER HeapFree IN WIN32API AS HFree ; INTEGER hHeap, ; INTEGER dwFlags, ; INTEGER lpMem LOCAL nCtr, lSucceeds lSucceeds = .F. WITH this FOR nCtr = 1 TO .inNumAllocsActive IF .iaAllocs[nCtr,1] = nPtr IF .iaAllocs[nCtr,3] =HFree(.inHandle, 0, nPtr) .iaAllocs[nCtr,3] = .F. ENDIF lSucceeds = .T. EXIT ENDIF ENDFOR ENDWITH RETURN lSucceeds ENDPROC PROCEDURE CopyTo * Copy a VFP string into a block LPARAMETER nPtr, cSource * ReDECLARE RtlMoveMemory to make copy parameters easy DECLARE RtlMoveMemory IN WIN32API AS RtlCopy ; INTEGER nDestBuffer, ; STRING @pVoidSource, ; INTEGER nLength LOCAL nCtr, lSucceeds lSucceeds = .F. WITH this * Find the Allocation pointed to by nPtr FOR nCtr = 1 TO .inNumAllocsActive IF .iaAllocs[nCtr,1] = nPtr * Is it active IF .iaAllocs[nCtr,3] * We can copy to it lSucceeds = .T. EXIT ENDIF ENDIF ENDFOR IF lSucceeds * Copy the smaller of the buffer size or the source string =RtlCopy((.iaAllocs[nCtr,1]), ; cSource, ; MIN(LEN(cSource),.iaAllocs[nCtr,2])) ENDIF ENDWITH RETURN lSucceeds ENDPROC PROCEDURE CopyFrom * Copy the content of a buffer back to the VFP world LPARAMETER nPtr * Note that we reDECLARE RtlMoveMemory to make passing things easier DECLARE RtlMoveMemory IN WIN32API AS RtlCopy ; STRING @DestBuffer, ; INTEGER pVoidSource, ; INTEGER nLength LOCAL nCtr, lSucceeds lSucceeds = .F. WITH this * Find the allocation whose address is nPtr FOR nCtr = 1 TO .inNumAllocsActive IF .iaAllocs[nCtr,1] = nPtr * Is it active? IF this.iaAllocs[nCtr,3] * Yes - we can copy there lSucceeds = .T. EXIT ENDIF ENDIF ENDFOR IF lSucceeds LOCAL uBuffer * Allocate a buffer in VFP big enough to receive the block uBuffer = REPL(CHR(0),.iaAllocs[nCtr,2]) =RtlCopy(@uBuffer, ; (.iaAllocs[nCtr,1]), ; (.iaAllocs[nCtr,2])) ELSE uBuffer = NULL ENDIF ENDWITH RETURN uBuffer ENDPROC PROCEDURE SizeOfBlock * Retrieve the actual memory size of an allocated block LPARAMETERS nPtr LOCAL nCtr, nSizeOfBlock nSizeOfBlock = NULL WITH this * Find the allocation whose address is nPtr FOR nCtr = 1 TO .inNumAllocsActive IF .iaAllocs[nCtr,1] = nPtr * Is it active? IF this.iaAllocs[nCtr,3] * Yes - we can copy there nSizeOfBlock = .iaAllocs[nCtr,2] EXIT ENDIF ENDIF ENDFOR ENDWITH RETURN nSizeOfBlock PROCEDURE Destroy DECLARE HeapDestroy IN WIN32API AS HDestroy ; INTEGER hHeap LOCAL nCtr WITH this FOR nCtr = 1 TO .inNumAllocsActive IF .iaAllocs[nCtr,3] .Dealloc(.iaAllocs[nCtr,1]) ENDIF ENDFOR HDestroy[.inHandle] ENDWITH DODEFAULT() ENDPROC PROCEDURE Init DECLARE INTEGER HeapCreate IN WIN32API AS HCreate ; INTEGER dwOptions, ; INTEGER dwInitialSize, ; INTEGER dwMaxSize #DEFINE SwapFilePageSize 4096 #DEFINE BlockAllocSize 2 * SwapFilePageSize WITH this .inHandle = HCreate(0, BlockAllocSize, 0) DIMENSION .iaAllocs[1,3] .iaAllocs[1,1] = 0 .iaAllocs[1,2] = 0 .iaAllocs[1,3] = .F. .inNumAllocsActive = 0 ENDWITH RETURN (this.inHandle # 0) ENDPROC ENDDEFINE * *-- EndDefine: heap ************************************************** * * Additional functions for working with structures and pointers and stuff * FUNCTION SetMem LPARAMETERS nPtr, cSource * Copy cSource to the memory location specified by nPtr * ReDECLARE RtlMoveMemory to make copy parameters easy * nPtr is not validated against legal allocations on the heap DECLARE RtlMoveMemory IN WIN32API AS RtlCopy ; INTEGER nDestBuffer, ; STRING @pVoidSource, ; INTEGER nLength RtlCopy(nPtr, ; cSource, ; LEN(cSource)) RETURN .T. FUNCTION GetMem LPARAMETERS nPtr, nLen * Copy the content of a memory block at nPtr for nLen bytes back to a VFP string * Note that we ReDECLARE RtlMoveMemory to make passing things easier * nPtr is not validated against legal allocations on the heap DECLARE RtlMoveMemory IN WIN32API AS RtlCopy ; STRING @DestBuffer, ; INTEGER pVoidSource, ; INTEGER nLength LOCAL uBuffer * Allocate a buffer in VFP big enough to receive the block uBuffer = REPL(CHR(0),nLen) =RtlCopy(@uBuffer, ; nPtr, ; nLen) RETURN uBuffer FUNCTION GetMemString LPARAMETERS nPtr * Copy the string at location nPtr into a VFP string * We're going to use lstrcpyn rather than RtlMoveMemory to copy up to a terminating null * nPtr is not validated against legal allocations on the heap DECLARE INTEGER lstrcpyn IN WIN32API AS StrCpyN ; STRING @ lpDestString, ; INTEGER lpSource, ; INTEGER nMaxLength LOCAL uBuffer * Allocate a buffer big enough to receive the data uBuffer = REPL(CHR(0), 512) IF StrCpyN(@uBuffer, nPtr, 512) # 0 uBuffer = LEFT(uBuffer, MAX(0,AT(CHR(0),uBuffer) - 1)) ELSE uBuffer = NULL ENDIF RETURN uBuffer FUNCTION DWORDToNum * Take a binary DWORD and convert it to a VFP Numeric * use this to extract an embedded pointer in a structure in a string to an nPtr LPARAMETER tcDWORD LOCAL b0,b1,b2,b3 b0=asc(tcDWORD) b1=asc(subs(tcDWORD,2,1)) b2=asc(subs(tcDWORD,3,1)) b3=asc(subs(tcDWORD,4,1)) RETURN ( ( (b3*256 + b2)*256 + b1) * 256 + b0) FUNCTION SHORTToNum * Converts a 16 bit signed integer in a structure to a VFP Numeric LPARAMETER tcInt LOCAL b0,b1,nRetVal b0=asc(tcInt) b1=asc(subs(tcInt,2,1)) if b1<128 * * positive - do a straight conversion * nRetVal=b1 * 256 + b0 else * * negative value - take twos complement and negate * b1=255-b1 b0=256-b0 nRetVal= -( (b1 * 256) + b0) endif return nRetVal FUNCTION WORDToNum *Take a binary WORD (16 bit USHORT) and convert it to a VFP Numeric LPARAMETER tcWORD RETURN (256 * ASC(SUBST(tcWORD,2,1)) ) + ASC(tcWORD) FUNCTION NumToDWORD * * Creates a 4 byte binary string equivalent to a C DWORD from a number * use to embed a pointer or other DWORD in a structure * Parameters: * * tnNum (R) Number to convert * LPARAMETER tnNum * * x,n,i,b[] will hold small ints * LOCAL x,n,i,b[4] x=INT(tnNum) FOR i=3 TO 0 STEP -1 b[i+1]=INT(x/(256^i)) x=MOD(x,(256^i)) ENDFOR RETURN CHR(b[1])+CHR(b[2])+CHR(b[3])+CHR(b[4]) FUNCTION NumToSHORT * * Creates a C SHORT as a string from a number * * Parameters: * * tnNum (R) Number to convert * LPARAMETER tnNum * * b0, b1, x hold small ints * LOCAL b0,b1,x IF tnNum>=0 x=INT(tnNum) b1=INT(x/256) b0=MOD(x,256) ELSE x=INT(-tnNum) b1=255-INT(x/256) b0=256-MOD(x,256) IF b0=256 b0=0 b1=b1+1 ENDIF ENDIF RETURN CHR(b0)+CHR(b1) FUNCTION NumToWORD * * Creates a C USHORT (WORD) from a number * * Parameters: * * tnNum (R) Number to convert * LPARAMETER tnNum * * x holds an int * LOCAL x x=INT(tnNum) RETURN CHR(MOD(x,256))+CHR(INT(x/256))