Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

Extending the PalmOS


Nov99: Extending the PalmOS

Greg is a director of software development for Bachmann Software and a contributor to Palm Programming, by Glenn Bachmann (Sams Publishing, Macmillan Computer Publishing, 1999). Greg can be contacted at [email protected] or at http://www.bachmannsoftware.com/.


Packaging code into shared libraries provides many benefits. For one thing, libraries provide a mechanism for reusing your code among two or more applications, thereby reducing development time. For another, multiple applications can use the same library, reducing both the installed and in-memory footprint. The lifetime of a shared library is not strictly tied to that of its clients, so its state can be maintained across applications. Finally, architecturally, shared libraries reduce coupling between modules, which results in more stable and maintainable code.

Although there is limited support from the CodeWarrior tools, a handful of articles and white papers, and one example on the Palm web site, information on shared libraries for the Palm Computing Platform is still hard to come by. Consequently, in this article, I'll examine the Palm shared library model and present a shared library (available electronically; see "Resource Center," page 5) that you can use.

The Palm Shared Library Model

A shared library is a special type of database that provides services to clients; these clients may be either Palm applications or other shared libraries. These clients refer to the shared library by its reference number, and invoke functions published by the library.

From the client side, use of a shared library is straightforward. The client loads the shared library into memory using SysLibLoad. If the library has already been loaded, this function will fail -- the client should then call SysLibFind to locate the loaded library. System libraries, such as SerialMgr and IrMgr, are always loaded, so clients of these libraries can skip the load step. Custom shared libraries are not initially loaded, and thus must be loaded by the client.

On success, SysLibLoad and SysLibFind both return a library reference number, which must be used for all subsequent calls to the shared library's functions. The convention is that the first function to be called must be the Open function. Then the client may call the rest of the library's API.

When the client is finished with the shared library, it calls the library's Close function. It may also need to unload the library using SysLibRemove. It is recommended that Close should return success if it can be unloaded, or an error if it still has active clients.

In conventional Palm development scenarios, functions are linked in as you would expect with any other platform. It is when you invoke functions defined in shared or system libraries that things get interesting.

If you look at any system header, you will notice that every function declaration is followed by the SYS_TRAP macro. The argument of this macro is the function trap identifier. This declaration syntax causes the function's arguments to be placed on the stack and then invokes the system trap dispatcher. This dispatcher maps the trap identifier to a specific function call.

The trap identifiers for system libraries are unique throughout the operating system. Shared library function trap identifiers are unique only within the library. As a shared library developer, you have to define the trap identifiers for your library, and implement the mechanism whereby traps are mapped to function calls.

The system provides three services for shared libraries:

  • The system trap dispatcher dispatches calls by library reference number to the appropriate library. When a client invokes a function by a trap identifier, it generates an exception. The trap dispatcher handles this exception by putting the address of the appropriate function onto the stack. When the exception returns, program execution is transferred to this address.
  • For each shared library open in the system, there is a corresponding entry in the system library table. This entry holds both the library's dispatch table and the memory allocated by the library for its global memory.

  • The library reference number is assigned by the system when the library is loaded for the first time. The client retrieves this number either from SysLibLoad, which loads the library, or SysLibFind if the library is already loaded. This reference number must be the first parameter to every shared library call; the system trap dispatcher uses it to locate the correct library.

The shared library provides the mechanism by which trap identifiers are translated into function calls. There are several parts to this. There is the definition of the trap identifiers and the associated function declarations. The installation entry point is invoked when the library is loaded. It informs the system of the library's dispatch table and initializes memory required by the library.

The installation entry point is called "__Startup__," and must be the first function in the library's link order. It returns 0 for success, or a negative error code. SysLibLoad returns the error code to the client application. The system passes the library's entry in the system library table as a parameter to this function; the library sets the dispatch table attribute of this structure.

The library dispatch table contains a list of all routines in the library. This is a lookup table used by the system trap dispatcher.

Implementing a Shared Library

There are three components in a shared library: the API declarations, API implementation, and dispatch table.

