Order the book from O'Reilly

Previous PageTable Of ContentsIndexNext Page

In this chapter:

 4.  Structure of an Application

The overall flow and content of a Palm OS application is the subject of this chapter. You will learn:

Terminology

Top Of Page

Like every operating system and coding interaction, the Palm OS has it own set of necessary terminology for you to learn. Much of it may already be familiar to you from other applications you have written. We suggest that you skim through this list and concentrate on the items that are new to you. New and unique terminology is listed first.

Form

An application window (what many people would think of as a view) that usually covers the entire screen. A form optionally contains controls, text areas, and menus. In a Palm OS application, there is only one active form allowed at a time. Chapter 5, Forms and Form Objects, covers forms in detail.

Window

A rectangular area in which things like dialogs, forms, and menus are drawn by the application. The Window Manager makes sure that windows properly display relative to each other (for example, it has the ability to restore the old contents when a window is closed). Note in a rather Shakespearian twist of logic that all forms are windows, even though all windows are not forms.

Database

A collection of persistent memory chunks. There are two kinds: resource and record databases.

Resource

A piece of data stored in a resource database. Each resource is identified by a resource type and number. A Palm application is a collection of resources. Chapter 5 covers resources in more detail.

Record

A data structure identified by a unique record ID. Applications typically store data in record databases.

Event

A data structure that describes things that happen in an application. Events can be low-level hardware events like a pen down, pen up, or hardware key press. They can also be higher-level events like a character entered, a menu item selected, or a software button pressed.

The Palm OS is an event-driven system. Only one application is open at a time. When that application is running, it runs an event loop that retrieves events and continues to handle them until the user starts another application.

Main event loop

The main loop of execution in an application, which repeatedly retrieves events and then acts on them.

Launch code

A parameter passed to an application that specifies what the application should do when that particular launch code is executed. An application typically handles more than one launch code. This is the communication method used between the OS and an application and between applications.

Menu

Menus are stored in resources grouped together into menubars and are displayed when the user taps the menu area. See Chapter 7, Menus, for more details.

Menubar

A collection of menus stored in a resource. Each form can have a menubar associated with it.

Dialog

A window containing controls that require the user to make a decision. In other words, the dialog must be dismissed (usually by tapping on one of its buttons) before the application can continue.

Alert

A warning or information dialog that needs to be dismissed by the user.

These brief descriptions cover the most important terminology. In the following section, we look at the basic elements of a very small Palm OS application.

A Simple Application

Top Of Page

Creating a small application before tackling a more complex one is a good way to gain familiarity with a new coding challenge. First, we tell you what our little application does and show you the code for it. After that we do a code walkthrough and point out important elements.

What the Application Does

Our Hello World application displays the words "Hello World" and provides a button to press. Pressing the button displays an alert, as shown in Figure 4-1, which is dismissed by tapping OK. There are two menus, each with one menu item (see Figure 4-2). As this is a very simple application, you just get a beep when you choose either menu item.

-Figure 4- 1. Dialog shown after tapping the button

Figure 4- 2. The menus of Hello World

The Hello World Source Code

Now that you have an idea of what the application can do, look at Example 4-1 to see the code that produces it. Once you have looked through it for yourself, we will discuss it.

 -Example  4- 1. The Hello World Source Code

#include <Pilot.h>  
#ifdef __GNUC__
#include "Callback.h"
#endif
#include "HelloWorldRsc.h"    

static Err StartApplication(void)
{
  FrmGotoForm(HelloWorldForm);
  return 0;
}

static void StopApplication(void)
{
}

static Boolean MyFormHandleEvent(EventPtr event)
{
  Boolean    handled = false;

#ifdef __GNUC__
   CALLBACK_PROLOGUE
#endif
   switch (event->eType) {
   case ctlSelectEvent:  // A control button was pressed and released.
      FrmAlert(GoodnightMoonAlert);
      handled = true;
      break;
    
   case frmOpenEvent:  
      FrmDrawForm(FrmGetActiveForm());
      handled = true;
      break;
      
   case menuEvent:    
      if (event->data.menu.itemID == FirstBeep)
         SndPlaySystemSound(sndInfo);
      else
         SndPlaySystemSound(sndStartUp);
      handled = true;
      break;
   }
#ifdef __GNUC__
   CALLBACK_EPILOGUE
#endif
   return handled;
}

