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.
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.
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 = 0; if (launchCode == sysAppLaunchCmdNormalLaunch) { if ((err = StartApplication()) == 0) { EventLoop(); StopApplication(); } } return err; }
Let’s start at the beginning with 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 Section 4.2.3.11.
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.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 = 0; 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.
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.
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.
Normally in StopApplication we handle all the standard closing operations, such as closing our database, saving the current state in preferences, and so on.
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); }
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.
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 Section 4.3 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).
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.
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.
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
-
penMoveEvent
Occurs when the user moves the stylus on the screen.
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.
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 Section 5.2, you can at least see that our routine handles the request to load our sole form.
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 #endif 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 }
Others want a more radical solution. They want to be able to use all functions as callbacks without any special declaration.
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.
In this simple application, we have all the major elements of any Palm application. In review, these are:
A set of necessary include files
A startup routine called StartApplication, which handles all our initial setup
A PilotMain routine, which starts an event loop to handle events passed to it by the system
An event loop, which continually hands events to a series of four managing routines—SysHandleEvent, MenuHandleEvent, ApplicationHandleEvent, and FrmDispatchEvent
A set of routines to handle our form-specific functionality
A closing routine called StopApplication, which handles the proper closing of our application
Get Palm Programming: The Developer's Guide now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.