The shared library publishes a header file describing the API to clients. Function declarations specify how the client invokes the API, including parameter information and return types. Trap identifiers allow the system to invoke the function using the trap mechanism on which the PalmOS relies. Result codes provide an expected set of errors for which the client should check. Domain-specific structures and constants provide additional information required by the custom portion of the API.

Every shared library must publish four standard functions:

  • The client must call the Open function first. It allows the shared library to initialize any resources it needs. No other API functions can be called prior to this function.
  • The client calls the Close function last. It allows the shared library to release any resources it is holding. Once this function is called, the library is in an invalid state; it must be reopened before it can be used.

  • The operating system calls the Sleep function before the device enters sleep mode. It allows system-level libraries to shut down hardware components to conserve power.

  • The operating system calls the Wake function when the device returns from sleep mode. It allows system-level libraries to reenable any hardware components that were shut down when the device entered sleep mode.

The shared library also publishes domain-specific functions. As a rule, these functions must be invoked after Open and before Close, to make sure the library has valid resources. The one exception is a function that retrieves the API version of the library, which may be invoked before Open to ensure compatibility.

If you examine the PalmOS SDK headers, you'll notice that every function declaration includes the SYS_TRAP macro:

Err SerOpen(UInt refNum, UInt port, ULong baud) SYS_TRAP(sysLibTrapOpen);

For the CodeWarrior compiler, this declaration expands to a Metrowerks extension called "opcode inline" syntax:

Err SerOpen(UInt refNum,UInt port,ULong baud)={m68kTrapInstr+sysDispatch TrapNum,trapNum}

The opcode inline syntax lets you specify the 680x0 opcodes for the function's implementation. When you call an opcode inline function, the compiler replaces the function calls with the specified opcodes. This feature supports calls through the 680x0's "A-Trap" mechanism; it generates the exception, which is handled by the PalmOS's system trap dispatcher.

Invoking a call to a shared library function is a two-step process. For client code, the function declaration expands into a call to the A-Trap mechanism. This mechanism uses the library's reference number to identify the appropriate entry in the system library table; the library's reference number must be the first parameter to any shared library function. Using the trap identifier as an offset into the entry's dispatch table, the appropriate function is called.

If this all seems like a lot of work, remember that the compiler takes care of it; your job is to declare the trap ID enumeration, and to make sure you use the right trap for each function declaration.

The system defines traps for the required Open, Close, Sleep, and Wake functions. You define the rest, starting with sysLibTrapCustom, and incrementing sequentially.

The public API also contains the errors that the functions may return. These are based on the appErrorClass, which is defined in SystemMgr.h. The library functions should not return results that are not either a success or one of the API-defined errors.

In addition to the functional API, there may be structures or constants defined by the library. For example, a printing library might publish a font structure and constants for bold, italic, or underline.

The functions described in the public header are implemented in a standard C file. In this module, the SYS_TRAP macro is disabled so that the function declarations evaluate to standard C declarations.

The Open function is responsible for allocating memory for this information and storing it in the system library table entry for the shared library. Once this is done, it performs any domain-specific initialization.

The Close function is responsible for releasing memory allocated for the library's globals, and removing this value from the system library table entry. Any domain-specific clean up should be done prior to releasing this memory. By convention, this function returns 0 if the library should be removed, or an error code indicating that the library is still in use by other clients.

The Sleep function handles notification from the system that the system is about to shut down. This notification lets system-level libraries shut down hardware components to conserve power.

The Wake function handles notification from the system that the system is about to wake up again. This notification lets system-level libraries reenable hardware components that were shut down when the system went to sleep.

Because these functions are invoked by system interrupts, they may only use interrupt-safe system services and must not take a long time.

Domain-specific API functions depend on the global data allocated in the Open function, and so should only be invoked between Open and Close. These functions follow a standard pattern. First, they retrieve the library's global data. Then, they perform the domain-specific task they provide. Finally, they release the global data.

There are exceptions to this rule. For example, many libraries provide a function to retrieve the API version of the library. Because this function does not rely on any global information, it may be called before the Open or after the Close function. Indeed, it probably should be called prior to Open to ensure compatibility between the client and the library.