static Boolean ApplicationHandleEvent(EventPtr event)
{
   FormPtr  frm;
   Int    formId;
   Boolean  handled = false;

   if (event->eType == frmLoadEvent) {
      //Load the form resource specified in the event then activate it
      formId = event->data.frmLoad.formID;
      frm = FrmInitForm(formId);
      FrmSetActiveForm(frm);

      // Set the event handler for the form.  The handler of the currently 
      // active form is called by FrmDispatchEvent each time it is called 
      switch (formId) {
      case HelloWorldForm:
         FrmSetEventHandler(frm, MyFormHandleEvent);
         break;
      }
      handled = true;
   }
  
   return handled;
}

static void EventLoop(void)
{
   EventType  event;
   Word      error;
  
   do {
    EvtGetEvent(&event, evtWaitForever);
    if (! SysHandleEvent(&event))
       if (! MenuHandleEvent(0, &event, &error))
          if (! ApplicationHandleEvent(&event))
             FrmDispatchEvent(&event);
   } while (event.eType != appStopEvent);
}

DWord PilotMain(Word launchCode, Ptr cmdPBP, Word launchFlags)
{
  Err err;    
  
  if (launchCode == sysAppLaunchCmdNormalLaunch) { 
     if ((err = StartApplication()) == 0) {
        EventLoop();
        StopApplication();
     }
   }

   return err;
}

A Code Walkthrough of Hello World

Let's start at the beginning with the #include files.

The #include files

Pilot.h is an include file that itself includes most of the standard Palm OS include files (using CodeWarrior, Pilot.h actually includes a prebuilt header file to speed compilation). To keep things simple, our application doesn't use anything beyond the standard Palm OS include files. Indeed, any calls outside the standard ones would have necessitated the use of other specific Palm OS include files.

The second include file, Callback.h, defines some macros needed if you are using GCC. They are needed to handle callbacks from the Palm OS to your code. We discuss this in "Callbacks in GCC" on page 78.

The third include file, HelloWorldRsc.h, defines constants for all the application's resources (for example, HelloWorldForm). As we'll see in Chapter 5, if you use Constructor, this file is generated automatically (see Example 4-2). If you use the GNU PalmPilot SDK, you usually create this file yourself (see Example 4-3).

Example  4- 2. HelloWorldRsc.h Generated by Constructor (Used with CodeWarrior)

// Header generated by Constructor for Pilot 1.0.2
//
// Generated at 9:55:01 PM on Thursday, August 20, 1998
//
// Generated for file: Macintosh HD:Palm:HelloWorld:Rsc:Hello.rsrc
//
// THIS IS AN AUTOMATICALLY GENERATED HEADER FILE FROM CONSTRUCTOR FOR PALMPILOT;
// - DO NOT EDIT - CHANGES MADE TO THIS FILE WILL BE LOST
//
// Pilot App Name:      "Hello World"
//
// Pilot App Version:      "1.0"

// Resource: tFRM 1000
#define HelloWorldForm                            1000   
#define HelloWorldButtonButton                    1003   

// Resource: Talt 1101
#define GoodnightMoonAlert                        1101
#define GoodnightMoonOK                           0

// Resource: MBAR 1000
#define HelloWorldMenuBar                         1000

// Resource: MENU 1010
#define FirstMenu                                 1010
#define FirstBeep                                 1010

// Resource: MENU 1000
#define SecondMenu                                1000
#define SecondBeepmore                            1000

Example  4- 3. HelloWorldRsrc.h Created by Hand (Used with GNU PalmPilot SDK)

#define HelloWorldForm                            1000   
#define HelloWorldButtonButton                    1003   
#define HelloWorldMenuBar                         1000

#define GoodnightMoonAlert                        1101

#define FirstBeep                                 1010

#define SecondBeepmore                            1000

The main routine: PilotMain

 Example 4-4 shows the main entry point into your application. The first parameter is the launch code. If your application is being opened normally, this parameter is the constant sysAppLaunchCmdNormalLaunch. The second and third parameters are used when the application is opened at other times.

Example  4- 4. PilotMain

DWord PilotMain(Word launchCode, Ptr cmdPBP, Word launchFlags)
{
   Err err;    
  
   if (launchCode == sysAppLaunchCmdNormalLaunch) {
      if ((err = StartApplication()) == 0) {
         EventLoop();
         StopApplication();
      }
   }

   return err;
}

If the launch code is sysAppLaunchCmdNormalLaunch, we do an initialization in StartApplication and run our event loop until the user does something to close the application. At that point, we handle termination in StopApplication.

The startup routine: StartApplication

 In the routine shown in Example 4-5, we handle all the standard opening and initialization of our application. In more complicated applications, this would include opening our databases and reading user preference information. In our rudimentary Hello World application, all we need to do is tell the Form Manager that we want to send our (one and only) form. This queues up a frmLoadEvent in the event queue.

