Wednesday, May 2, 2007

Using the Palm OS Progress Manager

The Progress Manager was introduced with Palm OS 3.0. It provides application developers with a way of giving status feedback to users during long operations, and offers users the chance to abort or cancel the long operation. The Progress Manager is useful when an application has to perform either a sequence of operations - for example, connecting to a server - or to carry out a complex and time consuming algorithm. In either case the user may want to interrupt the operation before it is completed.

Using the Progress Manager has four stages: launching the dialog box, updating the dialog box's display to show the current status, listening for events in the progress dialog and finally removing the dialog box when the operation is complete. To demonstrate the use of the Progress Manager I have created a sample application, which simulates a slow operation. In the example a loop counting to 1000 is initiated when the user presses a 'Start' button. The application launches a progress dialog and updates the user as to how far the count has reached. When the count reaches 1000 the operation is complete, and the progress dialog box is removed. The code below shows the event handler for the 'Start' button. The call to launch the progress dialog box is PrgStartDialog. This call is made just before the slow operation commences. PrgStartDialog takes two parameters, a title for the dialog box and a pointer to a callback function.


switch
(eventP>data.ctlSelect.controlID)
{
case MainStartButton:
{
ProgressPtr prg;
Long i=0, cnt=0;
EventType event;

prg = PrgStartDialog("Counting to 1000...", callbackFunction);

//Do something complicated...


while (i<1000)>

{

i++;

cnt++;

StrIToA(message, i);

if (cnt==10)

{

PrgUpdateDialog(prg, 0, i, message, false);

cnt=0;

}

EvtGetEvent(&event, 0);

if (!PrgHandleEvent(prg, &event))

if (PrgUserCancel(prg))

break;

}

PrgStopDialog(prg, false);

break;

}

default:

break;

}

Once the complex operation is underway, we need to make periodic calls to the Progress Manager to update the state of the progress dialog. This provides an opportunity to give the user feedback on how far into the operation we are, and how much longer they might have to wait. This call is made with PrgUpdateDialog. The function takes a pointer to the progress dialog (which was returned by PrgStartDialog) and error flag, a stage indicator, a text message and a boolean flag to determine whether to redraw the dialog or not. The interesting parameters are the stage indicator and the text message. The stage indicator represents the progress that the application has made so far, and can be used in our callback function to give the user some meaningful feedback. In a sequential operation we might pass an indicator to say which step of the sequence we have reached. (In the sample application I pass the current count.) The text message parameter is also used to give feedback to the user. This parameter is usually used to pass dynamic text information to the progress dialog box. For example, if the application was connecting to a server we might use the message parameter to pass the phone number that we had dialled.
Because the progress dialog box has a 'Cancel' button, which the user can press to stop the operation, we need to look for events in the Progress Manager. We do this by taking control of the event loop during the processing of long operation. To get events we make an EvtGetEvent call to the Event Manager. This gives us the next event from the event queue. We immediately pass this event to the Progress Manager, with a call to PrgHandleEvent. The Progress Manager calls our callback function to get the information that it should display. It should be noted that PrgHandleEvent internally makes a call to the system event handler (SysHandleEvent), so any system events - like the user pressing the power key - will be handled correctly.
The callback function was established when we made the call to PrgStartDialog. It is called during the PrgHandleEvent handler to give the application an opportunity to set the messages that will be displayed on the progress dialog. In the sample code we use callbackFunction, and its definition is shown below. This structure is defined in the header file progress.h and is copied below for reference. It is a complex structure, but fortunately the callback function only has to use a couple of fields, the rest are for system use. The most significant task of callbackFunction is to fill the textP field. This is used to give the user an indication of the progress through the operation. A typical use would be to perform a look up on the value given in the stage field and to set an appropriate text message describing the stage. The text message we sent with the call to PrgUpdateDialog can also be used to supplement the display with some dynamic information. For example, if we were connecting to a server we might have a value that indicated we were at stage 1. We would set textP to 'Dialing:' and then concatenate the value in the message field to provide the number we were dialing. This would give the output: 'Dialing: 01119 998229'. When we change textP we need to set the textChanged flag to true to force the dialog box to redraw. Finally, callbackFunction can set a bitmap to display on the progress dialog, and again this can be determined by the stage that the operation is at.
typedef struct
{
Word stage; // current stage
CharPtr textP; // buffer to hold text to display
Word textLen; // length of text buffer
CharPtr message; // additional text for display
Err error; // current error
Word bitmapId; // resource ID of bitmap to display
Word canceled:1; // true if user has pressed the cancel button
Word showDetails:1; // true if user pressed down arrow for more details
Word textChanged:1; // if true then update text (defaults to true)
Word timedOut:1; // true if update caused by a timeout
ULong timeout; // timeout in ticks to force next update (for animation)
DWord barMaxValue;
DWord barCurValue;
CharPtr barMessage; // text for display below the progress bar.
Word barFlags; // reserved for future use.
} PrgCallbackData;
Boolean callbackFunction(PrgCallbackDataPtr cbP)
{
if (cbP->stage<250)>textP, "Starting: ");
StrCat(cbP->textP, cbP->message);
cbP->bitmapId = startBitmap;
}
else if (cbP->stage>750)
{
StrCopy(cbP->textP, "Finishing: ");
StrCat(cbP->textP, cbP->message);
cbP->bitmapId = endBitmap;
}
else
{
StrCopy(cbP->textP, "In the middle: ");
StrCat(cbP->textP, cbP->message);
cbP->bitmapId = middleBitmap;
}
cbP->textChanged = true;
return true;
}
In most cases PrgHandleEvent will return true, allowing us to continue processing our task. If it returns false we should call PrgUserCancel. This will return true if the user pressed the cancel button on the progress dialog box. When the user presses cancel we should perform any tidy up that is necessary and then abort the long operation. The last call we make to the ProgressManager is made when our operation is complete. This is a call to PrgStopDialog, and it removes the progress dialog box from the screen.
Source : http://www.developer.com/lang/other/article.php/615961