The dispatch table implementation is provided by the Install and DispatchTable functions. The system invokes Install as part of SysLibLoad; this function invokes DispatchTable, which returns the address of the dispatch table. This address is stored in the library's entry in the system library table. DispatchTable is coded in assembler and includes the declaration of the dispatch table as well as the code that returns its address.

The first section of the dispatch table is an array of offsets from the beginning of the dispatch table. The first offset points to the library name. This string is stored at the very end of the table. The remaining offsets point to entries in the next section, which is an array of jump instructions: The second offset points to a jump to the Open function; the third, a jump to the Close function; and so on. The library name is stored as a null-terminated string at the end of the jump table.

For example, say that you had a shared library named "FooLibrary," with only one function -- Foo. The dispatch table would look like Table 1. The system executes library traps using this table. Using the trap identifier as an index into the first section of the dispatch table, it retrieves the offset to the jump instruction corresponding to the trap. The system moves to this offset in the dispatch table and executes the next instruction. This instruction is a jump to the appropriate shared library function.

As Figure 1 illustrates, this is conceptually no different from the implementation of virtual tables under C++. The client invokes a function that is mapped to an offset in a function table. Using this offset, the compiler retrieves the address of the function to execute. The main difference is that in C++, the compiler takes care of all this plumbing transparently; with shared libraries, we must do it ourselves.

This may seem more complex than it actually is. The dispatch table in the sample library I provide here (available electronically) can be copied and does work. There are a few things to remember:

  • The order of the trap identifiers as declared in the public header must match the order of the respective functions in the jump table.
  • There is a macro defined in the sample, numTraps. This must be equal to the number of public functions declared.

  • There must be one entry in the dispatch table for each public function.

  • The standard functions must appear first in the jump table and in the correct order: Open, Close, Sleep, and Wake.

If your library is crashing, or the wrong functions are being invoked, these are the first things to check.

And just in case shared libraries weren't daunting enough, there are some rules and caveats you should be aware of:

  • Do not use static and global values. Shared libraries are stored on the device as resource databases. The memory occupied by the library is, therefore, on the storage heap. By default this memory is protected; it can only be written to with the appropriate DataMgr function. A downstream effect of this is that all global or static values are, effectively, read only.
  • You could use static or global variables in your shared library. But if you do, you need to allocate a writeable data segment for them, and initialize this data segment by hand. When you enter a library function, you need to set the A4 register to point to this data segment. When you exit the function, you need to set it back to its original value. You also need to do this "A4 magic" before calling certain SDK functions, such as MemHandleNew. In general, it's best not to use them at all.

  • Install linkage. A shared library's Install function is actually a macro for the library entry point, __Startup__. The system expects this function to be the first entry point into the library. Therefore, this function must be first in the linkage order. We put it in its own module, and list that module first in the Segments panel of the project.

  • Library resources. Palm application resources are opened with the application at all times. If you want to show a form, or raise an alert, it is a straightforward task. Resources associated with a shared library are not left opened with the library; the library must specifically load its resource database. Consequently, it must unload the database as well.

  • Debugging. Bugs are a fact of life in software development. As a result, so are debuggers. Unfortunately, the CodeWarrior debugger does not currently support tracing into shared library code. This support is rumored to be coming in an upcoming release. Until this support is available, you must embed code in your library to display errors, variable values, and so on. You can use alerts, error messages, or the emulator logging facility. On some projects, we have created a debug message console on the Palm device.

A Shared Library Example

The sample shared library I present here lets the client application store and retrieve a text string. It also provides a function to retrieve the library's API version; this is useful to ensure version compatibility between application and library. The library also implements the four standard shared library functions -- Open, Close, Sleep, and Wake. While the actual functionality is straightforward, this sample provides a framework onto which you can build your own shared libraries.

Summary

Shared libraries are not hard to create. Conceptually, they are function tables that follow specific rules. The bad news is that these rules are not intuitive or apparent to the first-time programmer. The good news is that once you've created your first library, you can use it for a template and stop worrying about these rules. The best news is that you can just use the sample provided with this article.

Used correctly, shared libraries will repay the investment with greater code reuse, increased application stability, and reduced resource requirements. You might think of them as peace of mind, encapsulated.

DDJ


Copyright © 1999, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.