Much of the memory allocation required within a program is handled by GFABasic's commands and functions. However, every now and again, an occasion will arise when having direct access to reserved memory is preferable or the only way to carry out a task, and for that reason the following commands and their Window API equivalents have been included in GFABasic's list of commands and functions.
Using GFABasic Show
GFABasic uses the following commands to manage Memory Allocation:
addr% = mAlloc(size%)
newaddr% = mReAlloc(addr%, size%) newaddr% = mShrink(addr%, size%)
ret% = mFree(addr%)
addr%, newaddr% | : start of memory block |
size% | : size of memory block |
ret% | : integer return value |
The mAlloc(n) function is used to reserve a block of memory, allocates size% bytes of memory for the use of the program and returns the start of address of the memory block. The reserved memory block is not initialized (cleared and set to zero) - if that is desired, MemZero can be used.
mAlloc, when used with negative parameters, was also used for obtaining details of the status of the memory, but, since XP SP3, this function largely no longer works. Show workaround.
On any system running GFABasic 32 after XP SP3, the negative mAlloc commands from -1 to -4 no longer work and generally return -1 or incorrect values.
The workaround for this is to use MEMORYSTATUSEX as in the following example:
Declare Function GlobalMemoryStatusEx Lib "Kernel32" (MSE As MEMORYSTATUSEX)
Type MEMORYSTATUSEX
Length As Int32
MemoryLoad As Int32
TotalPhys As Large
AvailPhys As Large
TotalPageFile As Large
AvailPageFile As Large
TotalVirtual As Large
AvailVirtual As Large
AvailExtendedVirtual As Large
EndType
Dim m As MEMORYSTATUSEX, gb As Int32 = (1024 ^ 3), mb As Int32 = (1024 ^ 2)
m.Length = SizeOf(MEMORYSTATUSEX)
Print GlobalMemoryStatusEx(m)
Print "Total Physical Memory: "; Format(m.TotalPhys / gb, "0.0 GB"); _
" / "; Format(m.TotalPhys / mb, "0000"); " MB" // mAlloc(-2)
Print "Available Physical Memory: "; Format(m.AvailPhys / gb, "0.0 GB"); _
" / "; Format(m.AvailPhys / mb, "0000"); " MB" // mAlloc(-1)
Print "Total Page File: "; Format(m.TotalPageFile / gb, "0.0GB"); _
" / "; Format(m.TotalPageFile / mb, "0000"); " MB" // mAlloc(-4)
Print "Available Page File: "; Format(m.AvailPageFile / gb, "0.0GB"); _
" / "; Format(m.AvailPageFile / mb, "0000"); " MB" // mAlloc(-3)
Print "Total Virtual Memory: "; Format(m.TotalVirtual / gb, "0.0GB"); _
" / "; Format(m.TotalVirtual / mb, "0000"); " MB" // mAlloc(-6)
Print "Available Virtual Memory: "; Format(m.AvailVirtual / gb, "0.0GB"); _
" / "; Format(m.AvailVirtual / mb, "0000"); " MB" // mAlloc(-5)
Print "Available Extended Memory: "; Format(m.AvailExtendedVirtual / gb, "0.0GB"); _
" / "; Format(m.AvailExtendedVirtual / mb, "0000"); " MB"
Print "Memory Load: "; m.MemoryLoad & "%" // mAlloc(-7)
If the need arises to either enlarge or reduce the size of the reserved memory area, then either mReAlloc or mShrink can be used. These functions take two parameters: the start address of the area to be changed in addr% and the desired size of the new enlarged or shrunken block in size%. If the operation is successful, the functions create a new block of the required size, copy across the contents of the old block, free the old memory area and return the starting address of the new memory area as newaddr%; if not, then a negative error message is returned in newaddr% and the original memory block is retained. It should be noted again that neither of these functions initialize or clear the new block before loading in the old information.
Finally, to free the reserved memory, the mFree function is used: the start address of the memory block is passed as addr% and, if the return value is zero, the memory was successfully released; however, if an error occured with the operation, the return value will be a negative error value.
For more details, see Remarks below the section on 'Using Windows APIs'.
Local addr%, newaddr%, ret%
// Reserve a block of 256 bytes
addr% = mAlloc(256)
// Clear or initialise the block
MemZero addr%, 256
// Increase the block to 512 bytes
newaddr% = mReAlloc(addr%, 512)
// Free the memory block after use
ret% = mFree(newaddr%)
Note the use of newaddr% when resizing the memory block: this is so that, if the resize fails, you can still access the memory block using addr%.
Using Windows APIs Show
heap% = HeapCreate(flags%, size%, max_size%)
addr% = HeapAlloc(heap%, flags%, size%)
newaddr% = HeapReAlloc(heap%, flags%, addr%, size%)
ret% = HeapFree(heap%, flags%, addr%,)
ret% = HeapDestroy(heap%)
addr%, newaddr% | : start of memory block |
flags% | : flags to determine behaviour of function |
heap% | : address of heap |
max_size%, size% | : size of memory block |
ret% | : integer return value |
Using Windows APIs to manage memory allocation is more complicated than using the contemporary GFABasic functions, but not overly so. The additional factors that need to be addressed in each step are: in which memory heap is the memory to be reserved; and what flags need to be passed to determine the desired behaviour. In fact, both these factors can be ignored if necessary: simply using the API GetProcessHeap() to show that the reserved memory will come from the memory heap associated with the current process (the program itself), and setting flags% to zero in each case will ensure that HeapAlloc, HeapReAlloc and HeapFree work just like mAlloc, mReAlloc and mFree.
If, however, you wish to create a separate Heap in which to deploy your blocks of reserved memory, the process is simple enough using HeapCreate. This function takes three parameters, flags% (of more, see the Flags section below, but which will generally be zero for this function), size% which is the initial size of the Heap, and max_size% which is the maximum size to which the Heap can grow. These last two parameters can also be entered as 0 which will allow the Heap to expand virtually endlessly to accomodate all the reserved memory blocks you wish to store in it.
Allocating memory from the Heap is done with HeapAlloc which requires information about the Heap from which the memory is to be allocated, any flags which are pertinent (see Flags below) and the size of the desired memory block in bytes; if the function succeeds, it returns the start address of the memory block; if not, then it either returns Null or an error code, depending on which flags are set.
If at any stage you need to increase or reduce the size of the memory block, then HeapReAlloc should be used. This function takes four parameters: the Heap in which the memory block is to be found, any flags deemed pertinent, the start address of the memory block to be resized and the new size of the block. If this function succeeds, the new address of the block is returned; otherwise, Null or an error code is returned, depending on the flags set.
When the memory block is no longer required, the memory is freed with HeapFree. The parameters for this function are the Heap in which the memory block was created, any pertinent flags and the start address of the memory to be released. If this function succeeds, then a non-zero value is returned; otherwise zero is returned and more information can be retrieved by using GetLastError().
Finally, if you have created a custom Heap in which some or all of your reserved memory has been located, you need to free that as well - once all related memory blocks have been freed - and this is done with HeapDestroy. The sole parameter is the Heap ID itself and, if it succeeds a non-zero value is returned; otherwise, if zero is returned, more details of the error can be got using GetLastError.
All the flags that are pertinent to any and all of the Heap functions are listed below, along with to which of the functions they apply and a brief description of what they mean and do.
Flag | Is Used With | Description |
---|---|---|
HEAP_NO_SERIALIZE ($01) | HeapCreate, HeapAlloc, HeapReAlloc & HeapFree | Allocation of memory inside a Heap is usually serialized - memory blocks are usually contiguous. This is slow, reduces fragmentation but safer; if you want to increase speed, select this option to turn off serialization. |
HEAP_GENERATE_EXCEPTIONS($04) | HeapCreate, HeapAlloc & HeapReAlloc | The return value of a failed call is an exception rather than Null. (If the built-in Heap APIs are used, then this has no effect as GFABasic traps the error). |
HEAP_ZERO_MEMORY ($08) | HeapAlloc & HeapReAlloc | The memory block (or unused space) is initialised or zeroed. |
HEAP_REALLOC_IN_PLACE_ONLY ($10) | HeapReAlloc | The memory block is only resized if it is possible to fit the block into the Heap so that it has the same start address (setting HEAP_NO_SERIALIZE will help facilitate this). If this is not possible, then the memory area is not resized and the original block is retained. |
HEAP_CREATE_ENABLE_EXECUTE ($40000) | HeapCreate | All memory blocks that are allocated from this Heap allow code execution, if the hardware enforces data execution prevention. |
Local addr%, heap%, newaddr%, ret%
// Create a unique Heap
heap% = HeapCreate(0, 0, 0)
// Reserve Memory in the new Heap & Initialise
addr% = HeapAlloc(heap%, $8, 256)
// Resize Memory Block & Initialise
newaddr% = HeapReAlloc(heap%, $8, addr%, 512)
// Free the Reserved Memory
ret% = HeapFree(heap%, 0, newaddr%)
// Release the Heap itself
ret% = HeapDestroy(heap%)
Note the use of newaddr% when resizing the memory block: this is so that, if the resize fails, you can still access the memory block using addr%.
Remarks & Comparisons Show
It all depends what you are trying to do: if all you need is a memory area to load in a file or data then GFABasic's functions are probably the best option, and possibly slightly faster as well, although not enough to really make a difference; however, if you are designing a multi-threaded program with each thread processing data stored in reserved memory blocks, then the Windows APIs are probably the safer bet as you can assign a different Heap to each thread and thus reduce the possibility of conflicts and errors.
Put simply: GFABasic's functions are good but basic and should be used as a default choice; Windows APIs are more complex and require more care in use but, in return, they are more flexible, with additional functions over and above those listed above, such as HeapLock() and HeapUnLock(), HeapSize(), HeapCompact(), etc. See here for a full list.
The answer to this is yes...and no. There is no reason why you can not use GFABasic memory allocation functions and their corresponding Windows APIs in the same program; you can not, however, used them on the same memory blocks. In other words, if a memory block was created using mAlloc, you can not manipulate it with Window API functions and, similarly, if that block was created with HeapAlloc, GFABasic functions will not work with it. The reason for this is that GFABasic creates it own Heap internally and allocates memory from that and, as there is no way of getting that Heap ID, it is impossible to reference it with the Windows APIs...and vice versa.
One of the shortcomings of the GFABasic functions is that, once created, there is no way of determining the size of a memory block, short of storing this information in a separate variable or array (HeapSize(heap%, flags%, addr%) is available for Windows APIs). One workaround for this is to assign the first four bytes of any block to hold its size as a long integer as follows:
Local addr%
addr% = mAlloc(256)
LPoke addr%, 256
You may, however, decide that it is easier to use a variable, an array, or just keep track within the programming.
Since the dawn of Windows, there has been a problem with resizing memory blocks and yet, although there seem to be certain patterns, there seems to be no acknowledged reason why this should be. The most likely reason seems to be that Windows occasionally has problems resizing a block when the Heap in which it is situated is reaching its maximum limit or is heavily fragmented; certainly setting the HEAP_NO_SERIALIZE flags seems to affect when the resizing fails, but sadly does not seem to get rid of the problem totally.
One of the most effective workarounds to this problem is shown in the example below:
Local addr%, ct%, newaddr%
addr% = mAlloc(256)
newaddr% = mReAlloc(addr%, 512) // Try to resize the block
If newaddr% = 0 // Check for failure...
ct% = 0 // ... and, if so, reset the retry counter
While newaddr% = 0 And ct% < 5 // ....and try ...
newaddr% = mAlloc(512) // ... up to five times...
Inc ct% // ... to create a brand new block
Wend
If newaddr% <> 0 // If the new block is created successfully...
BMove addr%, newaddr%, 256 // ... copy over the data from the old block...
~mFree(addr%) // ... and then destroy the old block.
Else ' Error // Else, raise an error message
EndIf
EndIf
Generally, the new block will be created on the first attempt - it appears that whatever prevents a memory block being resized does not hinder the creation of new ones in the same heap - but the retry loop is just a precaution because, every once in a while, it does not. Also, the number of retries should be limited just in case the problem is more serious and prevents the reserving of new areas of memory; otherwise, in that situation, your program will just loop continuously.
{Created by James Gaite; Last updated: 08/09/2021 by James Gaite}