Palm OS Programmer's FAQ
Section 6, Articles: PRC-Tools Tutorial

PRC-Tools Tutorial

by Andrew Howlett

Last updated March 2001
Copyright © 1997 by Andrew Howlett
portions Copyright © 1997 by Theodore Ts'o
portions Copyright © 1994 by Palm Computing
portions Copyright © 1997 by R. Critchlow and B. Winton

This tutorial is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This tutorial is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

A copy of the GNU General Public License is included at the end of this document. If the license is missing, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

This tutorial records what I figured out as I taught myself to program my Palm using GCC and PilRC. Maybe it can help other recreational programmers. Many thanks to the experts who built free tools for Palm OS Programming. If you find errors in this tutorial or would like to contribute improvements, please let me know. For document control purposes, please do not distribute modified copies of the tutorial.

Some of the information in this tutorial is drawn from PilRC documentation, Palm Computing documentation, and Palm OS header files. That information will be identified when presented and is copyright of the contributor. "Palm Computing" and "Palm OS" are registered trademarks of Palm Computing. Motorola and DragonBall are registered trademarks of Motorola. Adobe and Acrobat are registered trademarks of Adobe.

You can download this tutorial and all the supporting files, to read it offline and to play with the code. Or, you can download just the Text2Hex example code.



Table of Contents

1.0 INTRODUCTION

2.0 PALMOS MEMORY AND DATABASES

3.0 PILOTMAIN

4.0 FORMS

5.0 OTHER RESOURCES

6.0 SIMPLE EXAMPLE

GNU GENERAL PUBLIC LICENSE



1.0 INTRODUCTION

1.1 Aim

This tutorial will explain how to build simple Palm OS applications using free software tools. This tutorial defines a simple application as an application with one form, one or two alert dialogs, one menu, and any number of buttons, fields, bitmaps, and labels. This tutorial supplements the documentation listed in section 1.5.

Later editions of this tutorial may explain how to build more sophisticated Palm OS applications. More sophisticated applications use tables, resource databases, data databases, popup lists, checkboxes, multiple forms, and other techniques.

This tutorial is written for newbies. Readers should be familiar with ANSI C.

1.2 But I Don't Know ANSI C

If you don't know ANSI C but still want to program your Palm then you have two options:

  1. Learn ANSI C. Check the web, especially Yahoo's list of C and C++ courses. Or, check used book stores for cheap programming books. I picked up an old copy of Mastering Borland C++ version 3.1 for $5.
  2. Use one of the other supported languages: Java, Basic, etc. As this tutorial only covers programming in ANSI C, you'll have to find another tutorial if you choose this route.
1.3 I Know ANSI C

Be careful! Palm OS itself does not support the Standard C Library. PRC-Tools adds partial support for the Standard C Library, but many facilities are still missing.

1.4 Limitations

This manual is limited to Palm OS 3.5. This tutorial was developed on a GNU-Linux system, however it should be equally applicable to development with the GNU Palm under a MS Windows environment.

1.5 Documentation

The great thing about programming Palm OS using free tools is that there is lots of free documentation. You must read the first three references. The rest are highly recommended. You will require Adobe Acrobat to read any files with a filename suffix of "pdf". Acrobat Reader can be downloaded from Adobe's website.

  1. Palm OS Reference. This monolithic document is available from Palm Computing's developer resources webpage, http://www.palmos.com/dev/. The document is in one large pdf file, which makes for easy searching. This document describes the Palm OS application programmer interface (API) in detail. When this tutorial mentions an API call you should look it up in the Palm OS Reference.
  2. Palm OS Companion. The Palm OS Programmer's Companion is a type of "howto" document describing why the API works the way it does. The Palm_OS_Reference.pdf document contains many links to the companion.pdf document. You need this file. Download it from the Palm Computing tech site.
  3. Palm OS Programmer's FAQ. Read the FAQ. In case you didn't hear that,



    Just do it.

  4. Using PRC-Tools. You should read the prc-tools manual, especially if you decide to create complicated projects using multiple source files.

  5. GNU documentation. Documentation for GCC, MAKE, GDB, etc. This covers the portable aspects of these tools; the previous item covers the Palm OS-specific aspects.
  6. PilRC User Manual.

  7. Palm OS Knowledge Base. This is Palm Computing's searchable database of many useful articles.
  8. News.falch.net. The pilot.programmer.gcc newsgroup on the news.falch.net news server contains many useful prc-tools related articles.
  9. Palm-dev archive. Palm Computing has it's own news server for Palm OS topics. To post to this news service you must be a registered Palm OS developer. However, anyone can read and search the palm-dev archive.

If you ask a question that is already in one of the above mentioned FREE documents, you probably won't get an answer.

1.6 Baseline Configuration

This tutorial and the sample application were developed using the following tools:

Tool Version
PRC-Tools Prc-tools-2.0.90
binutils-2.9.1
gcc-2.95.2
gdb-4.18
Palm OS SDK SDK 3.5
Resource Compiler PilRC v2.7
Emulator POSE 3.0a8
Documentation Palm_OS_Reference.pdf, SDK 3.5
Palm_OS_Companion.pdf, SDK 3.5
Adobe Acrobat Reader
pilrc v2.7 manual
prc-tools v2.0 manual

Make sure that you have the right version of PilRC. Sometimes the windows palm-GCC package installs an old version of PilRC. Get the latest version of PilRC from www.ardiri.com.

1.7 Installing the Software

There already exists excellent instructions on how to install the prc-tools software. If you plan to install prc-tools in the MS Windows environment, you should read the instructions written by Aaron Ardiri. If you plan to install on a unix or GNU/linux system, then follow the directions at http://lisas.de/~david/palmos/.


2.0 PALM OS MEMORY AND DATABASES

This tutorial will start by explaining Palm OS databases. To new Palm OS programmers, this may seem a little strange. Most C programming books start by talking about the user interface and coding, then explain how to build databases. But the Palm OS system is a little bit different than your desktop PC operating system. So throw away your preconceptions regarding hard drives and RAM and learn how Palm OS stores its applications.

2.1 Dynamic and Storage RAM

The Motorola DragonBall CPU can address up to 4GB of memory. No Palm OS device is currently equipped with 4GB, so most of this address space is not implemented.