Thursday, March 29, 2007

How do i programatically launch another application from my application

You can programatically launch another application from within your application using the SysUIAppSwitch function by passing it the sysAppLaunchCmdNormalLaunch launch code and a NULL parameter block.
Here is a code example that shows how to launch the built-in MemoPad application:


static void MainGotoApplication()

{

LocalID theDBID;

UInt theCardNo;

DmSearchStateType theSearchState;

// Grab the id of the application we want to launch (the memo pad)

DmGetNextDatabaseByTypeCreator(true, &theSearchState, sysFileTApplication, sysFileCMemo, true, &amp;amp;theCardNo, &theDBID);

// Launch the new app.

SysUIAppSwitch(theCardNo, theDBID, sysAppLaunchCmdNormalLaunch, NULL);

}

Beaming Using the Palm OS Exchange Manager

As a final part of our "inter-application communication," let us discuss one more way of how to do it. As you may guess, I mean here the Exchange Manager API. This kind of communication differs from those ones we've spoke about in previous articles. When you hear 'beaming', yes—that's it. But, as you may discover, a request for exchange data is just one of the launch codes your application may receive (e.g. sysAppLaunchCmdExgReceiveData), so it may be useful to export data to other apps at the same device. So, let's go ahead and take a look at practical cases.


Case 1: Standard Beaming of a Whole Database or Specific Records:
The Palm OS's user interface doesn't allow beaming of an abitrary database, only prc-files, i.e. applications. So, it'd be nice for your program to offer such functionality. Fortunately, it becomes a pretty simple task with Exchange Manager. The normal working flow is as follows:
Define a variable of type ExgSocketType and initialize it to zeroes.
Assign appropriate values to description, target, and optionally to the type and name fields.
ExgSocketType is declared in ExgMgr.h as

typedef struct ExgSocketType {

UInt16 libraryRef; // identifies the Exg library in use

UInt32 socketRef; // used by Exg library to identify this connection

UInt32 target; // Creator ID of application this is sent to

UInt32 count; // # of objects in this connection (usually 1)

UInt32 length; // # total byte count for all objects being sent

UInt32 time; // last modified time of object

UInt32 appData; // application specific info

UInt32 goToCreator; // creator ID of app to launch

ExgGoToType goToParams; // If launchCreator then this

UInt16 localMode:1; // Exchange with local machine

UInt16 packetMode:1; // Use connectionless packet mode

UInt16 noGoTo:1; // Do not go to app (local mode only)

UInt16 noStatus:1; // Do not display status dialogs

UInt16 preview:1; // Preview in progress: don't

UInt16 reserved:11; // reserved system flags

Char *description; // text description of object

Char *type; // Mime type of object (optional)

Char *name; // name of object, generally a file name

} ExgSocketType;typedef ExgSocketType *ExgSocketPtr;