Example  4- 5. StartApplication

static Err StartApplication(void)
{
   FrmGotoForm(HelloWorldForm);
   return 0;
}

The closing routine: StopApplication

 Because we are creating such a simple application, we don't actually have anything to do when it's closing time. We provided the routine in Example 4-6 so that our Hello World source code would have the same standard structure as other Palm applications.

Example  4- 6. An Empty StopApplication

static void StopApplication(void)
{
}

Normally in StopApplication we handle all the standard closing operations, such as closing our database, saving the current state in preferences, and so on.

The main event loop

In PilotMain, you will notice that after the initialization there is a call to the one main event loop (see Example 4-7). In this loop, we continually process events-handing them off wherever possible to the system. We go through the loop, getting an event with   EvtGetEvent, and then dispatch that event to one of four nested event handlers, each of which gets a chance to handle the event. If an event handler returns true, it has handled the event and we don't process it any further. EvtGetEvent then gets the next event in the queue, and our loop repeats the process.

The loop doggedly continues in this fashion until we get the appStopEvent, at which time we exit the function and clean things up in StopApplication.

Example  4- 7. EventLoop

static void EventLoop(void)
{
   EventType  event;
   Word      error;
  
   do {
    EvtGetEvent(&event, evtWaitForever);             system routine
    if (! SysHandleEvent(&event))                    system routine
       if (! MenuHandleEvent(0, &event, &error))     system routine
          if (! ApplicationHandleEvent(&event))      routine we write
             FrmDispatchEvent(&event);               system routine
   } while (event.eType != appStopEvent);
}

Handling events with EvtGetEvent

This Event Manager routine's sole purpose in life is to get the next event from the queue. It takes as a second parameter a time-out value (in ticks-hundredths of a second). EvtGetEvent returns either when an event has occurred (in which case it returns true) or when the time-out value has elapsed (in which case it returns false and fills in an event code of nilEvent).

We don't have anything to do until an event occurs (this application has no background processing to do), so we pass the evtWaitForever constant, specifying that we don't want a time-out.  

The event queue and application event loop

Let's step back for a moment and look at the events that are received from EvtGetEvent. Events can be of all different types, anything from low-level to high-level ones. In fact, one useful way to look at a Palm application is simply as an event handler-it takes all sorts of events, handing them off to various managers, which in turn may post a new event back to the queue, where it is handled by another event handler. We will discuss more sophisticated examples of this later (see "Scenarios" later in this chapter), but for now look at a very simple set of events to get an idea of how this all works together. Imagine the user has our application open and taps the stylus on the screen in the area of the silk-screened Menu button. The first time through the event queue the  SysHandleEvent routine handles the event, interprets it, and creates a new event that gets put back in the queue (see Figure 4-3).

Figure 4- 3. An event in the event loop

This new event, when it comes through the loop, gets passed through SysHandleEvent and on to the  MenuHandleEvent, as it is now recognizable as a menu request (see Figure 4-4). MenuHandleEvent displays the menubar and drops down one of the menus. If the user now taps outside the menu, the menus disappear.

Figure 4- 4. A regurgitated event in the event loop

If a menu item is selected, however, a new event is generated and sent to the queue. This event is retrieved by the event loop, where it is passed through SysHandleEvent and on to MenuHandleEvent. Given the way this process works, you can see that the different managers are interested in different types of events. Keeping this in mind, let's now return to our code and look at the event loop and the four routines in it.

SysHandleEvent

The first routine in the loop is always  SysHandleEvent, as it provides functionality common to all Palm applications. For instance, it handles key events for the built-in application buttons. It does so by posting an appStopEvent to tell the current application to quit; the system can then launch the desired application.

It handles pen events in the silk-screened area (the Graffiti input area and the silk-screened buttons). For example, if the user taps on Find, SysHandleEvent completely handles the Find, returning only when the Find is done.

Here are some of the more important events it handles:

keyEvent

Occurs, among other times, when one of the built-in buttons is pressed. The keycode specifies which particular button is pressed. SysHandleEvent handles pen events in the Graffiti input area. When a character is written, SysHandleEvent posts a keyEvent with the recognized character.

penDownEvent

Occurs when the user presses the stylus to the screen.

penMoveEvent

Occurs when the user moves the stylus on the screen.

NOTE:

penMoveEvents aren't actually stored in the event queue, because there are so many of them. Instead, when EvtGetEvent is called, if no other events are pending, EvtGetEvent will return a penMoveEvent if the pen is down and has moved since the last call.