Your Palm's RAM is divided into two areas: dynamic RAM and storage RAM. Dynamic RAM is the memory available to an application as it runs, similar to the RAM installed in your desktop computer. Dynamic RAM is used for the current application's global variables, local variables, stack space, etc. Depending on the OS vesion and the amount of memory in the device, the amount of available dynamic RAM varies from about 12 KB to a bit under 200 KB. (See the Memory article in Palm's knowledge base for details.) To be safe, you should only expect to be able to access about 32 KB.

32 KB isn't very much. Consider a problem I had porting a game to Palm OS: the game board is composed of a rectilinear grid of tiles, like a checkerboard but 100 tiles across and 80 tiles up and down. Each tile is described by a 50 byte data structure. Normally this board would be defined by a statement like:

struct tile_type game_board[100][80];

This global definition requires 100 x 80 x 50 = 400,000 bytes of dynamic memory. Oops. Not enough dynamic RAM. This means that I had to implement the gameboard in storage RAM as a database.

Storage RAM is the RAM used to store applications and their data, similar to the hard drive in your desktop computer. This analogy is handy but remember that the comparison isn't identical. For instance, when you launch a desktop application on your PC, the code is transferred from hard disk to dynamic RAM. But the Palm OS device works differently. Palm OS doesn't copy the application code to dynamic RAM, it just moves the program counter to point at the code where it sits in storage memory.

2.2 Resource vs. Normal Databases

Everything in storage RAM is formatted using one of two Palm OS database formats: "normal" database format and resource database format.

A normal database consists of a database header and from zero to dmMaxRecordIndex records of variable length. Each record is a chunk of memory, and SDK 3.5 has a limit of 65,536 bytes in a chunk, so a record has a maximum length of a little less than 65,536 bytes. (You have to subtract a few bytes for the record header). A normal database is very similar to a variable record length random access file which might be implemented on a desktop computer, except for a few minor details. One important detail is that you can get a pointer to the record data and use it like you would any other memory pointer. For instance, if your database contains a 50 byte record used to store addresses, you don't have to transfer the address information to and from dynamic RAM. You can get a pointer directly to the address data in storage memory and use that pointer in API calls such as WinDrawChars. This technique conserves power and dynamic memory.

Resource databases are similar to normal databases except that that the dmHdrAttrResDB bit is set in the attributes field of the database header, which means that each record of the database stores a Palm OS resource. A list of Palm OS resources is contained in Palm_OS_Reference.pdf. One example of a resource database is a PRC file. An application consists of one or more executable code resources and many user interface (UI) resources bundled into a resource database. You can actually open other applications as resource databases and use their icons or bitmaps. I once used a resource database to store a catalog of 536 bitmap icons.

2.3 Type and CreatorID

Palm OS identifies different databases by various database header fields, the most important fields being "type" and "creatorID". CreatorID is sometimes misnamed application ID. Type and creatorID are 32 bit values. In C they can be stored as unsigned long integers, but they are usually represented by four ASCII characters (you might find some applications using hexadecimal or decimal representation for #DEFINE instructions). For instance, Palm OS applications are database of type 'appl' (1634758764 or 0x6170706c). If you want to be sneaky, you could create an application using a type other than 'appl', then it won't show up in the "Applications" browser. Of course, it would be difficult to launch an application that doesn't show up in the Applications browser (but not impossible!). There exists a very interesting Palm OS application named Insider that will list the databases installed on your Palm OS device and let you examine their database header fields.

Some other types are commonly used. The built-in applications use 'DATA' type for their data databases, such as your addresses and appointments. The 'HACK' type identifies hackmaster extensions. A good rule of thumb is to use 'Rsrc' type to identify resource databases and 'Data' type to identify data databases. Of course, type and creatorID are case sensitive because 'A' is ASCII 0x40 whereas 'a' is ASCII 0x60. All lower case creatorIDs are reserved for use by Palm Computing, so your creatorID must have at least one upper case letter.

Because every application must have a unique type/creatorID combination, and since all applications have type 'appl', your application's creatorID must be different from all other creatorIDs in the Palm OS universe. If you load an application into your Palm device which has the same creatorID as an application that is already in storage memory, then the pre-existing application will be erased. To avoid creatorID conflicts, Palm Computing has a registration process for creatorIDs at http://www.palmos.com/dev/tech/palmos/creatorid/. There is no cost to register an ID, but you have to provide some professional information. If you're just fooling around, you don't need to register your creatorID, but if you plan to distribute your application to other Palm OS users, then you must register the creatorID with Palm Computing.

2.4 Simple Data Types

In ANSI C, a short integer is at least 16 bits wide, a long integer is at least 32 bits wide, and a plain "int" is ambiguously defined as at least as big as a short integer and less than or equal to the size of a long integer.

For Palm OS programming, it is very important to know the exact sizes of the storage elements. This is partly because you need to pay closer attention to your storage requirements on a Palm than when programming for a larger computer, and partly because the Palm database mechanism is based on raw data records, so you must use fixed-size data types: a future Palm OS device might have a different size for "int", but "UInt16" will always be 16 bits. Therefore Palm OS SDK 3.5 defines the following, unambiguous simple data types:

Data type name Description
Int8 Signed 8-bit value
UInt8 Unsigned 8-bit value
Int16 Signed 16-bit value
UInt16 Unsigned 16-bit value
Int32 Signed 32-bit value
UInt32 Unsigned 32-bit value
MemHandle A handle to a memory chunk
MemPtr A pointer to memory
FlpFloat 32-bit floating point value
FlpDouble 64-bit floating point value
Boolean Takes values 0 and 1


3.0 PILOTMAIN

3.1 Introduction

The first Palm OS device was called a "Pilot". Thus the entry point for a Palm OS application is called "PilotMain". PilotMain returns zero if no errors occur, otherwise it returns the error code. Its prototype is:

                UInt32 PilotMain (UInt16 launchCode, MemPtr cmdPBP, UInt16 launchFlags)

There are four parts to a typical PilotMain:

  1. Decide what launch code was given
  2. If it's a normal launch code, start application
  3. Event loop
  4. Stop application

Below is a generic PilotMain procedure. The start application, event loop, and stop application code is modularized in separate procedures. This chapter will explain the launch code test and each procedure.

                UInt32 PilotMain (UInt16 launchCode, MemPtr cmdPBP, UInt16 launchFlags)
                {
                  if (launchCode == sysAppLaunchCmdNormalLaunch) // 1. Launch code test
                  {
                    UInt16 error = StartApplication(); // 2. Application start code
                    if (error) 
                    {
                      return error;
                    }

                    EventLoop(); // 3. Event loop

                    StopApplication(); // 4. Application stop code
                  }

                  return 0;
                }
3.2 Launch Code Test

There are several reasons why Palm OS might launch your application. The most obvious reason is that the user tapped on your application's icon in the Launcher. But there are other reasons to start your application: for instance, when the user taps on the silk-screen "Find" button, Palm OS starts each application so that the application can execute its own find algorithm. Another example happens after the user synchronizes the Palm OS device: Palm OS starts each application so that the application can examine any new data in its database(s) and take any necessary action (e.g. set alarms). So, when Palm OS launches your application, the first argument it passes is a 16-bit unsigned integer describing why the system launched your application. These UInt16 values are called SysAppLaunch commands. You can find a list of all the commands in the SDK file include/Core/System/SystemMgr.h.

Your PilotMain procedure must check which command Palm OS issued to your application. If you fail to check which command was issued YOU WILL GET ERRORS. A common newbie mistake is to forget to test the launch code, then post dumb questions on the newsgroup asking why their code doesn't work. Simple applications only respond to the normal launch command, so you should test for sysAppLaunchNormalLaunch then PilotMain should execute your application code within the if block.

If you look up sysAppLaunchCmdNormalLaunch in the SDK, you'll see that it's defined as 0. Some programmers therefore use the test expression if (!launchCode) as a shortcut to test for a normal launch. I recommend you use the full expression since the full notation is easier to understand: I've seen at least one post on pilot.programmer.gcc from someone who was confused by the (!launchCode) notation. Besides, if you turn on the compiler's optimization, the two notations should compile to the same machine code anyway, so (!launchCode) really doesn't save you anything.

Only declarations and the final return statement should be outside of the command test. PRC-Tools users have reported strange behaviour, such as the Palm device hanging during hotsync, when even innocent looking code is added before the command test.

3.3 Start Application

The application start code typically has three functions: open the application's data database (if any), load system preferences (if any), and set the first form.

                static UInt16 StartApplication(void)
                {
                  UInt16 error;

                  error = OpenDatabase();
                  if (error) return error;

                  FrmGotoForm(formID_StockMarket);
                }

If the application uses a database, then the application should open it before doing anything else. I use a boolean procedure called OpenDatabase to open my database and create the database if no database exists. Here's an example of such a procedure:

                static Boolean OpenDatabase(void)
                {
                  UInt16   index = 0;
                  MemHand  RecHandle;
                  MemPtr   RecPointer;

                  StockMarketDB = DmOpenDatabaseByTypeCreator(StockMarketDBType,
                        StockMarketAppID, dmModeReadWrite);

                  // if StockMarketDB doesn't exist, create it
                  // and create a zero record with new game state information

                  if (!StockMarketDB) {
                    if (DmCreateDatabase(0, StockMarketDBName, StockMarketAppID,
                        StockMarketDBType, false))
                      return 1;
                    StockMarketDB = DmOpenDatabaseByTypeCreator(StockMarketDBType,
                            StockMarketAppID, dmModeReadWrite);
                    RecHandle = DmNewRecord(StockMarketDB, &index, 42);
                    RecPointer = MemHandleLock(RecHandle);
                    DmWrite(RecPointer, 0, &SM_Values, 42);
                    MemPtrUnlock(RecPointer); 
                    DmReleaseRecord(StockMarketDB, index, true); 
                  }

                  // load game state information

                  RecHandle = DmQueryRecord(StockMarketDB, index);
                  RecPointer = MemHandleLock(RecHandle);
                  MemMove(&SM_Values, RecPointer, 42); 
                  MemPtrUnlock(RecPointer); 

                  return 0;
                }

What does this code do? First it tries to open the database which has type StockMarketDBType and CreatorID StockMarketAppID in read/write mode. If the database doesn't exist, it creates the database and creates record zero of the database. Now that we know a database exists (because if it didn't, we just created it!) we can copy record zero of the database to a data structure.

Another thing you may want to do in your OpenDatabase procedure, which isn't shown in the example above, is check the version of the database. When you hotsync a new version of an application to your handheld, the previous version of the application is erased. But although hotsync erases the previous "appl" type, the previous "Data" type data database is still there. In many cases using the old version of the data database with the new version of the application causes errors. This is why new versions of Palm software sometimes come with a warning to delete previous version before installing. When you delete an application, every database with that creatorID is deleted, even if the database has a different type. More sophisticated applications will check the version of their data database when they run. If the data database is an old version then a sophisticated application will either delete the old one and create a new one, or upgrade the old data database to the new format.

System preferences are typically used for restoring the state of the application to whatever it was when the user last closed the application. For instance, in a chess game the board configuration might be stored in system preferences. In the memopad application system preferences records which category was last open and which memo was open, if a memo was open. There's no defined limit on the size of your system preferences record -- I would suspect that the limit is a little less than 64k bytes, same as most memory chunks. However, I don't use the system preferences API calls anymore, so I won't discuss syspref in this article. Instead, I will mention that I use record zero of my data database in the same way that I used to use the system preferences API. I usually load the state information in my OpenDatabase procedure, as can be seen in the example code above.

After opening and checking the database, the next thing to do is set the form. Although there are some optimizations which can be applied to applications which use only one form, I suggest that you use the generalized method, the API call FrmGotoForm as seen in the last line of the StartApplication example.

3.4 The Event Loop

Palm OS applications are event driven. This means that the user, the operating system, and the application generate operating system events which are placed in the application's event queue. You can find a full list of events in the SDK file include/Core/UI/Event.h.

One at a time, the application takes events from the event queue and handles them, in first-in-first-out order. Event handling is the core activity of most Palm OS applications: get an event from the queue, handle it if it's one the application is interested in, and if not, pass it to the operating system's default event handlers. Continue until done. :)

The diagram below shows program flow for the event loop. As you can see, it's very simple. The application gets an event from the event queues using EvtGetEvent and handles the event. If the event is an AppStopEvent then execution exits the event loop, otherwise execution loops back to EvtGetEvent.



You use the EvtGetEvent function to fetch an event from the queue. The EvtGetEvent function takes two arguments: the first is a pointer to an EventType structure. EvtGetEvent will load the next event into the structure. The other argument is a timeout specifying how long to wait for an event, if there are no events already waiting in the queue. The timeout is measured in ticks -- to find out the duration of a tick on your device, use the SysTicksPerSecond API call.

The most common timeout argument is probably evtWaitForever. This puts the CPU into doze mode until the user provides some input. Occasionally the programmer defines a time limit, for instance if the application is performing an animation which needs to be updated at regular intervals. Here are some examples of valid EvtGetEvent calls:

                EvtGetEvent(&event, 200);    // Wait 200 ticks (~2 seconds) for an event.
                                             // If timeout expires, nilEvent is returned.
                EvtGetEvent(&event,          // Better way of waiting 2 seconds for an event.
                        2 * SysGetTicksPerSecond()); 
                EvtGetEvent(&event, evtWaitForever); // Self-explanatory

