Introducing IOS STDIO
The IOS STDIO shared library provides a set of functions that are mostly compatible with the Posix STDIO interface. These functions forward I/O requests to the I/O Subsystem (IOS) for processing. The STDIO function calls include calls to open and close devices, read and write data, and perform control operations on devices.
In general, the functions in the Palm OS IOS STDIO library are used similarly to those in the standard Posix standard I/O library; the key difference is that the Palm OS versions have the prefix "IOS" added to the function names.
You can learn much more about these functions by reading any good Unix programming book.
Synchronization Issues
The I/O Process, in order to optimize performance, directly accesses the calling process' memory space to read from and write into buffers. Although the calling thread is blocked, other threads in the calling process might access that memory at the same time as the I/O Process, which would cause synchronization problems.
Therefore, if this may be an issue for your application, be sure to use a semaphore or other mutual exclusion device.
Polling STREAMS File Descriptors
Your application's main event loop can reduce its overall impact on the performance of the device by blocking until a user interface event occurs. This can be done using the IOSPoll()
function, similar to the example in Listing 18.1.
Listing 18.1 An example main event loop that blocks until an event occurs
status_t error; EventType event; int32_t eventFd; int32_t fdCount; struct pollfd fdSet[1]; eventFd = EvtGetEventDescriptor(); fdSet[0].fd = (int) eventFd; fdSet[0].events = (short)(POLLIN | POLLHUP); fdCount = 1; do { if ((error = IOSPoll(fdSet, fdCount, 10000, &fdCount)) != errNone) { printf("IOSPoll() failed with error: 0x%08lx\n", error); } EvtGetEvent(&event, 0); if (!SysHandleEvent(&event)) { if (!MenuHandleEvent(0, &event, &error)) { if (!ApplicationHandleEvent(&event)) { FrmDispatchEvent(&event); } } } } while (event.eType != appStopEvent);
This code calls EvtGetEventDescriptor()
to determine the file descriptor for the event queue.
Once that's done, it calls IOSPoll()
to block for 10,000 milliseconds or until message occurs on the event queue's file descriptor. Once an event occurs, it is fetched using EvtGetEvent()
and the event is processed normally.
By blocking on IOSPoll()
, this event loop avoids busy-waiting—the practice of looping constantly, non-stop, executing code that repeatedly checks for pending events. This saves processor time for other tasks.
Using a PollBox to Monitor Multiple File Descriptors
Applications that use multiple file descriptors can simplify their event loops by using a PollBox. A PollBox is a mechanism that automatically handles polling, calling a specified routine each time an event occurs that affects a file descriptor you're monitoring.
Once an application creates a PollBox, it can add and remove file descriptors from the set of file descriptors to monitor right from within its event loop. Each file descriptor has a callback routine associated with it, which is called whenever an event affects the file descriptor.
Creating a PollBox
Creating a PollBox is a simple matter of calling the PbxCreate()
function, as shown in Listing 18.2.
Listing 18.2 Creating a PollBox
#include <PollBox.h> ... PollBox *pbx = PbxCreate();
Destroying a PollBox
When your application is done using the PollBox, it must return resources to the system by calling PbxDestroy()
. This will close all the file descriptors currently in the PollBox and free all memory used by the box. This is demonstrated in Listing 18.3.
Listing 18.3 Destroying a PollBox
PbxDestroy(pbx);
Adding File Descriptors to Monitor
To add a file descriptor to the set of file descriptors being monitored by a PollBox, your application should call the PbxAddFd()
function, as shown in Listing 18.4.
Listing 18.4 Adding a file descriptor to a PollBox
status_t err = PbxAddFd(pbx, fd, eventMask, MyCallbackProc, myContextPtr);
The eventMask
passed into the PbxAddFd()
function is a bitwise OR of one or more of the following values:
- POLLIN
- Set this if your application should be informed when a non-priority message is available for the file descriptor.
- POLLPRI
- Set this if your application should be informed when a high-priority message is available for the file descriptor.
- POLLOUT
- Set this bit if your application should be informed when a message is sent on the file descriptor.
The MyCallbackProc
parameter is a pointer to a callback routine, which will be called whenever any of the desired events occur. It will be called with the myContextPtr
pointer as one of its parameters.
NOTE: If you want to poll without receiving callbacks, you can specify
NULL
for the callback procedure pointer.
You can poll for the existence of user interface events by using the file descriptor returned by EvtGetEventDescriptor()
, although you can't use IOS to read the events. So to handle user interface events, your application can set up a special callback just for handling those, which calls the appropriate Event Manager and other functions to fetch and handle the events:
Listing 18.5 Adding the UI file descriptor to a PollBox
status_t err = PbxAddFd(pbx, EvtGetEventDescriptor(), POLLIN, MyUICallbackProc, NULL);
Removing a File Descriptor from the PollBox
The PbxRemoveFd()
function removes a file descriptor from a PollBox, as shown in Listing 18.6.
Listing 18.6 Removing a file descriptor from a PollBox
PbxRemoveFd(pbx, fd);
Polling for Events using a PollBox
Once you've added all the file descriptors you wish to monitor to the PollBox, you can simply call the PbxPoll()
function in a loop to watch for events, as shown in Listing 18.7. The PbxPoll()
function automatically dispatches events to the appropriate callback handlers, so all you have to do is watch for error conditions, and possibly perform some idle activities.
Listing 18.7 Polling for events
for (;;) { err = PbxPoll( pbx, timeout, &nReady ); if ( err ) { // Some unexpected error occurred. } else if ( nReady == 0 ) { if ( pbx->count == 0 ) { // There are no more file descriptors in the pollbox. } else { // The timer expired before any events occurred. } } else { // Normal case. There are pbx->count > 0 file descriptors in // the pollbox, and nReady of them have events. The callbacks // associated with the ready file descriptors have been called. // If you are working without callbacks, then do something here // using the contents of the pollbox. } }
The call to PbxPoll()
blocks until at least one event is available on at least one file descriptor, or the specified timeout period elapses. The timeout is specified in milliseconds.
NOTE: If you wish the
PbxPoll()
function to return immediately if no events are pending, specify 0 as the timeout. If you don't want it to time out at all, specify -1 instead.
When PbxPoll()
returns, any callbacks that apply have already been called; nReady
contains the number of file descriptors that have events waiting and pbx->count
indicates the total number of file descriptors in the PollBox. If your application isn't using callbacks, you can look at the contents of the pbx
PollBox and perform whatever actions your application needs to.
Polling the Easy Way
As you can probably see, in the typical case, all you need to do is call PbxPoll()
in a loop until your application is ready to quit. For this case, you can use the convenient PbxRun()
function, as demonstrated in Listing 18.8.
Listing 18.8 The easy way to write an event loop
status_t err = PbxRun(pbx); if (err) { // Some unexpected error occurred } else { // There are no fds left in the PollBox; this is a normal exit PbxDestroy(pbx); }
This can literally be your entire event loop. If your application uses a UI event callback on file descriptor 0, that callback can cause the application to exit by simply removing all the file descriptors from the PollBox, which will cause PbxRun()
to exit.
Implementing a PollBox Callback
Your callback procedures must be of type PbxCallback
:
void PbxCallback(PollBox *pbx, struct pollfd *pollFd, void *context);
The first parameter is a pointer to the PollBox itself. The second parameter is a pointer to the pollfd
structure associated with the file descriptor on which the event has occurred. The fields your callback can access within this structure are shown in Table 18.1.
The final parameter is a pointer to the context variable specified when your application called PbxAddFd()
to add the file descriptor to the PollBox.
In Listing 18.9, we see an example of a callback procedure.
Listing 18.9 Sample callback procedure
void MyCallback( PollBox* pbx, struct pollfd *pollFd, void* context ); { MyContext* ctx = (MyContext*)context; status_t err = 0; if ( pollFd->revents & POLLIN ) { IOSGetMsg( pollFd->fd, ctx->ctlBuf, ctx->dataBuf, 0, &err ); } if ( err || (pollFd->revents & (POLLERR|POLLHUP)) ) { PbxRemoveFd( pbx, pollFd->fd ); IOSClose( pollFd->fd ); return; } // Handle the event that has been read into the ctl and data buffers. ... }
If the message received is a POLLIN
event, the callback calls the IOSGetmsg()
function to fetch the message. In this case, the context variable is a structure into which the data gets copied.
If an an error occurred on the file descriptor, or it's been hung up, we remove the file descriptor from the PollBox and close it, then return to the caller.
Other processing can be handled here as needed. For example, if your application is using IOS STDIO calls to communicate with a Bluetooth device, you may receive events from the Bluetooth Management Entity which need to be handled.