Introduction
Memory management on Palm handhelds is quite different from standard C/C++ and windows memory management that you may be used to. This article will try to shed some light on the crazy world of Palm memory.
Memory is Limited
Although newer Palms are getting more and more RAM most still have under 16MB and none of them have hard drives. All applications and databases are stored in the RAM so Palms have to do things a little differently than PCs. We wouldn't want our applications overwriting permanent storage now would we.
The RAM is divided into two sections: the storage area and the dynamic area (or dynamic heap). As you can guess, the storage area is where permanent data resides, like applications and databases, and is handled by the Database Manager. The dynamic heap is used for Palm OS globals, Palm OS dynamic allocations, your application's global variables, your application's stack space, and any dynamic allocations your application makes. The dynamic heap is handled by the Memory Manager and its size depends on the amount of RAM your Palm has, the OS version and the applications you have installed.
Because of the limited amount of dynamic memory, the Palm OS needs to be able to move chunks of memory around to keep its free space contiguous so there is enough room for new allocation. Thus there are two types of memory chunks, pointers and handles. Pointers are nonmovable chunks of memory, whereas handles are movable by the Palm OS. It is recommended that you use handles as much as possible.
The Memory Manager API
In order to manipulate memory on the Palm, you must use the Memory Manager API.
Using Handles
Lets first look at how we can allocate a new memory handle using the API.
- The
MemHandle MemHandleNew (UInt32 size)
function returns a newly allocated MemHandle
of the size in bytes specified in the size parameter. FYI: You cannot allocate a single memory chunk larger than 64KB.
Since the OS can move this handle around at will, you need to "lock" it in order to read or write to it.
- Use
MemPtr MemHandleLock (MemHandle h)
to lock a handle and obtain a MemPtr
(pointer) to the handle's chunk of memory.
After using a locked handle, you must "unlock" it quickly so the OS can move it around again.
- To unlock a previously locked handle, use
Err MemHandleUnlock (MemHandle h)
.
When you are finished with a dynamically created handle, you need to call Err MemHandleFree (MemHandle h)
to dispose of it.
Here is an example of using the Memory Manager functions for handles:
MemHandle myHandle = MemHandleNew(13); Char* myStr = (Char*)MemHandleLock(myHandle); StrCopy(myStr, "Hello World!"); MemHandleUnlock(myHandle); MemHandleFree(myHandle);
When a handle is locked, the OS increments a lock count for that handle. You can lock a handle up to 14 times before you receive a "chunk overlocked" error. Each lock you perform on a handle, you must also unlock. If you unlock a handle more times than the lock count, you will receive a "chunk underlocked" error.
An alternative to the MemHandleUnlock
function is Err MemPtrUnlock (MemPtr p)
. This is handy if you just want to pass your locked pointer to another function and your handle is not available. It decreases the lock count on the locked handle just like MemHandleUnlock
.
Using Pointers
To allocate a nonmovable chunk of memory (a pointer), use MemPtr MemPtrNew (UInt32 size)
. FYI: Remember, you cannot allocate a single memory chunk larger than 64KB.
When you are finished with the pointer, call Err MemPtrFree (MemPtr p)
to free the chunk.
Here is an example of using the pointer functions:
Char* myStr = (Char*) MemPtrNew(13); StrCopy(myStr, "Hello World!"); MemPtrFree(myStr);
You should use pointers instead of handles if you need the memory throughout your entire application, or if your allocation will be short lived. Keep in mind that frequent locking and unlocking of handles will also incur a performance cost over pointer allocation.
Other Memory Functions
Err MemSet (void* dstP, Int32 numBytes, UInt8 value)
will set
numBytes
of the
dstP
pointer to
value
(just like C++'s
memset
).
Err MemMove (void* dstP, const void* sP, Int32 numBytes)
will move numBytes
of the sP
source pointer to the dstP
destination pointer, handling overlapping ranges automatically.
UInt32 MemHandleSize (MemHandle h)
reports the size in bytes of a handle.
UInt32 MemPtrSize (MemPtr p)
reports the size in bytes of a pointer.
To resize an unlocked handle, call Err MemHandleResize (MemHandle h, UInt32 newSize)
. If you are making the handle larger and there is not enough free space after the handle, the OS will move the chunk to a new location.
If you need to resize a pointer, call Err MemPtrResize (MemPtr p, UInt32 newSize)
. A pointer will only be resized if you are making it smaller or if there is enough free space directly after the pointer. This function may also be used on a locked handle given its pointer.
The Demo
I modified the "Hello World!" application from my first palm article, An Introduction to Palm Handheld Development, to show how you would use the various Memory Manager functions. As before, the demo was compiled with Metrowerk's CodeWarrior IDE (version 6).
A static/global MemHandle
, called myHandle, was added which will hold the text to display on the screen. This handle is created in the MainFormInit
function then locked in the SayHello
or SayGoodbye
functions. The pointer returned from the lock is then passed to the DrawText
function for display to the screen. After returning from the drawing function, the handle or pointer is unlocked. The handle is freed in the MainFormClose
function.
As you will notice from the code, myHandle is being resized using MemHandleResize
and there are also calls to MemSet
and MemMove
in there just so you can see an example of how they are used. MemPtrResize
was not used in the demo since it may not always work. It is essentially the same as resizing a handle.
Here are the main code highlights:
static MemHandle myHandle = NULL;
static short nCount = 0;
static void DrawText(Char* pText)
{
short nCharWidth = 0;
short width = 0, height = 0;
nCharWidth = FntCharsWidth(pText, StrLen(pText));
WinGetWindowExtent(&width, &height);
WinDrawChars(pText, StrLen(pText), (width/2) - (nCharWidth/2), height/2);
}
static void SayHello()
{
if (MemHandleSize(myHandle) == 13 ||
MemHandleResize(myHandle, 13) == 0)
{
Char* pText = (Char*) MemHandleLock(myHandle);
MemSet(pText, 13, 0);
StrCopy(pText, "Hello World!");
DrawText(pText);
MemHandleUnlock(myHandle);
}
}
static void SayGoodbye()
{
nCount++;
if (MemHandleSize(myHandle) == 15 ||
MemHandleResize(myHandle, 15) == 0)
{
Char* pText = (Char*) MemHandleLock(myHandle);
MemSet(pText, 15, 0);
StrCopy(pText, "Goodbye World!");
if (nCount%2 == 0)
{
Char* pTemp = (Char*) MemPtrNew(15);
StrCopy(pTemp, "Bye bye World!");
MemMove(pText, pTemp, 7);
MemPtrFree(pTemp);
pTemp = NULL;
}
DrawText(pText);
MemPtrUnlock(pText);
}
}
static void MainFormInit(FormType* frmP)
{
myHandle = MemHandleNew(20); }
static void MainFormClose()
{
if (myHandle != NULL)
{
MemHandleFree(myHandle);
myHandle = NULL;
}
}
Conclusion
From my own experience, Palm memory handling can be quite harrowing. Writing this article has helped me understand the whole system a little better and I hope it will help you as well
Look for more palm articles in the near future from myself and Christian Graus. We plan to cover palm databases, custom drawn lists, tables, palm conduits and other relevant topics specific to the PalmOS.