In the first example, if no events happen within 2 seconds then EvtGetEvent will return with a nilEvent in the event structure. Setting the time-out value can be used as a simple method of timing for some applications. However this technique doesn't work for games/applications that use a lot of user input, because the timer gets reset every time an event occurs. For instance, it wouldn't work in Space Invaders, because every time the player pressed a button to move or fire, EvtGetEvent would exit with a keyDownEvent and the 2 second limit might never be reached. Setting a time-out on EvtGetEvent is good for timing simple games, but for more demanding games you'll have to use a more sophisticated mechanism.

The event handler part of the loop is a little more complicated. Before your application gets a chance to handle the event, various parts of the operating system should be offered the event. If the operating system handles the event, then you shouldn't need to. For instance, if a penDownEvent occurs within the bounds of a button then you probably don't want to interfere. After all, you don't want to know when the button was touched, what you want to know is when it was released. Usually the next event generated after a penDownEvent is a penUpEvent. The SysHandleEvent function will determine if the penUpEvent and penDownEvent occured within the bounds of the same button, and if so generate a ctlEnterEvent, which indicates that the button was pushed. Then your application handles the ctlSelectEvent. Simple!

Well, not so simple -- you have to know what events to look for for each of the user interface methods (form buttons, grafiti input, silkscreen buttons, etc.) that you enable for user input. Writing a Palm OS application is all about carefully selecting which events you will handle.

Be warned: If you don't call SysHandleEvent or if you carelessly interfere with the processing of hardware events you can seriously screw up the behaviour of your Palm OS device.

My EventLoop example, below, lets the system handle most events. Notice that you give several different parts of the Palm OS a chance to handle an event: First, SysHandleEvent, second MenuHandleEvent, and finally FrmDispatchEvent.

There's only one event we care about in this example: frmOpenEvent. We handle this event because each form may have its own way of handling events - in other words, its own form event handler function. The last "generic" handler, FrmDispatchEvent, looks up the form's event handler and passes the event on to it. But what do you do when the application changes forms? The form event handler must be changed before control gets to the FrmDispatchEvent function call. A change of form is indicated by frmLoadEvent. So in my EventLoop I detect frmLoadEvents before calling FrmDispatchEvent

Some Palm programmers write a "ApplicationEventHandler" procedure and call this procedure between MenuHandleEvent and FrmDispatchEvent. The ApplicationEventHandler processes frmLoadEvent and appStopEvent.