Note that the localMode field controls the operation mode, so you may want to set a 1 there to work locally.
To establish a connection, call ExgPut.
To send the whole database, call ExgDBWrite. An alternative way is to call ExgSend in a loop to send all desired data. Actually, these are the same methods because ExgDBWrite requires a callback function as a parameter, which uses ExgSend internally.
After all jobs are finished, call ExgDisconnect to close the connection.
The following code chunk illustrates all that was said above:

void BeamWholeDB(){
ExgSocketType exgSocket;
UInt16 cardNo;
LocalID dbID;
DmSearchStateType searchState;
Err err;

Char *appName = "TestDB";
err = DmGetNextDatabaseByTypeCreator(true, &searchState,'data', 'test', true,&amp;amp;cardNo, &dbID);
if (!err)
{
MemSet(&exgSocket,sizeof(exgSocket),0);
exgSocket.description = appName;
exgSocket.name = "TestDB.pdb";
err = ExgPut(&exgSocket);
if (err == 0)
{
ExgDBWrite(WriteProc, &exgSocket, appName, dbID,cardNo);
ExgDisconnect(&exgSocket,err);
}
}
}
As you can see, it's really not a big deal. Sure, we've left a lot of details behind the scene, such as sending multiple objects (refer to the count member of ExgSocketType), which application should be launched (if any) after receiving data, and so forth. Like any other Palm OS API, Exchange Manager API is rich, so you're welcome to investigate it more closely.
Okay, coming back to sending individual records, you may guess now that it differs from the previous example only in using ExgSend function in some loop instead of ExgDBWrite. Therefore, we'll consider the receiving part of flow; i.e. what the application should do when it is going to get some data via Exchange library.

A sequence of steps follows:
First, you should handle the sysAppLaunchCmdExgReceiveData launch code in PilotMain.
Call ExgAccept to accept the incoming request.
Call ExgReceive in a loop until it returns zero.
And finally, call ExgDisconnect to close the connection.

So, as we've just said:

// in PilotMainDmOpenRef g_dbP;

// our database...

if (cmd == sysAppLaunchCmdExgReceiveData){

DmOpenRef dbP;

if ((launchFlags & sysAppLaunchFlagSubCall) == sysAppLaunchFlagSubCall) {

dbP = g_dbP;

FrmSaveAllForms();

err = ReceiveData(dbP,(ExgSocketPtr)cmdBPB);

}

else

{

dbP = DmOpenDataByTypeCreator('data','test', dmModeReadWrite);

if (dbP) {

err = ReceiveData(dbP,(ExgSocketPtr)cmdBPB); DmClosedatabase(dbP);

}

}

}