MenuHandleEvent

The second routine in our event loop is  MenuHandleEvent. As you might have imagined, the MenuHandleEvent handles events involving menus. These events occur when a user:

MenuHandleEvent also switches menus if the user taps on the menubar. As would be expected, it closes the menu and menubar if the user taps on a menu item. At this point, it posts a menu event that will be retrieved in a later call to EvtGetEvent.

ApplicationHandleEvent

The third routine,   ApplicationHandleEvent, is also a standard part of the event loop and is responsible for loading forms and associating an event handler with the form. Note that this is also the first time our application is doing something with an event. Here is the code in our Hello World application for that routine:

static Boolean ApplicationHandleEvent(EventPtr event)
{
   FormPtr  frm;
   Int    formId;
   Boolean  handled = false;

   if (event->eType == frmLoadEvent) {
   // Load the form resource specified in the event and activate the form.
      formId = event->data.frmLoad.formID;
      frm = FrmInitForm(formId);
      FrmSetActiveForm(frm);

  // Set the event handler for the form.  The handler of the currently 
  // active form is called by FrmDispatchEvent each time it gets an event.
      switch (formId) {
      case HelloWorldForm:
         FrmSetEventHandler(frm, MyFormHandleEvent);
         break;
      }
      handled = true;
   }
   return handled;
}

While we'll see a more complex example of ApplicationHandleEvent in Chapter 5, you can at least see that our routine handles the request to load our sole form.

Callbacks in GCC

We need to swerve down a tangent for a moment to discuss GCC. There is one way that your code will differ depending on whether you use GCC or CodeWarrior. Even if you're not using GCC, it's still worth reading this section to learn why we sprinkled a bunch of "#ifdef __GNUC__ " in our functions.

The GCC compiler's calling conventions differ from those in the Palm OS. In particular, the GCC compiler expects at startup that it can set up the A4 register (which it uses to access global variables) and that it will remain set throughout the life of the application. Unfortunately, this is not true when a GCC application calls a Palm OS routine that either directly or indirectly calls back to a GCC function (a callback).

The most common example of this occurrence is when we've installed an event handler for a form with  FrmSetEventHandler. Once we've done that, a call to  FrmDispatchEvent (a Palm OS routine) can call our form's event handler (a GCC function, if we've compiled our application with GCC). At this point, if our event handler tries to access global variables, it'll cause a spectacular application crash.

The solution is to use a set of macros that set the A4 register on entry to the callback function and restore it on exit. You need to provide a Callback.h header file as part of your project (see Example 4-8) and #include it in your file. Then, every callback needs to add the CALLBACK_PROLOGUE macro at the beginning of the callback function (just after variables are declared) and a CALLBACK_EPILOGUE macro at the end of the callback function. Here's a very simple example:

static int MyCallback()
{
   int myReturnResult;
   int anotherVariable;
#ifdef __GNUC__
   CALLBACK_PROLOGUE
#endif
   // do stuff in my function
#ifdef __GNUC__
   CALLBACK_EPILOGUE
   return myReturnResult;
}

It's crucial that you don't try to access global variables before the CALLBACK_ PROLOGUE macro. For example, here's code that will blow up because you're accessing globals before the macro has had a chance to set the A4 register:

static int MyCallback()
{
   int myVariable = gSomeGlobalVar;
#ifdef __GNUC__
   CALLBACK_PROLOGUE
#endif
   ...
}

It's also important that you return from your function at the bottom. If you must ignore our advice and return from your function in the middle, make sure to add yet another instance of the CALLBACK_EPILOGUE right before the return.

 -Example  4- 8. The Callback.h File, Needed for GCC

#ifndef __CALLBACK_H__
#define __CALLBACK_H__

/* This is a workaround for a bug in the current version of gcc: gcc assumes
   that no one will touch %a4 after it is set up in crt0.o. This isn't true
   if a function is called as a callback by something that wasn't compiled by
   gcc (like FrmCloseAllForms()).  It may also not be true if it is used as a
   callback by something in a different shared library. We really want a function
   attribute "callback" that inserts this prologue and epilogue automatically.
- Ian */

register void *reg_a4 asm("%a4");

#define CALLBACK_PROLOGUE \
   void *save_a4 = reg_a4; asm("move.l %%a5,%%a4; sub.l #edata,%%a4" : :);
#define CALLBACK_EPILOGUE reg_a4 = save_a4;

#endif
NOTE:

There's been some discussion among those who use the GCC compiler about a more convenient solution to the Example 4-8 workaround. Some folks want to get rid of the macros by modifying the GCC compiler with a callback attribute to the function declaration. This would cause the compiler to add code that manages A4 correctly. Here's an example:

callback int MyCallback()

{

// code which can safely access globals

}

NOTE:

Others want a more radical solution. They want to be able to use all functions as callbacks without any special declaration.

 

FrmDispatchEvent

This fourth and last routine in the event loop is the one that indirectly provides form-specific handling. This routine handles standard form functionality (for example, a pen-down event on a button highlights the button, a pen-up on a button posts a ctlSelectEvent to the event queue). Cut/copy/paste in text fields are other examples of functionality handled by FrmDispatchEvent. In order to provide form-specific handling, FrmDispatchEvent also calls the form's installed event handler. Therefore, when FrmDispatchEvent gets an event, it calls our own MyFormHandleEvent routine:

static Boolean MyFormHandleEvent(EventPtr event)
{
   Boolean    handled = false;

   switch (event->eType) {
    case ctlSelectEvent:  // A control button was pressed and released.
       FrmAlert(GoodnightMoonAlert);
       handled = true;
       break;
    
    case frmOpenEvent:  
       FrmDrawForm(FrmGetActiveForm());
       handled = true;
       break;
      
    case menuEvent:    
       if (event->data.menu.itemID == FirstBeep)
          SndPlaySystemSound(sndInfo);
       else
          SndPlaySystemSound(sndStartUp);
      handled = true;
      break;
   }
   return handled;
}

As the code indicates, we take an action if the user taps on our button or chooses either of the two menu items. We beep in either case.

Hello World Summary

In this simple application, we have all the major elements of any Palm application. In review, these are:

Scenarios

Top Of Page

Now that you have a better understanding of the code in the Hello World application, let's take a close look at what happens as events are passed from the event queue into the event loop. Unlike our earlier example, where we hand-waved through the technical parts of what happens when a menu was chosen, we will now look with great detail at three different user actions and the flow through the code as these scenarios occur.

This first code excerpt shows what happens when a user opens the application by tapping on the application's icon. Example 4-9 shows the flow of events. Pay particular attention to the frmLoadEvent, which is handled by  ApplicationHandleEvent, and the frmOpenEvent, which is handled by MyFormHandleEvent.

 -Example  4- 9. Flow of Control as Hello World Application Is Opened

PilotMain (enter)
  StartApplication (enter)
    FrmGotoForm(HelloWorldForm)                  open the HelloWorldForm
  StartApplication (exit)                        returns 0 (proceed)
  EventLoop (enter)
      EvtGetEvent                                   returns frmLoadEvent (formID 
                                                    HelloWorldForm)
    SysHandleEvent                               returns false
    MenuHandleEvent                              returns false
    ApplicationHandleEvent (enter)
      FrmInitForm(HelloWorldForm)                load the form
      FrmSetActiveForm(frm)                      activate the form
      FrmSetEventHandler(frm, MyFormHandleEvent) set the event handler
    ApplicationHandleEvent (exit)                returns true
  EvtGetEvent                                    returns frmOpenEvent
  SysHandleEvent                                 returns false
  MenuHandleEvent                                returns false
  ApplicationHandleEvent                         returns false
  FrmDispatchEvent (enter)                       calls form's event handler
    MyFormHandleEvent (enter)
      FrmDrawForm(FrmGetActiveForm())            draws the form and its contents
    MyFormHandlEvent (exit)                      returns true

In Example 4-10 our user taps on the button labeled "Button," which in turn puts up an alert. Notice that eventually, the penDownEvent is transformed into a ctlSelectEvent, which is handled by our routine, MyFormHandleEvent.

Example  4- 10. Flow of Control in Event Loop When "Button" Button Is Pressed

EvtGetEvent                         returns penDownEvent
SysHandleEvent                      returns false
MenuHandleEvent                     returns false
ApplicationHandleEvent               returns false
FrmDispatchEvent (enter)
  MyFormHandleEvent                 returns false
  CtlHandleEvent                    standard control-manager routine that posts 
                                       ctlEnterEvent to the event queue and returns true.
                                      a tap hits a usable control; a ctlEnterEvent is sent
FrmDispatchEvent (exit)             returns true

EvtGetEvent                         returns ctlEnterEvent
SysHandleEvent                      returns false
MenuHandleEvent                     returns false
ApplicationHandleEvent              returns false
FrmDispatchEvent (enter)
  MyFormHandleEvent                 returns false
  CtlHandleEvent                    inverts the button and waits for the pen to be lifted
                                      (EvtGetPen); when the pen is lifted, inverts
                                      the button; posts ctlSelectEvent to the event queue
                                      as the pen is lifted from the control; returns true