After all these other handlers get their chance, then your application has an opportunity to handle the event in your customized way. The event gets passed to FrmHandleEvent. FrmHandleEvent will do some stuff with the event (I don't know exactly what) then send the event to the form event handler which you have already defined using FrmSetEventHandler.

                static void EventLoop(void) 
                {
                  do 
                  {
                    EvtGetEvent(&event, 200);

                    if (SysHandleEvent(&event)) continue;
                    if (MenuHandleEvent((void *)0, &event, &err))
                        continue;
 
                    if (event.eType == frmLoadEvent)
                    {
                      formID = event.data.frmLoad.formID;
                      form=FrmInitForm(formID);
                      FrmSetActiveForm(form);
                      switch (formID) 
                      {
                      case formID_Buy:
                        FrmSetEventHandler(form, BuyEventHandler); 
                        break;

                      case formID_Sell:
                        FrmSetEventHandler(form, SellEventHandler);
                        break;

                      case formID_StockMarket:
                        FrmSetEventHandler(form, StockMarketEventHandler);
                        break;

                      case formID_About:
                        FrmSetEventHandler(form, AboutEventHandler);
                        break;

                      case formID_Help:
                        FrmSetEventHandler(form, HelpEventHandler);
                        break;
                      }
                    }
                        FrmDispatchEvent(&event);
                  } while(event.eType != appStopEvent)
                }

3.5 Application Stop Code

Application stop code should save the application's state and close any databases that your application may have opened. Both of these tasks are optional. Your application may not need to save any state information. The M+ key in the built-in calculator is a very simple example of state information. If someone stores a number in the calculator's memory register, it should still be there next time they launch the application. So when the application ends, Calculator has to store it's memory register. It probably uses SysPrefs to do it, but as I mentioned earlier your applications should use a data database. The second function of application stop code is to shut down databases. If your application shuts down and leaves databases open, Palm OS will close them automatically. However I prefer to explicitly close any databases I may have opened. I usually have use a separate CloseDatabase procedure. You may choose to reduce the final PRC size by omitting this call.

3.6 Optimizations and Customizations

Before closing this chapter, you should understand that there are many variations on PilotMain. As I mentioned, some programmers write an ApplicationEventHandler. Some programmers write a MenuEventHandler (MenuEventHandler is always recommended for large apps) and call it directly from their event loop procedure. You should write some code and collect samples from different authors to find out what works best for you.


4.0 FORMS

4.1 Introduction

Humans interact with the Palm OS graphical user interface via user interface objects. From Palm_OS_Companion.pdf, "A Palm OS UI object is a C structure that is linked with one or more items on the screen." Palm OS 3.5 supports the following UI objects:

In addition to these UI objects, Palm OS also supports text and string processing, and bitmap display.

Palm OS organizes UI objects using "forms". A form is a set of UI objects grouped together to achieve some common purpose. For instance, if you press the MemoPad hardware button a form appears on the screen. The form contains a title object "Memo List" in the upper left corner, a popup trigger in the upper right corner (categories), a button in the lower left corner "New", and a two column, eleven row table in the middle of the screen. These UI objects achieve the common objective of listing memos of the category selected with the popup trigger. UI objects are not resources, but they might be associated with a resource, which provides the UI object with data. For instance,

"Windows" are related to forms, but a little different. A window is just a section of the screen. For instance, all forms include a window where the UI objects are displayed. But not all windows are forms. For instance, the screen space occupied by a pull down menu is a window. The screen space occupied by an alert box is a window. Palm OS also permits off-screen windows. Just imagine an invisible section of screen on the back of your handheld that follows all the same rules as normal windows. If you want to draw on the LCD screen inside your window, use the WinDraw API calls, such as WinDrawChars, WinDrawBitmap, etc. However stuff you draw using WinDraw calls isn't part of the form, so it will be erased by a FrmDrawForm call.

A menu might be associated with the form using the MENUID identifier. The form can be specified as modal or non-modal. "Modal forms ignore pen events outside their boundaries" (Palm_OS_Reference.pdf). Helpstrings are only usable with modal forms. A modal form displays the information icon on the right side of the title bar. If a helpstring is defined, then the helpstring text is displayed in an information alert box when the user taps the title bar information icon. You can define a default button for the form. If the form quits, then the system will add a ctlSelectEvent to the Event queue with buttonID equal to the button ID set by DEFAULTBTNID. For instance, suppose your form has two buttons, "do it" and "cancel". This form is displayed when the application stops due to some other user input, for instance another application was launched from the Applications screen. You may want the "cancel" button to execute automatically to release locked handles, reset global variables, etc. This is a situation where you could use DEFAULTBTNID.

To build forms and other resources, Wes Cherry created the Palm Resource Compiler, "PilRC". PilRC has been upgraded and maintained by Aaron Ardiri for the last few years. Defining a form requires multiple PilRC instructions because every option and UI object must be specified. PilRC uses the FORM identifier to identify the start of a form definition, followed by the form options, then the UI objects are contained between BEGIN and END identifiers. I indent the UI object definitions by four spaces, but this is not required. Order of the UI objects is not important. This chapter will discuss the most commonly used UI objects:

PilRC resources start with an identifier, followed by a number of fields. Optional fields are enclosed by [ ], required fields are enclosed by < >. A full explanation of all resource types is in the PilRC's User Manual.

4.2 Title
                TITLE <Title.s>

Title gives your form a title. The title appears at the top of the form as white characters on black background. The characters are always font 1. (The fonts are listed in figure 4.2.2. Download the ASCIIChart tool to see the different fonts on your Palm.) For modal forms the title is centered. In a non-modal form, the title bar uses the top fifteen pixel rows of the form. In a non-modal form the title bar uses only fourteen pixel rows.

4.3 Label
                LABEL <Label.s> ID <Id.n> AT (<Left.p> <Top.p>) [USABLE] [NONUSABLE]
                    [FONT <FontId.n>]

The label object allows you to fix text strings on the form. The font and location is user selectable. (See below for a list of font IDs.) Usable and nonusable supposedly allows the user to determine whether or not the Label is drawn. I suggest you set all labels USABLE. If you want text to appear and disappear use the Field UI object or the WinDrawChars API call.

FONT ID Description Height Width Comments
0 Standard 11 variable extended ASCII character set
1 Bold 11 variable extended ASCII character set
2 Large 14 variable extended ASCII character set
3 Symbol 10 variable alarm clock, up and down arrows, memo, bullet, etc.
4 Symbol11 11 variable AKA checkbox font
5 Symbol7 8 11 up and down triangles
6 LED 19 variable calculator numbers
4.4 Button
                BUTTON <Label.s> ID <Id.n>
                    AT (<Left.p> <Top.p> <Width.p> <Height.p>)
                    [USABLE] [NONUSABLE] [DISABLED] [LEFTANCHOR] [RIGHTANCHOR] [FRAME]
                    [NOFRAME] [BOLDFRAME] [FONT <FontId.n>] [GRAPHICAL]
                    [BITMAPID <bitmapID.n>] [SELECTEDBITMAPID <bitmapID.n>]

A button is exactly what it sounds like: an area on screen, usually framed, which activates some process when touched. You have the option of labeling the button, you decide where the button is located, you decide whether it's usable or non usable. For instance, if your application is a list of words, and you flip from word to word with "Previous" and "Next" buttons, then you may want to make the "Previous" button unusable on the first word and the "Next" button unusable on the last word. Initially you can set the object usable using it's PilRC definition. During runtime, you can set it usable and nonusable using the FrmSetObjectUsable and FrmSetObjectNonusable calls. Setting a button non-usable removes it from the screen. Buttons cannot be "grayed out" as done in some other operating systems. However, some programmers cheat by making a bitmap that looks like a "grayed out" button, and then calling WinDrawBitmap after FrmSetObjectNonusable.

When you tap a button SysHandleEvent puts a ctlSelectEvent onto the queue. The event.data.ctlEnter.controlID field contains the resource ID of the button that was tapped.

4.5 Field
                FIELD ID <Id.n> AT (<Left.p> <Top.p> <Width.p> <Height.p>)
                    [USABLE] [NONUSABLE] [DISABLED] [LEFTALIGN] [RIGHTALIGN]
                    [FONT <FontId.n>] [EDITABLE] [NONEDITABLE] [UNDERLINED] [SINGLELINE]
                    [MULTIPLELINES] [MAXCHARS <MaxChars.n>] [DYNAMICSIZE]

Fields are probably the most powerful tool in the Palm OS UI. You can use a field to display a string. Unlike LABEL, however, you can change the string at runtime using FldSetText, FldSetTextHandle, or FldSetTextPointer. FldSetText can only be used with NONEDITABLE fields. That leads into the important thing about fields - the user can be allowed to edit the field. Using a field the user can write memos, record names and addresses, add to do list items. Lots of applications use fields. The great thing is that Palm OS handles all the messy text processing stuff like backspace, changing focus, where's the cursor, selecting text, etc. Your application just deals with the final result.

It's best to use FldSetTextHandle when setting the text for editable fields. When you use handles the memory manager can resize the allocated memory. As far as I can tell, if you use FldSetTextPointer then the allocated memory cannot change. When you work with handles, if the user only enters one character, memory manager only allocates one byte. If the user enters ten thousand characters, then memory manager allocates ten thousand bytes. Figure 4.5.2 shows my WordPower app using handles with fields and a data database. Each database record stores a text string. To edit the database records, all you have to do is get the handle of the record and assign it to a field. Palm OS will use the database record as it's field text and will resize the database record as required. This is cool. And it saves Palm programmers time and RAM. But remember to set the field handle to NULL before shutting down the form, or else you'll get an error.

                static void EditGetRecord(void)
                {
                  MemHandle recHandle;

                  // Set handle for Word field to Word database
                  recHandle = (Handle) DmGetRecord(WordDB, CurrentRecord);
                  FldSetTextHandle(fieldptr_Word, recHandle);

                  // Set handle for Definition field to Definition database
                  recHandle = (Handle) DmGetRecord(DefinitionDB, CurrentRecord);
                  FldSetTextHandle(fieldptr_Definition, recHandle);
                }

You can make a field UNDERLINED or NONUNDERLINED. For debugging, I find it best to make all fields underlined, that way I can see where they are. You can limit the field to one line (SINGLELINE) or you can permit multiple lines (MULTIPLELINES). For multiple line fields remember to use the FldRecalculateField call after setting the text to recalculate the word wrapping parameters. Your application can change the field options during run-time using the FldSetAttributes call.

4.6 Formbitmap
                FORMBITMAP AT (<Left.p> <Top.p>) BITMAP <BitmapId.n> [NONUSABLE]

A bitmap is actually a separate resource, defined outside a form. But to mount a bitmap inside a form, you must use the FORMBITMAP object. For instance:

FORMBITMAP AT ( 20 20 ) BITMAP 1000

puts bitmap resource 1000 on the form with the top left corner at (20, 20). Only use the FORMBITMAP object when you want the BITMAP to be fixed on the screen at the same location visible all the time. For instance, if you want a fancy frame around a certain form, you could add the frame to your application as a bitmap resource and lock it into your form like this:

FORMBITMAP AT ( 0 0 ) BITMAP bitmapID_FancyFrame

If, on the other hand, you want the bitmap to appear and disappear, to be changed with other bitmaps, to move around the screen don't use FORMBITMAP. Instead use the technique described in section 5.4.

4.7 Graffiti State Indicator
                GRAFFITISTATEINDICATOR AT (<Left.p> <Top.p>)

When you edit a memo, the graffiti state indicator is the symbol in the bottom right corner which shows special symbols for shift, shift lock, and punctuation. The graffiti state indicator isn't automatically there -- you have to add it to your form resource, with code like the above PilRC statement. You don't have to do anything special to manage the indicator: Palm OS keeps track of shift keystrokes and will make sure the graffiti state indicator shows the appropriate symbol.

4.8 Internal Representation

In Palm OS, the form and each UI object exist as data structures. Each data structure is linked with the object on the screen, so if you change the value of the "pos.x" element of a FormLabelType structure, then the label will be in a different horizontal position the next time the form is drawn. The structures for resources and UI objects are contained in the Palm header files, most of which are in the SDK directory include/Core/UI.

4.9 FormEventHandler

The form declaration in PilRC will put objects on the screen, but if you want anything to happen you have to write a form event handler -- a C function. Section 3.4 mentioned that the event loop calls FrmEventHandler and FrmEventHandler calls your customized form event handler. Section 3.4 also demonstrated how you inform Palm OS of your custom form event handler using the FrmSetEventHandler call.

My convention is to give the form event handler the same name as the form, however many programmers add the words "EventHandler" to the end of all their event handler procedure names, which is probably a good idea. You could even put "FormEventHandler" at the end of your procedure name, if you want to make it really obvious. The form event handler deals with events relevant to your form. If your form has a button, then your form event handler has to detect ctlEnterEvent. If your form has a table, your form event handler will have to detect and process a tblSelectEvent. If your form has many objects, then the form event handler can be quite long. To reduce the procedure's size and improve it's readability, typically the form event handler will call other procedures which performs the activities associated with the detected events. Here's my form event handler for the main form of my Stock Market game:

                Boolean StockMarketEventHandler(EventPtr event)
                {
                    UInt16 handled;

                    switch (event->eType) {
                        case frmOpenEvent:
                            StockMarketDrawForm();
                            break;

                        case ctlSelectEvent:
                            // A control button was pressed and released.
                            if (event->data.ctlEnter.controlID == buttonID_Next) {
                                RollDice();
                                SM_Values.turn++;
                                StockMarketDrawForm();
                                handled = true;
                            }
                            if (event->data.ctlEnter.controlID == buttonID_Buy) {
                                FrmGotoForm(formID_Buy);
                                handled = true;
                            }
                            if (event->data.ctlEnter.controlID == buttonID_Sell) {
                                FrmGotoForm(formID_Sell);
                                handled = true;
                            }
                            break;

                        case nilEvent:
                            RollDice();
                            SM_Values.turn++;
                            StockMarketDrawForm();
                            handled = true;
                            break;

                        default:
                            return 0;
                    }
                    return handled;
                }


5.0 OTHER RESOURCES

Chapter four discussed the form resource, but your application will require other resources in order to work. This chapter will explain the following resources:

5.1 Version (tVER)
                VERSION ID <VersionResourceId.n> <Version.s>

Optional. You can use this to indicate the version of the application or resource database. The resource is a string, so you can include characters as well as numbers.

5.2 String (tSTR)
                STRING ID <StringResourceId.n> <String.ss>

You can define strings in the global section of your code, or you can define them as tSTR resources. Modal forms can be associated with a string resource for their help or "tips" button. If you code constant global strings as string resources, then they can be accessed by other applications. Instead of using a data database to save memos, you could use a resource database and save each memo as a string resource. I haven't tried this yet, so no sample code.

5.3 Alert (tALT)
                ALERT ID <AlertResrouceId.n>
                [HELPID <HelpId.n>]
                [INFORMATION] [CONFIRMATION] [WARNING] [ERROR]
                BEGIN
                    TITLE <Title.s>
                    MESSAGE <Message.ss>
                    BUTTONS <Button.s> <BUTTON.s>...
                END

There are four different types of Alert resources: information, confirmation, warning and error. The only difference between the four is the bitmap displayed when it pops up. Alert resources are handy for displaying a message and halting the application until the user acknowledges the message. The alert resource is modal, meaning that it stays on the screen until the button is pushed. For this reason, your Alert IDs should almost always have at least one button.

You can use an alert for user input. You don't need to write an event handler for the alert's buttons. Instead, the FrmAlert and FrmCustomAlert calls return the number of the button that the user pushed. The buttons are numbered in the order that they are declared in the PilRC file. When you call FrmAlert the alert box is displayed. Your application halts until the user presses one of the buttons, then the number of the button is returned to your application.

Most alert IDs are constant, meaning that you define what they look like in the PilRC file and they always look the same. But you can also make dynamic alert boxes. Use the FrmCustomAlert call. When you call FrmCustomAlert you pass up to three strings. Palm OS looks for the symbols "^1" "^2" and "^3" in the MESSAGE part of the requested alert box, and replaces these symbols with the strings defined in the FrmCustomAlert call. This is a fairly powerful capability. For example:

                // PilRC file
                ALERT ID 1000
                INFORMATION
                BEGIN
                  TITLE "Custom Alert Demo"
                  MESSAGE "First choice is ^1 \n" \ 
                    "Second choice is ^2 \n" \ 
                    "Third choice is ^3" 
                  BUTTONS "First" "Second" "Third"
                END

                // GCC file
                  switch FrmCustomAlert(1000, "Apples", "Oranges", "Bananas");
                  {
                  case 0:
                    WinDrawChars("Apples", 6, 30, 30);
                    break;

                  case 1:
                    WinDrawChars("Oranges", 7, 30, 30);
                    break;

                  case 2:
                    WinDrawChars("Bananas", 7, 30, 30);
                    break;
                  }
5.4 Bitmap (Tbmp)
                BITMAP       [<ResType.s>] ID <BitmapResourceId.n> <BitmapFileName.s> 
                             [NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
                BITMAPGREY   [<ResType.s>] ID <BitmapResourceId.n> <BitmapFileName.s> 
                             [NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
                BITMAPGREY16 [<ResType.s>] ID <BitmapResourceId.n> <BitmapFileName.s> 
                             [NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
                BITMAPCOLOR16 [<ResType.s>] ID <BitmapResourceId.n> <BitmapFileName.s> 
                             [NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
                BITMAPCOLOR  [<ResType.s>] ID <BitmapResourceId.n> <BitmapFileName.s> 
                             [NOCOLORTABLE] [COLORTABLE]
                             [TRANSPARENT r g b] [TRANSPARENTINDEX index]
                             [NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
                BITMAPFAMILY [<ResType.s>] ID <BitmapResourceId.n> 
                             <BitmapFileName.s> ... <BitmapFileName.s> 
                             [NOCOLORTABLE] [COLORTABLE]
                             [TRANSPARENT r g b] [TRANSPARENTINDEX index]
                             [NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
                BITMAPFAMILYSPECIAL [<ResType.s>] ID <BitmapResourceId.n> 
                             <BitmapFileName.s> ... <BitmapFileName.s> 
                             [NOCOLORTABLE] [COLORTABLE]
                             [TRANSPARENT r g b] [TRANSPARENTINDEX index]
                             [NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]

Palm OS 3.5 has powerful bitmap capabilities in support of the color series of Palm OS handhelds. Palm OS supports monochrome bitmaps (BITMAP pilrc type), 2-bit per pixel (2 bpp) grey bitmaps (BITMAPGREY), 4 bpp grey bitmaps (BITMAPGREY16), 4 bpp color (BITMAPCOLOR16), and 8 bpp color (BITMAPCOLOR). The color bitmap resources use indexed color tables, so 4 bpp means that the color table contains 16 colors. Likewise the 8 bpp color bitmap uses a 256 index color table. The color table consists of the usual RGB quad structures. The index color bitmaps also support a transparent index. If no color table is included with the bitmap, then the OS will use the draw window's current palette. Usually this is the default system palette, but you can change the current palette using the WinPalette function. This can be handy if you need to draw many bitmaps which have a common palette.

PilRC 2.7 has a quirk when creating color bitmaps. If you use the COLORTABLE option, then pilrc will create a bitmap resource using the with a colortable and preserving the indices in your original bitmap. Colortables use up some memory, but that isn't a big deal anymore with 8MB handhelds. However, Palm OS draws colortables very slowly. Palm OS recommends that programmers use the WinPalette function to change the draw window's palette to your desired colortable, then draw the bitmap using a bitmap resource which does not include a colortable but whose color indices are synchronized to your custom colortable. Now the problem: if you use pilrc to compile a bitmap with the NOCOLORTABLE option, then pilrc assumes that you want to use the default system colortable, and pilrc always changes the color indices of your bitmap to the closest default system color. Effectively, pilrc has randomized your colors. The bottom line is, if you use pilrc and want to display a bitmap using a custom palette, then you can't use the WinPalette function -- instead you have to create bitmaps using the COLORTABLE option and accept the performance penalty.

In order to achieve portability of applications between Palm OS devices with different color capabilities, Palm OS allows bitmaps to be grouped together into bitmap families. The bitmap family would contain a copy of the bitmap at each bit depth. Then the operating system automatically displays the bitmap possessing the appropriate number of colors for whichever device the application is running on. Bitmap families are optional -- if you try to display a color bitmap on a Palm OS device which cannot display a color bitmap, the OS will do it's best to reduce the color depth.

Palm OS supports two types of compression: scan line and RLE.

Use WinDrawBitmap to draw a bitmap. To erase a bitmap use WinEraseRectangle or redraw the form using FrmDrawForm. To "permanently" embed a bitmap in a form, define it as a UI object in the form using the FORMBITMAP identifier in the PilRC file.

5.5 Menu (MBAR)
                MENU ID <MenuResourceId.n>
                BEGIN
                  <PULLDOWNS>
                END

                <PULLDOWNS>: one or more of:

                PULLDOWN <PulldownTitle.s>
                BEGIN
                  <MENUITEMS>
                END

                <MENUITEMS>: one or more of:

                MENUITEM <MenuItem.s> <MenuItemId.n> [AccelChar.c]

When you define a form in PilRC you can associate the form to an MBAR resource. If you want, you should be able to have multiple menus for the same form. You can only associate one in the PilRC definition, but supposedly your app can switch menus using the MenuSetActiveMenu. I've never done it, so I can't provide sample code.

Palm OS doesn't allow grayed out menu items, and doesn't support check marks beside menu items, as some other GUIs do. It does allow "keyboard shortcuts" (Palm Computing's name) which are optional and declared with the [AccelChar.c] field of the MENUITEM identifier. The keyboard shortcut allows the user to choose the menu item using the "Command" graffiti stroke followed by [AccelChar.c].

Don't be confused by guide1.pdf. Guide1.pdf defines two menu UI resources: menu bar (MBAR) and menu (MENU). Guide1.pdf describes the pulldowns as separate MENU resources. As far as I can tell, PilRC does not generate MENU resources. I've inspected PRC files and can't find any MENU resources. The menus are included within the MBAR resource.

5.6 Code (code)

Code is stored as a resource in the database. Simple applications like those described in this tutorial have two codes resources: code0000 and code0001. code0000 defines the stack size and memory requirements for global variables. code0001 is where your application code goes. There are two limitations on the size of a code resource: the first is the maximum chunk size, as previously discussed. All Palm OS memory chunks, include code resources, must be less than 64k bytes. The second limitation is that the current microprocessor (Motorola 68328) used in the Palm OS devices has a 16-bit relative jump limitation. That places a limit of about 32k bytes on the code resource. Because of these limitations, complicated Palm OS applications must include multiple code resources.

5.7 Data (data)

The data0000 resource contains the information used to initialize the application's global variables.

5.8 Application (APPL)
                APPLICATION ID <ApplResourceId.n> <APPL.s>
                <APPL.s> must be 4 characters long

The application resource may be used to define the application's creatorID. I never use this resource -- I specify the creatorID when I run build-prc.

5.9 Application Icon Bitmap (tAIB)
                ICON "myicon.bmp"
                ICONFAMILY "icon1bpp.bmp" "icon2bpp.bmp"
                ICONFAMILY "icon1bpp.bmp" "icon2bpp.bmp" "icon4bpp.bmp" "icon8bpp.bmp"
                ICONFAMILY "icon1bpp.bmp" "" "" "icon8bpp.bmp" TRANSPARENTINDEX 255

The Palm OS application launcher uses this resource. If an application has a tAIB resource then the launcher will use it as the application's icon using standard bitmap commands. Resource ID 1000 is automatically assigned. ICONFAMILY allows the programmer to create different icons for Palm OS devices which possess different display capabilities.

5.10 Application Icon Name (tAIN)
                APPLICATIONICONNAME ID <AINResourceId.n> <ApplicationName.s>

The Palm OS application launcher uses this resource. If an application has a tAIN resource, then it will be displayed in the launcher as the application name. Otherwise, the application launcher uses the name field from the application's resource database header. I don't use this resource - the name field in the database works fine.


6.0 SIMPLE EXAMPLE

Now that you know how to create resources and write a PilotMain procedure, it's time to put these together and build a simple Palm OS application. A simple application is an application with one form, a few alert dialogs, one menu, buttons, fields, bitmaps, and labels. We will build the application in eight steps:

  1. State the aim of the application
  2. Design the form
  3. Design other resources
  4. Write the header file
  5. Write the PilotMain function
  6. Write the form handler function
  7. Write the makefile
  8. Testing and debugging

Before starting this chapter, you should have installed prc-tools and pilrc. The tex2hex source files should be unzipped in a convenient subdirectory. You should have your editor up and aimed at the tex2hex source and have a command window open for making the file. You should also open a paint application and have a look at the two bitmap files, "text.bmp" and "hex.bmp".

In this chapter code fragments will be interspersed with the discussion. I've removed the "Figure 6.x.x" labels because they were annoying. Code is in fixed font, discussion is in proportional font.

6.1 State the Aim of the Application

It's important to state the aim of the application at the beginning, especially if you are writing the application for someone else. If you're just writing for fun, it's still important to know what you want before you start coding. For this example, we want an application that converts text strings to their hexadecimal equivalents. I want to enter a text string, push a button, and see the hexadecimal representation of the string. In the hexadecimal representation, I want each two character byte translation separated by hyphens for easy viewing. If someone tries to convert a null string, an error message should be displayed.

I'm an egotist, so I want an "about" screen which will show my name. To get to the about screen there must be a menubar with an "about" selection on it. The menu should also have a "Convert" command, which does the same thing as the on-screen button.

Just for practice, I want a few bitmaps. At the bottom of the screen there should be a bitmap of some ASCII characters. Every two seconds it should change to hexadecimal representation, and vice versa.

I want to be able to copy the text string to the clipboard and paste text strings from the clipboard to the application. The copy and paste commands should appear in the menu.

If someone enters something in the field and leaves the application, it should be there when they relaunch Tex2Hex.

6.2 Design the Form

The application needs one form. It doesn't matter whether or not the form is modal or if it has a frame. I choose a non modal form with no frame, because I like the look. The form must have a menu bar. It's non-modal so it can't have a helpstring. It doesn't need a default button. I'm going to call the form "Tex 2 Hex". I want the form to cover the whole screen.

The form must have a field for entering the text string. I want the text field to be centred near the top of the screen, about 100 pixels wide. This gives it a top left corner around (40 40). I will fine tune the layout later. The field has to be editable, should be leftaligned and use the standard font. Underlined is good, so the user knows how much room is available. Maximum number of characters will be 20 (100 pixels, average width for standard font is about five pixels). I can change that later, if I want.

The form must have a field for displaying the hexadecimal conversion. The hex field should be underneath the text field, centered, 100 pixels wide. Left align the field, use the standard font. Each text character will be converted to two hex characters plus a space to separate the hex numbers. So if the text field is twenty characters wide, the hex field must accomodate sixty characters. At five pixels per character that won't fit on a single line, so the field must be multiline. It doesn't have to be underlined. It can't be editable - the user doesn't change the hex representation.

The form needs a command button to initiate the conversion. The button should go between the two fields and have the caption "Convert to hex".

The alternating bitmaps will go at the bottom of the screen. Because they alternate, we won't use FORMBITMAP, but we should make sure that there's an empty space where we want the bitmaps to be. The bitmaps are 120 pixels wide and 40 pixels high.

The user may want to enter shifted characters, so we should put a graffitistateindicator in the bottom right corner.

Now we have enough information to write our PilRC code defining the form:

                FORM ID formID_tex2hex AT ( 0 0 160 160 )
                NOFRAME
                USABLE
                MENUID menuID_tex2hex
                BEGIN
                    TITLE "Tex 2 Hex"
                    FIELD ID fieldID_text AT (30 25 100 AUTO ) USABLE LEFTALIGN FONT 0
                            EDITABLE UNDERLINED SINGLELINE MAXCHARS 20
                    BUTTON "Convert to Hex" ID buttonID_convert AT ( 40 48 AUTO AUTO)
                            USABLE FRAME FONT 0
                    FIELD ID fieldID_hex AT (30 70 100 50 ) USABLE LEFTALIGN FONT 0
                            NONEDITABLE MULTIPLELINES MAXCHARS 90
                    GRAFFITISTATEINDICATOR AT (145 150)
                END
6.3 Design other Resources

This is Tex2Hex version 1.0, so I'll use a tVER resource. I used Paint to make a customized icon. It's in file "tex2hex.bmp". I used Paint to make two bitmaps -- "text.bmp" and "hex.bmp" -- that will alternate at the bottom of the screen.

To make the user interface consistent with other applications, I'll use two pull down menus, "Commands" and "Edit". Commands will include the Convert and About instructions. Edit will include Copy and Paste. Each command will have a graffiti shortcut character.

We need two alert resources. We can use an information alert for the About screen. We can use an error alert if the user tries to convert a null string.

                VERSION "1.0"
                ICON "tex2hex.bmp"
                BITMAP ID bitmapID_text "text.bmp"
                BITMAP ID bitmapID_hex  "hex.bmp"

                MENU ID menuID_tex2hex
                BEGIN
                  PULLDOWN "Commands"
                  BEGIN
                    MENUITEM "Convert" menuitemID_convert
                    MENUITEM "About"   menuitemID_about   "A"
                  END
                  PULLDOWN "Edit"
                  BEGIN
                    MENUITEM "Copy"    menuitemID_copy    "C"
                    MENUITEM "Paste"   menuitemID_paste   "P"
                  END
                END

                ALERT ID alertID_about
                INFORMATION
                BEGIN
                  TITLE "About Tex2Hex"
                  MESSAGE "Demo program for the PRC-Tools tutorial"
                  BUTTONS "Done"
                END

                ALERT ID alertID_errornullstring
                ERROR
                BEGIN
                  TITLE "Error"
                  MESSAGE "Cannot convert a null string"
                  BUTTONS "Oops"
                END
6.4 Write the header file

You have probably noticed that I'm not using literal numbers for resource IDs. I put all my resource IDs in a header file and include the header file in both the gcc source file and the PilRC source file. Figure 6.4 shows the header file so far.

                #define formID_tex2hex          1000

                #define menuID_tex2hex          1000

                #define menuitemID_convert      1000
                #define menuitemID_about        1001
                #define menuitemID_copy         1002
                #define menuitemID_paste        1003

                #define bitmapID_text           1000
                #define bitmapID_hex            1001

                #define fieldID_text            1000
                #define fieldID_hex             1001

                #define alertID_about           1000
                #define alertID_errornullstring 1001

                #define buttonID_convert        1000
6.5 Write PilotMain, StartApplication, StopApplication, and EventLoop

The PilotMain procedure is straight-forward.

                UInt32 PilotMain (UInt16 cmd, void *cmdPBP, UInt16 launchFlags)
                {
                  UInt16 error;

                  if (cmd == sysAppLaunchCmdNormalLaunch)
                  {
                    error = StartApplication();     // Application start code
                    if (error) return error;

                    EventLoop();                    // Event loop

                    StopApplication ();             // Application stop code
                  }
                  return 0;
                }

StartApplication is unremarkable. OpenDatabase creates the database if it doesn't exist and creates record zero containing a null string. In section 3.3 I said that the OpenDatabase procedure usually loads saved values from record zero, but in this case we won't, because we will use a special Palm OS memory manager trick which will be described in section 6.6.

                static UInt16 StartApplication(void)
                {
                  UInt16 error;

                  error = OpenDatabase();
                  if (error)
                    return error;

                  FrmGotoForm(formID_tex2hex);
                }

                static Boolean OpenDatabase(void)
                {
                  UInt16       index = 0;
                  MemHandle    RecHandle;
                  MemPtr       RecPointer;
                  char         nullstring = 0;

                  Tex2HexDB = DmOpenDatabaseByTypeCreator(Tex2HexDBType, Tex2HexAppID,
                      dmModeReadWrite);
                  if (!Tex2HexDB) {
                    if (DmCreateDatabase(0, Tex2HexDBName, Tex2HexAppID, Tex2HexDBType,
                        false)) {
                      return 1;
                    }
                    Tex2HexDB = DmOpenDatabaseByTypeCreator(Tex2HexDBType, Tex2HexAppID,
                        dmModeReadWrite);
                    RecHandle = DmNewRecord(Tex2HexDB, &index, 1);
                    RecPointer = MemHandleLock(RecHandle);
                    DmWrite(RecPointer, 0, &nullstring, 1);
                    MemPtrUnlock(RecPointer); 
                    DmReleaseRecord(Tex2HexDB, index, true);  
                  }
                  return 0;
                }

Because there's only one form, the EventLoop doesn't need to use a switch statement to process the frmLoadEvent. After all, there's nothing to switch -- event.data.frmLoad.formID can only have one value, formID_tex2hex, because that's the only form in the application. In fact, for single form applications you can move the FrmInitForm, FrmSetActiveForm, and FrmSetEventHandler form calls into the StartApplication procedure. Then you don't need to switch on frmLoadEvent at all and you can delete the FrmGotoForm call from StartApplication. This optimization will save you some RAM. I've included the switch anyway, because eventually you will want to write multi-form applications, and then you'll need it. If you want, after completing this chapter you can optimize the example by yourself and see how much RAM you can save.

                static void EventLoop(void)
                {
                  UInt16    err;
                  UInt32    formID;
                  FormPtr   form;
                  EventType event;

                  do
                  {
                    EvtGetEvent(&event, 200);

                    if (SysHandleEvent(&event)) continue;
                    if (MenuHandleEvent((void *)0, &event, &err)) continue;

                    if (event.eType == frmLoadEvent)
                    {
                      formID = event.data.frmLoad.formID;
                      form = FrmInitForm(formID);
                      FrmSetActiveForm(form);
                      switch (formID) 
                      {
                      case formID_tex2hex:
                        FrmSetEventHandler(form, (FormEventHandlerPtr) tex2hex);
                        break;
                      }
                    }
                    FrmDispatchEvent(&event);
                  } while(event.eType != appStopEvent);
                }

Note the FldSetTextHandle call in StopApplication. Since we're using the technique of setting the field handle to record zero's database handle (which will be explained in section 6.6), we have to reset the field handle to null before the form is closed. Otherwise Palm OS would try to deallocate a handle which is still being used by the database. Strangely, however, in this application you can leave out the FldSetTextHandle(fieldptr_text, NULL) call and it still works. Maybe it's because the database closes before the field gets deallocated. Nevertheless, I recommend leaving the FldSetTextHandle call in, otherwise you might forget in some other app where it might cause an error.

                static void StopApplication(void)
                {
                  FldSetTextHandle(fieldptr_text, NULL);
                  DmCloseDatabase(Tex2HexDB);
                }

You may have noticed some global variables and a few new definitions in this code. The global declarations and definitions will go at the beginning of the tex2hex.c file. The definitions could go in the tex2hex.h file, if you prefer. One note: if you ever build a large application which has several header files and many source code files, you should remember that the extern keyword will cause PilRC to fail, so external declarations must appear in your C file. Tex2HexAppID and Tex2HexDBType are used in the DmCreateDatabase call. By specifying Tex2HexAppID the same as the application's creator ID, Palm OS knows that this data database belongs to our Text to Hex application.

                // FieldPtr and DmOpenRef are defined in the Palm OS header files.
                // See if you can find where they are defined.
                FieldPtr   fieldptr_text;   
                FieldPtr   fieldptr_hex;    
                DmOpenRef  Tex2HexDB;
                char       Tex2HexDBName[] = "Tex2HexDB";

                #define Tex2HexAppID    'TxHx'
                #define Tex2HexDBType   'Data'
6.6 Write the Form Handler

We're going to use the technique of setting the text field handle to record zero's database handle. At this point it is important to understand what a handle is. The Palm OS memory manager divides memory into "chunks". A chunk is resizable and relocatable. This is important, because it means that if a chunk is being used for a database record its size can change if the record gets larger or smaller. And if the chunk happens to be located between two other chunks, and there isn't enough room to grow, then the memory manager can relocate the chunk somewhere else. Every chunk is identified by a unique 32-bit number -- this 32-bit number is called the handle. So when we get the handle of the database record, and then use FldSetTextHandle to the value of the database record handle, we are telling the Palm OS memory manager that the field will use the same memory chunk as database record zero. This is great - when the application closes we don't have to save the contents of the field because they are already stored in the database record chunk! But the problem with this technique is that we can't set the field text handle in the StartApplication procedure because the form has not been initialized yet. We have to wait until control gets to the form event handler -- at that time we know that the form has been initialized and that space has been allocated for a field UI structure.

                Boolean tex2hex(EventPtr event)
                {
                  FormPtr   form;
                  UInt16    handled = 0;

                  switch (event->eType) 
                  {
                  case frmOpenEvent:
                    form = FrmGetActiveForm();
                    fieldptr_text = FrmGetObjectPtr(form,
                        FrmGetObjectIndex(form, fieldID_text));
                    FldSetTextHandle(fieldptr_text,
                        (MemHandle)DmGetRecord(Tex2HexDB, 0));
                    fieldptr_hex = FrmGetObjectPtr(form,
                        FrmGetObjectIndex(form, fieldID_hex));
                    FrmDrawForm(form);
                    handled = 1;
                    break;
                    
                  case ctlSelectEvent:  // A control button was pressed and released.
                    if (event->data.ctlEnter.controlID== buttonID_convert)
                    {
                      Convert();
                      handled = 1;
                    }
                    break;

                  case menuEvent:
                    switch (event->data.menu.itemID)
                    {
                    case menuitemID_convert:
                      Convert();
                      break;
                    case menuitemID_about:
                      FrmAlert(alertID_about);
                      break;
                    case menuitemID_copy:
                      FldCopy(fieldptr_text);   // copy text user has highlighted
                      break;
                    case menuitemID_paste:
                      FldPaste(fieldptr_text);
                      break;
                    }
                    handled = 1;
                    break;

                  case nilEvent:
                    ChangeBitmap();
                    handled = 1;
                    break;
                  }
                  return handled;
                }

The point of this tutorial is to explain Palm OS programming, not how to convert ASCII to hex, so I'll just skim over most of the Convert procedure. The FldGetTextPtr returns a pointer to the character string that the user input. We assemble the hexadecimal output in the global string hex[60]. Then use the FldSetTextPtr to point the hex field at the hex string. We can do this because the hex field is a read only field. If the user were able to input directly into the hex field, then we wouldn't be able to use the FldSetTextPtr call. Why? Well, to answer this, you need to know the difference between handles and pointers.

We already talked about handles -- the memory manager works with relocatable, resizable "chunks" of memory and each chunk is identified by a unique handle. So suppose you want to copy a string to a database record. A string is a specific section of memory whose location is given by the string pointer, for instance "hex" in our tex2hex application. But a handle isn't a specific area of memory -- chunks are relocatable. So to copy from a string to a chunk, first you have to lock the chunk so it doesn't move around, and second you have to find out exactly where the chunk is. The API function MemHandleLock performs both these tasks: it locks the chunk and it returns a pointer to where the chunk is. Go back and have another look at the OpenDatabase() example. Near the bottom the procedure uses the MemMove function to copy the game's parameters to record zero of the database. To do that, the database record has to be fixed in memory: you can't copy memory to a moving target. So the previous line uses MemHandleLock to lock the record in place and get a pointer to its location. After the MemMove, the procedure uses MemPtrUnlock to return control of the chunk back to the memory manager. If you didn't understand this, then don't be disappointed, it can take a while to figure this out. However you must understand the difference between pointers and handles if you want to program Palm OS effectively.

Another interesting thing to point out is that the hex string must be global. If it were a local variable, then it would cease to exist after the function returns. Then the hex field would point at nothing.

                void Convert(void)
                {
                  char     textchar;
                  UInt16   textpos;
                  UInt16   lownybble, hinybble;
                  char     hexchar;
                  UInt16   textlength;
                  char*    textpointer;

                  textpointer = FldGetTextPtr(fieldptr_text);
                  textlength = FldGetTextLength(fieldptr_text);
                  if (textlength) {
                    for (textpos = 0; textpos < textlength; textpos ++) {
                      textchar = textpointer[textpos];
                      lownybble = textchar % 16;
                      hinybble = textchar / 16;
                      if (hinybble < 10) hexchar = 48 + hinybble;
                        else hexchar = 55 + hinybble;
                      hex[textpos * 3] = hexchar;
                      if (lownybble < 10) hexchar = 48 + lownybble;
                        else hexchar = 55 + lownybble;
                      hex[textpos * 3 + 1] = hexchar;
                      hex[textpos * 3 + 2] = 45;
                    }
                    hex [textpos * 3 - 1] = 0;
                    FldSetTextPtr(fieldptr_hex, hex);
                    FldRecalculateField(fieldptr_hex, true);
                    FrmDrawForm(FrmGetActiveForm());
                  }
                  else {
                    FrmAlert(alertID_errornullstring);
                  }
                }

                void ChangeBitmap(void)
                {
                  MemHandle   bitmap_handle;

                  WhichBitmap = (WhichBitmap + 1) % 2;
                  if (WhichBitmap == 0) {
                    bitmap_handle = DmGet1Resource('Tbmp', bitmapID_text);
                  }
                  else {
                    bitmap_handle = DmGet1Resource('Tbmp', bitmapID_hex);
                  }
                  WinDrawBitmap (bitmap_handle, 20, 120);
                }
6.7 Write the Makefile

The prc-tools tool chain uses the Unix make utility make to make the application. Make is a complicated utility, I won't try to explain it here. Try searching google for "make tutorials", and you can read the GNU make manual here.

Here's a simple Makefile. "all" is a phony target. When a make file produces more than one output file, then "all" is important. For a single prc you don't need to use it. The list (ls) command isn't necessary. It's there because I like to see the filesize of the built prc. In the last rule, I've used gcc's -c option to separate the compiling and linking. You don't have to separate the compiling and linking. You could use the single command m68k-palmos-coff-gcc -O1 tex2hex.c -o tex2hex.

So how does all this work? pilrc compiles the tex2hex.rcp file, creating a bunch of files all have the suffix "bin". gcc compiles the code, creating a file named tex2hex. build-prc takes the code and data resources out of the tex2hex file, and takes all the "bin" files and creates a prc file.

MAKEFILE WARNING: in a makefile, a tab must appear before every command. For instance, the first character of line 4 of the makefile is a tab, the second character is a "b", the third character is a "u", etc. Some editors automatically replace tabs with spaces. If your editor does this you will have problems.

I always like to have a "clean" rule. So I can enter "make clean" and all the temporary files will be removed.

                all : tex2hex.prc

                tex2hex.prc : tex2hex tfrm03e8.bin
                        build-prc tex2hex.prc "Text to Hex" TxHx tex2hex *.bin 
                        ls -l *.prc

                tfrm03e8.bin: tex2hex.rcp tex2hex.h tex2hex.bmp
                        pilrc tex2hex.rcp 

                tex2hex: tex2hex.c tex2hex.h
                        m68k-palmos-coff-gcc -O1 -c tex2hex.c -o tex2hex.o 
                        m68k-palmos-coff-gcc -O1 tex2hex.o -o tex2hex

                clean:
                        rm -f *.bin *.o tex2hex *.grc *~

Everything's ready to go. Now you should go to your command line and compile the prc. The next section will be more valuable if you read it while working on the file.

6.8 Testing and Debugging

You read through the entire tutorial, compiled the code in the example, and it didn't work! Of course not. I'm not a great programmer. I don't think I've ever had an app work right on the first make. In my opinion testing and debugging is an essential part of building an application. I left a couple bugs in the code to illustrate the testing and debugging function. There were other mistakes on the first compile, but going through all of them would be too boring. As we identify the bugs and make corrections, you should correct the source code using your editor.

Before we start, I'll tell you something now that you should remember whenever the app you're debugging uses a data database. When you debug you may load the same app into your handheld or emulator again and again. Reread section 2.2. When an application is hotsync'd to your Palm or Visor (or loaded into POSE) the previous application is erased. But it's data database is not erased. If you have a problem with your database calls, then something like this might happen: load app first time, find a user interface error, fix the user interface error, load app second time, app crashes when its launched. The first time you launched the app it created a bad database. The second time you launched the app, it tried to load the bad database and crashed. I suggest that if your application uses a data database, use the memory application to delete the application before you load in a new copy. The memory application will delete all data databases having the same Creator ID as your application.

So try make again. GCC barfed lots of stuff. If you're new to C don't be dismayed -- in C little mistakes often create tons of error and warning messages. You've probably just forgotten to terminate a line with a semicolon. Make it again, this time use the pause key to stop the display before the first lines scroll off the screen. I see this:

                [palm@radagast tex2hex]$ make
                m68k-palmos-gcc -O1 -c tex2hex.c -o tex2hex.o 
                tex2hex.c: In function `ChangeBitmap':
                tex2hex.c:34: parameter `Tex2HexDBName' is initialized
                tex2hex.c:39: parse error before `{'
                tex2hex.c:39: declaration for parameter `PilotMain' but no such parameter
                tex2hex.c:36: declaration for parameter `WhichBitmap' but no such parameter
                tex2hex.c:35: declaration for parameter `hex' but no such parameter
                tex2hex.c:34: declaration for parameter `Tex2HexDBName' but no such parameter
                tex2hex.c:33: declaration for parameter `Tex2HexDB' but no such parameter
                tex2hex.c:32: declaration for parameter `fieldptr_hex' but no such parameter
                tex2hex.c:31: declaration for parameter `fieldptr_text' but no such parameter
                tex2hex.c:39: number of arguments doesn't match prototype
                cc1: prototype declaration
                tex2hex.c:42: `cmd' undeclared (first use in this function)
                tex2hex.c:42: (Each undeclared identifier is reported only once
                tex2hex.c:42: for each function it appears in.)
                tex2hex.c:46: warning: `return' with a value, in function returning void
                tex2hex.c:52: warning: `return' with a value, in function returning void
                tex2hex.c: In function `OpenDatabase':
                tex2hex.c:72: `Tex2HexDB' undeclared (first use in this function)
                tex2hex.c:74: `Tex2HexDBName' undeclared (first use in this function)
                tex2hex.c: In function `EventLoop':
                tex2hex.c:114: parse error before `}'
                tex2hex.c: In function `StopApplication':
                tex2hex.c:118: `fieldptr_text' undeclared (first use in this function)
                tex2hex.c:119: `Tex2HexDB' undeclared (first use in this function)
                tex2hex.c: In function `tex2hex':
                tex2hex.c:131: `fieldptr_text' undeclared (first use in this function)
                tex2hex.c:132: `Tex2HexDB' undeclared (first use in this function)
                tex2hex.c:133: `fieldptr_hex' undeclared (first use in this function)
                tex2hex.c: In function `Convert':
                tex2hex.c:182: `fieldptr_text' undeclared (first use in this function)
                tex2hex.c:193: `hex' undeclared (first use in this function)
                tex2hex.c:200: `fieldptr_hex' undeclared (first use in this function)
                tex2hex.c: At top level:
                tex2hex.c:211: redefinition of `ChangeBitmap'
                tex2hex.c:31: `ChangeBitmap' previously defined here
                tex2hex.c: In function `ChangeBitmap':
                tex2hex.c:214: `WhichBitmap' undeclared (first use in this function)
                tex2hex.c:217: warning: passing arg 1 of `WinDrawBitmap' from incompatible \ 
                pointer type
                make: *** [tex2hex] Error 1
                [palm@radagast tex2hex]$ 

Look for the first line in the file that has an error. Sometimes the errors aren't reported in order. The first reported error is in line 31, so look there first. Line 31 looks fine. Work your way back. The previous line is missing a semicolon. Add the semi-colon and make again.

                [palm@radagast tex2hex]$ make
                m68k-palmos-gcc -O1 -c tex2hex.c -o tex2hex.o 
                tex2hex.c: In function `EventLoop':
                tex2hex.c:114: parse error before `}'
                tex2hex.c: In function `ChangeBitmap':
                tex2hex.c:217: warning: passing arg 1 of `WinDrawBitmap' from incompatible \ 
                pointer type
                make: *** [tex2hex] Error 1
                [palm@radagast tex2hex]$ 

The first error is "parse error before `}'". Check line 114. It's a "}". There must be something wrong with the {} nesting. This took a minute or two to figure out. It's the while(event.eType != appStopEvent) statement. It's at the end of the "if" statement, it should be at the end of the "do" statement. The corrected code looks like this:

                      case formID_tex2hex:
                        FrmSetEventHandler(form, tex2hexHandler);
                        break;
                      }
                    }
                  FrmDispatchEvent(&event);
                  } while(event.eType != appStopEvent);
                }

Make it again. Warning from line 219 "tex2hex.c:219: warning: passing arg 1 of `WinDrawBitmap' from incompatible pointer type", but the code compiles, links, and builds properly. Lets check the pointer type. Guide1.pdf says that the argument for WinDrawBitmap should be of type "BitmapPtr". Guide2.pdf says that DmGet1Resource is of type "VoidHand". But does DmGet1Resource return a handle or a pointer? Later, guide2.pdf claims"Result: Returns a pointer to resource data, or nil if unsuccessful." For now we can ignore the warning and see what happens.

So load, launch, and try converting some numbers. The graphics don't show up. So the warning was probably important. Use MemHandleLock to convert the handle to a pointer. The corrected code is below.

                static void ChangeBitmap(void)
                {
                  VoidHand   bitmaphandle;
                  BitmapPtr  bitmap;

                  WhichBitmap = (WhichBitmap + 1) % 2;
                  if (WhichBitmap == 0) bitmaphandle = DmGet1Resource('Tbmp', bitmapID_text);
                  else bitmaphandle = DmGetResource('Tbmp', bitmapID_hex);
                  bitmap = MemHandleLock(bitmaphandle);
                  WinDrawBitmap(bitmap, 20, 120);
                  MemHandleUnlock(bitmaphandle);
                }

Make gives no errors or warnings. Load and launch the application on POSE. Try to convert a number. The bitmaps work, but they flash too fast when the field has the focus. The graphics seem to be synchronized with the cursor. The cursor turning on and off must be accompanied by a nil event. Cool, I didn't know that. If you don't like the graphic flashing that fast you can find your own way to deal with it, for instance a global counter. Next, test all the menu options. Test the grafitti shortcut commands for the menu options.

Time to test the database. Put something in the text field. Change to another application. Fine. Change back to Tex to Hex. The text isn't in the field. (That's the behaviour I got on POSE. I got a "Data manager error getting record" on my PalmPilot (Palm OS 2.0) unit.) We must have neglected to do something when we closed the database. Looking up DmCloseDatabase in Palm_OS_Reference.pdf "This routine doesn't unlock any records that were left locked. Records and resources should not be left locked." Is the record locked? Under DmGetRecord Palm_OS_Reference.pdf says "Return a handle to given record and sets the busy bit for the record." On page 177, Palm_OS_Companion says "The busy bit is set when an application currently has a record locked for reading or writing." So the first time Tex2Hex runs it gets record zero and locks it. The second time it runs, the record is still locked. The normal way to unlock a record is to release it before leaving the application. So we add a DmReleaseRecord call to the StopApplication procedure.

                static void StopApplication(void)
                {
                  FldSetTextHandle(fieldptr_text, NULL);
                  DmReleaseRecord(Tex2HexDB, 0, false);
                  DmCloseDatabase(Tex2HexDB);
                }

Test it again. Everything works. Wow, that was an easy debug. But next time you'll have to do it by yourself. Don't panic, though. If you can't debug your code you can post your problem at news.falch.net, pilot.programmer.gcc. But try to debug the problem yourself first, because every time you post a stupid question your credibility goes down. When your credibility hits zero, othersome programmers will put your name on their kill lists. If you post too many inane questions, you will be on everybody's kill list. Also, notice that news.falch.net keeps all the messages since pilot.programmer.gcc was first started. So you should download all the messages and search them before asking a question.

When everything works, and if you want to distribute your application, then it's time to connect to register the CreatorID "TxHx" at Palm Computing's on-line creatorID registration site. If someone has already claimed your creatorID, then you have to choose an unused creator ID and make the appropriate changes to tex2hex.h and Makefile. Then you can distribute your application. If you want, as an exercise you could go to the CreatorID registration site and find out if the creator ID TxHx is registered.

6.9 Debugging with the Error API

Maybe later I'll write some instructions about how to use gdb with prc-tools. But for now, I'll just offer my old techniques for in-line debugging code. I usually keep these four lines commented out at the end of every application, then I can copy them wherever I need them.

                //      char  debugstring1[20], debugstring2[20];
                //      StrIToA(debugstring1, **111**);
                //      StrIToA(debugstring2, **222**);
                //      ErrDisplayFileLineMsg (debugstring1, **333**, debugstring2);

They can be used to enter breakpoints in your code. The first line has to be copied to the top of your source code where you declare global variables. The next three lines go where you want the breakpoint. You can display three integers. Replace **nnn** with the variable names of the integers you want displayed. Another useful call is ErrFatalDisplayIf. The prototype is:

                void ErrFatalDisplayIf (Boolean condition, char* message)

The boolean test is useful for entering conditional breakpoints. Suppose you don't know exactly why an application is misbehaving, but you do know that bad things will happen if the integer "MyNumber" exceeds 250. Then you can use this code snippet to display the value of "MyNumber" if it exceeds 250:

                StrIToA(debugstring1, MyNumber);
                ErrFatalDisplayIf( MyNumber > 250, debugstring1);

That's the end of this tutorial. If you find out something new, remember to post it at news.massena.com.

Andrew Howlett
Owen Sound, Canada
March 2001


GNU GENERAL PUBLIC LICENSE

Version 2, June 1991

                Copyright (C) 1989, 1991 Free Software Foundation, Inc.  
                59 Temple Place - Suite 330, Boston, MA  02111-1307, USA

                Everyone is permitted to copy and distribute verbatim copies
                of this license document, but changing it is not allowed.

Preamble

The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.

Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.

Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.

The precise terms and conditions for copying, distribution and modification follow.

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  1. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".

    Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.

  2. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.

    You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.

  3. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:

    1. You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.

    2. You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.

    3. If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)

    These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.

    Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.

    In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.

  4. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:

    1. Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

    2. Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

    3. Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)

    The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.

    If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.

  5. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

  6. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.

  7. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.

  8. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.

    If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.

    It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.

    This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.

  9. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.

  10. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

    Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.

  11. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
NO WARRANTY

BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.


<< Using Floating Point Math Book and Software Reviews >>
Last modified on 29 March 2003 at 18:43 UTC-7 Please email me if you have any corrections.
< Go to the main FAQ page << Go to the Home Page