Err ReceiveData(DmOpenRef dbP, ExgSocketPtr sockP){

Err err = 0;

UInt16 wIndex = -1;

err = ExgAccept(sockP);

if ( err == errNone ) {

char buffer[1024];

ULong lBytesRecv = 0;

while ((lBytesRecv = ExgReceive(sockP,buffer, sizeof(buffer)))

{

// process data ...

}

err = ExgDisconnect(sockP,err);

}

}
Now, a few words about the name member the of ExgSocketType structure. Starting from Palm OS 4.0, this field may contain a URL to identify the exchange library to connect with. Using a local exchange library may become handy if you want to debug your application locally or to send/request data to/from another application at the same device. On Palm OS 4.0 and later, if the URL starts from the '?' character, the user may be prompted to choose which exchange library to use if more than one library is registered for the selected scheme.
Case 2: Exchanging Data with the Local PIM
There are at least several situations when you might want to use Local Exchange Manager. A good example is exporting data to some built-in application (e.g. to MemoPad as the simpliest case). Even if the user has decided to use some third-party program instead of a standard one, you'll be sure that this program will correctly receive exported data. A small example looks like this:

void SendToLocalMemopad(){

ExgSocketType exgSocket;

Err err = 0;

Char *textBuf = "test";

UInt32 size = StrLen(textBuf) + 1;

MemSet(&exgSocket,sizeof(exgSocket),0);

exgSocket.description = "Test message";

exgSocket.name = "?_local:Sample.txt"; exgSocket.type = ".txt";

err = ExgPut(&exgSocket);

if (err == 0) {

ExgSend(&exgSocket,textBuf,size,&amp;amp;amp;err);

ExgDisconnect(&exgSocket,err);

}

}
Well, after we've discussed the basic terms of Exchange Manager stuff, you are ready to go ahead and study additional details. Here I don't intend to copy the Palm OS Reference guide, so it'll be our homework. In real applications, you may want to use much more than we've covered here. Games, responding to 'Get' requests from a remote application, two-ways communications etc., are just instant examples. Good luck!
Communication with Devices Running an OS other than Palm OSOne other task that becomes relevant today is to exchange data between different devices with different OSes. Here, I'm speaking mainly about a PalmOS/Symbian <=> WinCE exchange. The main point is that Exchange manager uses the OBEX protocol to do its job. Early Pocket PC 2000 handhelds did not utilize OBEX at all. If you are facing such a case, you need to implement some kind of OBEX simulation on the Pocket PC device (refer to the OBEX specification for exact details). Fortunately, on Pocket PC 2002 and later, OBEX is supported, so data may be freely sent back and forth either from the OS GUI or programmatically. The only thing you might need to take care of in your Palm OS application is to register an application to handle desired types of data, say '.doc', by calling ExgRegisterDatatype. That's all, folks.

Steps for Configuring Java for Palm Os Application Development

1. Install a 'regular' j2se sdk (http://java.sun.com/j2se/1.5.0/download.jsp)
2. Install 'J2ME wireless toolkit' (WTK) from sun (
http://java.sun.com/products/j2mewtoolkit/download-2_2.html)
3. Install 'Websphere Everyplace Micro Edition for Palm' (WEME) (
https://www.developerpavilion.com/palmos/page.asp?page_id=365&tool_id=104) and copy the content of the directory Tools/lib installed to the lib/ subdirectory of your jdk (installed at step 1)
4. Install 'Palm OS Garnet 5.4 Simulator' from PalmSource (
https://www.developerpavilion.com/palmos/page.asp?page_id=365&tool_id=8)
Copy J9JavaVMMidp20.dll from Java_Tech_for_Garnet_WEME57\JVM\Simulator installed at step 3 in the same folder than PalmSim_54_rel.exe installed during previous step
Create a folder 'Autoload' in the same folder than PalmSim_54_rel.exe and copy inside it all the prc files from Java_Tech_for_Garnet_WEME57\JVM\Simulator (not all of them are needed actually but anyway...)
5. Install Eclipse (
http://eclipse.org/)
6. Install EclipseME (
http://eclipseme.org/) within eclipse and configuring the location of WTK (see EclipseME documentation : http://eclipseme.org/docs/installation.html)
Run Eclipse and create a new project of type 'J2ME Midlet Suite' dans 'J2ME' (ctrl-n) named HelloMIDlet
7. Create a new class within the project (right mouse button on HelloWorld project in the 'Package Explorer' view then 'New' and 'Class') named HelloMIDlet
Replace the generated content of the file HelloMIDlet.java with the Sun tutorial :
http://developers.sun.com/techtopics/mobility/midp/articles/wtoolkit/src/HelloMIDlet.java
Edit HelloWorld.jad by double-clicking on it, select 'Midelts' tab, click 'Add' then 'New Class' then 'New Midlet' then select HelloWorld and save (ctrl-s)
8. Create jar file (right mouse button on project, J2ME menu then 'create package') which will be created in the 'deployed' project subdirectory
9. Run jartoprc_w.exe from the Tools/bin of WEME installed at step 3, browse to select the jad file (not jar) copied by eclipseME in the 'deployed' directory and click 'generate PRC'
10. Run the simulator installed at step 4 (PalmSim_54_rel.exe)
11. Install the prc by drag & dropping it on the simulator window
12. Click on the icon to run the application within the simulator
13. For a real device you need another JVM installed :
http://www.palmone.com/us/support/jvm
14. They are some ways to automate the build process using ant tasks also, basically it uses antenna :
http://antenna.sourceforge.net/

Sending and Receiving Notifications

Notifications are a feature of Palm OS 3.5 and higher; they are sent by the system to notify interested applications that some event has happened or is happening. Applications can send notification as well. For information on sending notifications, see the section Sending Notifications below.

Receiving Notifications

For efficiency, your application registers only for notifications that you want to receive. You can find a list of available notifications in Palm OS Programmer's API Reference or in the header file NotifyMgr.h.

Different applications will register for notifications under different circumstances:

  • Sometimes you only want notifications when your application is running. For example, you might want to know that an expansion card has been inserted or removed so that you can update your display.
  • Sometimes you always want to be notified even when the user isn't running your application. In this case you would register when you get the sysAppLaunchCmdSystemReset launch code, and might never un-register. (If the user deletes your application, you are automatically unregistered from all notifications.)

The following example will show how to receive notifications when the system wakes up and goes to sleep (that is, when the handheld is turned on and off). These notifications could be used to track how a handheld is used or to monitor battery consumption, for example.

To register and use notifications, you need to do the following:

  • In your PilotMain function, add calls to the functions RegisterForNotifications and HandleNotification.
  • Create the RegisterForNotifications function.
  • Create the HandleNotification function.
  • Test the notification code.

Add Code to PilotMain

Add the following code to your PilotMain function's launch code switch statement, in the same place where you determine if the application was launched with a sysAppLaunchCmdNormalLaunch launch code:

   case sysAppLaunchCmdSystemReset:
   case sysAppLaunchCmdSyncNotify:  // sent when our app is installed
      RegisterForNotifications();
      break;
 
   case sysAppLaunchCmdNotify:
      HandleNotification((SysNotifyParamType *)cmdPBP);
      break;

With this code added to PilotMain, the RegisterForNotifications function will be called upon every system reset, as well as when your application has been installed via a HotSync process (but not necessarily when you've been installed by a debugger or when dragged into the Palm OS Emulator).

Be very careful when registering for notifications on system resets. If you have a bug in how you handle resets (like accessing global variables), you may crash the Palm OS and force another reset, creating an infinite loop. Many users won't know to do a no-notify reset so that they can delete your application.

Create the RegisterForNotifications function

Create the RegisterForNotifications function:

   static void RegisterForNotifications()
   {
      UInt16 cardNo;
      LocalID dbID;
 
      if (0 != SysCurAppDatabase(&cardNo, &dbID))
         return; // shouldn't ever fail, but just in case.
 
      // Tell the system we want to know when 
      // the device sleeps and wakes.
      SysNotifyRegister(cardNo, dbID, sysNotifySleepNotifyEvent, NULL,
        sysNotifyNormalPriority, 0);
      SysNotifyRegister(cardNo, dbID, sysNotifyLateWakeupEvent, NULL,
        sysNotifyNormalPriority, 0);
   }

With this RegisterForNotification function added, the HandleNotification function will be called upon every sleep and wake. Note that the fourth parameter to the SysNotifyRegister function allows you to pass a pointer to a callback function that will handle the notification. Passing NULL as the pointer to the callback function causes the sysAppLaunchCmdNotify launch code to be sent to your application (we had you add code to your PilotMain function to handle this above). In general, it is simpler to use launch codes, but if you know you are the active application or are writing a shared library you may want to use a callback function.

Create the HandleNotification Function

What you do in the HandleNotification function is really up to you, but for demonstration purposes we'll keep track of how long the device was asleep (for the last time it was asleep):

   #define kWhenWeWentToSleepFtr 1  // local definition for this routine
   #define kLastSleepLengthFtr   2  // read by your app somewhere else
 
   static void HandleNotification(SysNotifyParamType *np)
   {
      UInt32 timeAsleep;
 
      if (np->notifyType == sysNotifySleepNotifyEvent)
         FtrSet(appFileCreator, kWhenWeWentToSleepFtr, TimGetSeconds());
      else if (np->notifyType == sysNotifyLateWakeupEvent) {
         FtrGet(appFileCreator, kWhenWeWentToSleepFtr, &timeAsleep);
         FtrSet(appFileCreator, kLastSleepLengthFtr, TimGetSeconds()
               - timeAsleep);
      }
   }

Test the Notification Code

Now to prove the notification code works, add the following code to your main form's event handler. This code will display how long the system was asleep, reading the feature that was set in the example HandleNotification function.

   case frmOpenEvent: {
      char buffer[80];
      UInt32 sleepTime;
 
      frmP = FrmGetActiveForm();
      MainFormInit(frmP);
      FrmDrawForm(frmP);
 
      // Crudely draw on top of the form's contents.  (Just for a demo!)
      FtrGet(appFileCreator, kLastSleepLengthFtr, &sleepTime);
      StrPrintF(buffer, "Last sleep time: %ld seconds.", sleepTime);
      WinDrawChars(buffer, StrLen(buffer), 0, 16);
 
      handled = true;
      break;
   }
 

Sending Notifications

It is easy to broadcast a notification to inform other applications (or your own application) that something noteworthy has occurred. If no applications have registered for the notification that you are sending, your call to the SysNotifyBroadcast function will return quickly, though it does obviously take a little time for the system to make that determination.

  • You can send a notification immediately by using SysNotifyBroadcast. In this case, you'll wait until every recipient processes the notification.
  • Or else you can send a deferred notification by using SysNotifyBroadcastDeferred. In this case, the notification will be delivered later (currently, "later" means the next time EvtGetEvent is called).

The following code shows how to send a notification.

   // must be a registered creator ID; use your
    // application's or else register a new one
    // at http://www.palmos.com/dev/creatorid/
        #define myInterestingEvent 'MyEv'
 
 
   SysNotifyParamType notify;
 
   notify.notifyType     = myInterestingEvent;
   // say who is sending the event (i.e. you.)
   notify.broadcaster    = appFileCreator;
   notify.handled        = false;
   // or whatever additional information you need to send
   notify.notifyDetailsP = NULL;
 
        // or possibly use SysNotifyBroadcastDeferred
SysNotifyBroadcast(¬ify);

Tips from the VFS files

The good thing about the VFS (Virtual File System) APIs in Palm OS are that they work just like a traditional desktop-style file system, so they are usually fast to learn and add to your application's code. The bad thing is that many developers don't realize that the best practices for using the VFS APIs differ both from Palm OS database approach, as well as from traditional file system approaches. You can certainly use them under the database or desktop models, but with a little reflection you might well improve your application's UI, usability and/or speed.

One of the most obvious differences is that reading from external storage is considerably slower than reading from internal RAM, which is what you do when using Palm OS Databases. If your application was first created under the assumption that reading stored data was effectively instantaneous, you'll probably have already noticed that random access through one or more large VFS-resident files results in a, shall we say, suboptimal user experience. Therefore, consider going directly against your well-honed memory conservation instincts and try caching data in RAM - perhaps even large quantities of data, for extended periods of time. Nearly all devices that have VFS slots also have a megabyte or more of dynamic heap, so unless you're already putting that space to good use, try caching it there. Alternatively investigate reading it in to the storage heap: most users have lots of space left there for you to use either Feature Pointers or else a temporary database. If your files are normal Palm OS databases and they're only on the card for convenience purposes, consider caching the entire database in RAM while you're running ? but make sure you delete the cache both when you quit as well as after a reset in case you crash (no offense implied!) or the user hits the reset button for some reason.

Another big difference between in-RAM databases and files is that simply finding your data takes more time. DmGetNextDatabaseByTypeCreator is really fast, but doing the simple equivalent with a file (assuming you haven't a unique file extension you're looking for) requires that you open each file and read the first 64 bytes. If you're looking within the /palm/launcher directory, your performance will drop the more applications the user installs on their card. This is a good time to remember that the user normally cares about perceived performance, not actual performance. Therefore if you really do need to scan every file in /palm/launcher to find your data, don't do that when the application launches but instead generate a list of files and then check them one by one during nilEvents. For example if you're showing thumbnails of pictures, get a directory listing and draw the first one's thumbnail immediately, but then return to your main event loop and have it call EvtGetEvent with a very short timeout. When you get a nilEvent, read the next file and draw its thumbnail, and repeat. (When you're done, make sure to change the timeout to be unlimited to save battery.) The total time spent will be about the same, but the user will not have to wait for it all to finish. Even better, if you don't expect the data to change often, cache the thumbnails along with their filenames and modification dates so if it looks like nothing has changed, then you can draw that first display before you open up a single file. Of course you'll need to adjust the rest of your application to know about these tricks, and ensure the current data has been read in at some point, but the speedups can be truly dramatic.

When designing a system to use VFS, you need to decide whether to keep your data in Palm OS database formats (i.e. PRC or PDB files) or in some other traditional file format (e.g. a .TXT file). Your users will probably appreciate being able to install large data files either on a card or internally, which is a vote for using databases in both places. There are utility VFS routines to read the contents of a record or resource from a card-resident PDB, but writing is problematic in such a condition because changing the size of a record or resource can mean that nearly all the file would have to be rewritten. (Consider reading the entire database into RAM and then saving the whole thing again with VFSExportDatabaseToFile, or else parsing the file yourself if performance requires it. The PDB/PRC format is well documented.) On the other hand, you might not be in control of the data files, if you're doing a photo viewer as described above you have to use the same format as the camera did. In that case your task is determining whether to also support saving the photos in RAM or not, in a different container.

If you're coming from a traditional desktop file system mindset, the biggest differences between that and VFS are in the user experience, but there are a few programming aspects too. The user experience when using VFS will ideally be indistinguishable from using internal RAM, and this is usually achievable with care and by using some of the aforementioned ideas. Entering filenames or using file browsers is natural to techie types like us, and might even be requested by users who don't realize there is a better way, but many superior interfaces recognize that files are a means to an end: the focus should be on working with the user's data, not on managing filenames and directories.

On a desktop system you also don't need to worry much about the user pulling out the hard drive that you're in the middle of using, or installing new ones while you're running. But on handheld devices, that needs to be easy and natural. While you're running, you should register for the sysNotifyVolumeMountedEvent and sysNotifyVolumeUnmountedEvent notifications and smoothly show the additional data or gracefully stop using any open files. Telling the user to put back the card is an option to be avoided if possible, since they might not notice your alert until much later, and then you've got to deal with the possibility of the card (or file) having been modified in some other system in the interim.

Also consider locality of reference when reading files, and the number of bytes you read at a time. Palm OS devices may or may not have an automatic low-level cache for the card, so reading a byte at a time might incur considerable overhead. It may well be better to read say 32k of data into your own cache: try some timing tests for your own scenarios.

Palm OS Databases are fast to search and read, and their characteristics are well suited for handheld use and UI. Files can be just as good if you take the time to provide a great user experience... as well as cache appropriately and work some idle-time sleight of hand.

Monday, February 19, 2007

Segmenting an Application

A Palm OS application is a collection of resources packed together into a resource database. Because of limitations in the current HotSync architecture, no single resource can be larger than roughly 64K. If a resource is larger than 64K, your application fails to load into either the Palm OS Emulator or onto a Palm OS handheld via HotSync Manager.
Each application includes a resource of type 'CODE'. This is the resource that stores your application's compiled code and is the most likely resource to exceed the 64K resource size limit. When this happens, you need to segment your application. The process of segmenting an application breaks a large CODE resource into multiple, smaller CODE resources. An application that has been broken in to multiple CODE resources is called a multi-segment application.
There are two ways to create a multi-segment application. If you are using Metrowerks CodeWarrior and creating an application from scratch, you can select "Palm OS Multi-Segment App" from the "New Project" window. This stationery includes the necessary library and linker settings to build a multi-segment application.
If you are converting an existing single-segment application to a multi-segment application, you need to replace the "StartupCode.lib" file with a "MSL Runtime Palm OS (xx).lib". Additionally, you need to disable the "Link Single Segment" flag in the "68K Linker" settings of your project's settings. If you have enabled the 4-byte ints option in the "68K Processor" preferences in your project, then you need to use the "MSL Runtime Palm OS (4I).lib"; otherwise use the "MSL Runtime Palm OS (2I).lib".
To create a new segment, click on the "Segment" tab in your project window. You can then create a new segment by selecting "Create New Segment" from the "Project" menu. Name your segment as appropriate and click "OK". Once you have multiple segments you can simply drag files from one segment to another as appropriate. Note that all segmentation takes place under the "Segment" tab and not the "Files" tab.
There are a few important points to keep in mind. The first segment MUST contain the "MSL Runtime Palm OS (xx).lib", your application's PilotMain() function, and any code that is executed when PilotMain() receives a launch command other than sysAppLaunchCmdNormalLaunch. This stipulation is is necessary because A5-relative data is not relocated when the application is called with any other launch code, causing all global variable addresses to be incorrect.
All other files in your application can be placed in any segment you wish.
If you intend to use functions defined in another segment, they must be declared as "extern" in the segment you are calling from so that the compiler knows that function exists in another segment.
Note to Codewarrior users The best way to determine which files can go into segments other than segment 1 is to:
comment out the code in PilotMain() that handles the sysAppLaunchCmdNormalLaunch command
select the "Generate Link Map" option from the "68K Linker" preferences in your project
rebuildAny file that shows up in the link map on a line beginning with "Code:" must be in the first segment. The link map file is created in the same folder as your project and ends in ".map". Double-click its icon to view the link map. The map includes some functions that the OS usees, you can ignore these functions (most begin and end with two underscore characters ie: __)