FrmDispatchEvent (exit)             returns true

EvtGetEvent                         returns ctlSelectEvent
SysHandleEvent                      returns false
MenuHandleEvent                     returns false
ApplicationHandleEvent              returns false
FrmDispatchEvent (enter)
  MyFormHandleEvent (enter)
    FrmAlert                        returns after the OK button has been pressed 
                                      (FrmDoAlert has its own event loop)
  MyFormHandleEvent (exit)          returns true

EvtGetEvent                         returns penUpEvent
SysHandleEvent                      returns false
MenuHandleEvent                     returns false
ApplicationHandleEvent              returns false
FrmDispatchEvent (enter)
  MyFormHandleEvent                 returns false
FrmDispatchEvent (exit)             returns false

Last, but not least, examine Example 4-11 to see what happens when the user finally chooses a menu item. The penDownEvent is transformed into a keyEvent (tapping on the hardware keys or on the soft buttons causes a keyEvent to be posted). When the user finally taps on a particular menu item, a menuEvent is posted to the event queue, which is once again handled by MyFormHandleEvent.

Example  4- 11. Event Loop Handling a Menu Item

                              Tap on Menu button
EvtGetEvent                   returns penDownEvent
SysHandleEvent                tracks pen; doesn't return until pen up; returns true
  
EvtGetEvent                   returns penUpEvent
SysHandleEvent                posts keyDownEvent on the event queue and returns true
  
EvtGetEvent                   returns keyDownEvent with key: menuChr (0x105). This 
                                is a special system key event that triggers menu
                                code in MenuHandleEvent
SysHandleEvent                returns false
MenuHandleEvent               puts up menu bar and "First" menu and returns true

                              Tap on Second menu
EvtGetEvent                   returns penDownEvent
SysHandleEvent                returns false
MenuHandleEvent               puts up "Second" menu and returns true
   
EvtGetEvent                   returns penUpEvent
SysHandleEvent                returns false
MenuHandleEvent               returns false
ApplicationHandleEvent        returns false
FrmDispatchEvent (enter)      calls MyFormHandleEvent
  MyFormHandleEvent           returns false
FrmDispatchEvent (exit)       returns false
  
                              Tap on Beep Another Item
EvtGetEvent                   returns penDownEvent
SysHandleEvent                returns false
MenuHandleEvent               removes menubar and menu and posts menuEvent to the 
                                event queue and returns true
  
EvtGetEvent                   returns menuEvent with itemID: 1000
SysHandleEvent                returns false
MenuHandleEvent               returns false
ApplicationHandleEvent        returns false
FrmDispatchEvent (enter)      calls MyFormHandleEvent
  MyFormHandleEvent           beeps and returns true
FrmDispatchEvent (exit)       returns true

EvtGetEvent                   returns penUpEvent
SysHandleEvent                returns false
MenuHandleEvent               returns false
ApplicationHandleEvent        returns false
FrmDispatchEvent (enter)      calls MyFormHandleEvent
  MyFormHandleEvent           returns false
FrmDispatchEvent (exit)       returns false

Memory Is Extremely Limited

Top Of Page

Now that you have an idea of how the system hands events off to the application, it's time to look at how memory is handled. To start off, it will help if you remember one crucial point: memory is an extremely limited resource on Palm OS devices. Because of this, Palm OS applications need to be written with careful attention to memory management.

To that end, let's examine the memory architecture on Palm devices. RAM is divided into two areas: storage and dynamic (see Figure 4-5). The storage area of memory is managed by the Database Manager, which we discuss in Chapter 6, Databases. It is dynamic memory, which is handled by the Memory Manager, that we discuss here.

Figure 4- 5. Memory map

The dynamic memory is used for Palm OS globals, Palm OS dynamic allocations, your application's global variables (note that C statics are a form of globals), your application's stack space, and any dynamic memory allocations you make. As you can see in Table 4-1, the size available depends on the operating system and on the amount of total memory on the device.

-Table 4- 1. Dynamic Memory Usage for Various Palm OS Configurations

The Dynamic Heap

The dynamic memory area is called the dynamic heap. You can allocate from the heap as either nonrelocatable chunks (called pointers) or relocatable chunks (called handles). It is always preferable to use handles wherever possible (if you're going to keep something locked for its entire existence, you might as well use a pointer). This gives the memory manager the ability to move chunks around as necessary and to keep free space contiguous.

In order to read or modify the contents of a relocatable block, you temporarily lock it. When a memory allocation occurs, any unlocked relocatable block can be relocated (see Figure 4-6 for a diagram of unlocked relocatable blocks moving due to a memory allocation).

Figure 4- 6. The dynamic heap before and after doing an allocation

Memory API

Here is the API for using handles in your code.  MemHandleNew lets you allocate a handle like this:

VoidHand myHandle = MemHandleNew(chunkSize)

MemHandleNew will return NULL if the allocation was unsuccessful.

Before you read from or write to a handle in your program, you need to lock it. You do so by calling MemHandleLock, which returns a pointer to the locked data. While the handle is locked, the relocatable block can't be moved and you can do things like reading and writing of the data. In general, you should keep a handle locked for as short a time as possible (keeping in mind, however, that there is a performance cost to repetitive locking and unlocking); locked handles tend to fragment free memory when compaction takes place. Here is the code to lock and unlock a memory handle:

void *myPointer = MemHandleLock(myHandle);
// do something with myPointer
MemHandleUnlock(myHandle);

  MemHandleLock and MemHandleUnlock calls can be nested, because MemHandleLock increments a lock count (you can have a maximum of 15 outstanding locks per handle). MemHandleUnlock decrements the lock count. Note that it doesn't actually allow the chunk to move unless the lock count hits 0. If you get overeager and try to lock a handle that's already been locked 15 times, you get a runtime "chunk overlocked" error message. Similarly, unlocking a handle that is already unlocked (whose lock count is 0) generates a "chunk underlocked" error message.

Alternatively, you can call  MemPtrUnlock. This may be more convenient, especially when the unlock is in a separate routine from the lock. This way you only have to pass around the locked pointer.

Lock Counts

A lock count allows nested locking to work. For example, imagine the following code:

    void A(VoidHand h)

    {

    VoidPtr p = MemHandleLock(h);

    // do stuff with P

    B(h);

    // code after B

    MemHandleUnlock(h);

    }

    void B(VoidHand h)

    {

    VoidPtr s = MemHandleLock(h);

    // do stuff with s

    MemHandleUnlock(h);

    }

When A locks h, its lock count goes to 1. When A calls B, it passes this locked handle. When B locks it again, the lock count goes to 2. When B unlocks it, it goes down to 1. After B returns, the handle is still locked, with a lock count of 1. After A unlocks it, it is really unlocked.

If MemHandleLock and MemHandleUnlock didn't use lock counts (some operating systems do provide handle locking but don't use lock counts), there would be a problem with the previous code. When B unlocked the handle, it would in fact be unlocked. Then, in A's code after the call to B, but before the call to unlock the handle, the handle would be unlocked. If A's code used the pointer p during that time, havoc could ensue, as p is no longer valid once its handle is unlocked (actually, it's still valid until the chunk moves, but that could happen any time after the handle is unlocked).

Lock counts add a small amount of complexity to the Memory Manager, but make applications easier to code.

To allocate a pointer, use  MemPtrNew:

struct s *myS = MemPtrNew(sizeof(struct s));

To free a chunk, use   MemPtrFree or MemHandleFree:

MemPtrFree(myPointer);
MemHandleFree(myHandle);

As a chunk is allocated, it is marked with an owner ID. When an application is closed, the Memory Manager deallocates all chunks with that owner ID. Other chunks (for instance, those allocated by the system with a different mark) are not deallocated.

You shouldn't rely on this cleanup, however. Instead, you should code your application to free all its allocated memory explicitly. Just consider the system cleanup to be a crutch for those application writers who aren't as fastidious as you. However, in the rare case that you might forget a deallocation, the system will do it for you.

This cleanup makes the lives of Palm device users much happier. They are no longer prey to every poorly written application with a memory leak. Without this behavior, there would be no cleanup of memory allocated by an application but never deallocated. Imagine an application that allocates 50 bytes every time it is run but never deallocates it. Running the application twice a day for two weeks uses 1,400 bytes of dynamic memory that could be reclaimed only by a reset. A Palm device isn't like a desktop computer that is rebooted fairly often (at least we know our desktop computers are rebooted fairly often!). Instead, a Palm device should run months or years without a reset. The fact that handhelds need a reset button is a flaw. (Don't get us wrong, though; given the current state of affairs, handhelds do need reset buttons.)

The Memory Manager provides other facilities, including finding the size of a chunk, resizing a chunk, and finding a handle given a locked pointer. For more information about these routines, you should see the Memory Manager documentation (or the include file MemoryMgr.h).

Last, there are two useful memory utility routines you should know about. They are   MemSet and MemMove:

MemSet(void *p, ULong numBytes, Byte value)
MemMove(void *from, void *to, ULong numBytes)

MemSet sets a range of memory to the specified byte value. MemMove copies the specified number of bytes from a particular range to another range (it correctly handles the case where the two ranges overlap).

Other Times Your Application Is Called

Top Of Page

The Palm OS makes a distinction between communicating with the active application and communicating with a possibly inactive application. In this first case, the active application is busy executing an event loop and can be communicated with by posting events to the event queue. As shown in Hello World, this was how our application got closed; the appStopEvent was posted to the event queue. When the active application gets that event, it quits.

Because there are other times that your application gets called by the Palm OS, there needs to be a way to communicate with it in those instances. First, let's look at a list of the circumstances under which the system might want to talk to your application:

In all these cases, a communication must take place to an inactive or closed application. The question is how the system does this. The answer is launch codes; all these communications are handled by your application's launch codes.

Launch Codes

Within the Palm OS, the launch code specifies to the application which of the circumstances just listed exist and what the application needs to do. These codes arrive at the application's  PilotMain routine by way of its launchCode parameter. Here are some common launch codes:

sysAppLaunchFind

This code tells the application to look up a particular text string and return information about any matching data.

sysAppLaunchGoTo

This code tells the application to open, if it isn't already open, and then to go to the specified piece of data.

sysAppLaunchNormal

As we have already seen, this code opens the application normally.

Launch Flags

Associated with these launch codes are various launch flags. The launch flags specify important information about how the application is being executed. Here are some examples:

A Few Scenarios

To help make this whole relationship between the application and when it gets called by the system clear, let's look at some examples of when this happens and what the flow of the code is like.

Example 4-12 shows what happens when a user does a Find when the built-in application Memo Pad is open. The PilotMain of Hello World is called with the sysAppLaunchCmdFind launch code and with no launch flags.

 -Example  4- 12. Flow of Control When User Chooses Find When MemoPad Is Open

MemoPad's                                     sysAppLaunchFlagNewStack AND 
PilotMain(sysAppLaunchCmdNormalLaunch,params, sysAppLaunchFlagNewGlobals AND
flags)                                        sysAppLaunchFlagUIApp
  MemoPad's EventLoop
  SysHandleEvent (enter)
                                              after user taps Find
    Loop through all applications:
      MemoPad's PilotMain(sysAppLaunchCmdFind,
        parameters, sysAppLaunchFlagSubCall)
      PilotMain(sysAppLaunchCmdFind,          calls HelloWorld's PilotMain
        parameters, 0)
  SysHandleEvent (exit)

Now take a look in Example 4-13. This is what happens when we do a Find with our application already open. In this case, HelloWorld's PilotMain is called with the same launch code, sysAppLaunchCmdFind, but with the launch flag sysAppLaunchFlagSubCall, specifying that the HelloWorld application is already open and running. This signifies that global variables are available and that the StartApplication routine has been called.

Example  4- 13. Flow of Control When User Chooses Find When Hello World Is Open

HelloWorld's PilotMain(                       sysAppLaunchFlagNewStack AND
sysAppLaunchCmdNormalLaunch, params,          sysAppLaunchFlagNewGlobals AND
flags)                                        sysAppLaunchFlagUIApp
  HelloWorld's EventLoop
  SysHandleEvent (enter)
                                              after user taps Find
    Loop through all applications:
      HelloWorld's PilotMain(sysAppLaunchCmdFind, 
       parameters, sysAppLaunchFlagSubCall)
      PilotMain(sysAppLaunchCmdFind,parameters, 
       0)
  SysHandleEvent (exit)

Summary

Top Of Page

In this chapter, we have given you a description of how an application interacts with the system on a Palm device. We have also done a code walkthrough of a sample program that contains all the code components that are standard to all Palm applications. You have learned that the Palm application is an event driven system. The system's event queue feeds a constant flow of events to your application, and it is up to you to handle them. You have also seen the wide variety of instances under which your application may get called by the system and the resources available to you to deal with these instances. Last, but not least, we have discussed some of the more important elements in handling memory in a Palm application.

From all this information, you should now be well on your way to understanding this application architecture. In the following chapters, you will use this information to create a full-featured application.


Palm Programming: The Developer's Guide
Copyright © 1999, O'Rielly and Associates, Inc.
Published on the web by permission of O'Rielly and Associates, Inc. Contents modified for web display.

Previous PageTop Of PageTable Of ContentsIndexNext Page