Skip Headers
Oracle® Database Lite Developer's Guide
10g (10.0.0)
Part No. B13788-01
  Go To Table Of Contents
Contents
Go To Index
Index

Previous
Previous
Next
Next
 

8 Palm Shared Library Manager (PSLM)

This document discusses the Palm Shared Library Manager (PSLM). It includes the following topics:

8.1 Overview

PalmOS provides built-in facilities to load and use shared libraries. However, native support has severe limitations that make it virtually impossible to port existing code of significant size. The size of native shared libraries is limited to 32-64K, depending on code structure. In addition, global variables and many important C++ features, such as virtual functions and exceptions, can not be used. There are a couple of techniques to overcome these limitations, such as PRC-Tools glib support and CodeWarrior 9 "expanded mode". However, none of them succeeds in making a shared library as easy to develop as an application.

Oracle's solution, PSLM, allows one to build a shared library as a regular Palm application. It is possible to use multiple segments, C++ virtual tables and exceptions and global variables, even ones with constructors and destructors. PSLM doesn't require any support from the OS and only uses limited compiler support (a patch the the publicly available sources of the CW runtime library). It does require some extra code to be written, but the existing code of the application and an existing static library does not need to be modified.

8.2 Trying out PSLM

There is a sample using the framework in &fmv657;\Mobile\Sdk. After you do "Build All" on the project file, there will be two PRC files in that directory - SampleLibrary.prc and SampleApp.prc. Install Oracle Database Lite Runtime using olSetup.If you touch the "PSLM Sample" icon on the Palm now, it will just print a couple of message boxes and exit. What happens inside is considerably more interesting. SampleLibrary.prc is a PSLM shared library that has global variables and even makes use of another shared library, the ANSI C library that comes with Oracle Database Lite. Look at Sample.cpp for the implementation of the shared library and AppStart of Starter.cpp to see how it is called.

8.3 Writing a PSLM Library

A PSLM library is a C++ class that extends PSLibrary. It exposes all it's functionality as virtual functions. Here is how the SampleLibrary class looks like.

class SampleLibrary : public PSLibrary {
protected:
        /*
         * Overloaded PSLM functions.
         */
        virtual pslmError startup();
        virtual void cleanup(bool isFinal);
public:
        /**
         * Increment an internal counter by a specified value and then
         * return a result as a string (global buffer that will be reused
         * on next call).
         */
        virtual const char *getCounter(int incVal);
        /**
         * Reset a counter to the specified value
         */
        virtual void setCounter(int newVal);
};

This library defines two APIs - getCounter and setCounter. The remaining two virtual methods - startup and cleanup are called by PSLM itself and are very important. Basically, a PSLM library must use it's startup and cleanup methods rather than constructor and destructor to manage it's state. Also, it must be able to handle another startup after cleanup is done. This is one of the few artifacts caused by lack of the compiler/OS support. The constructor of a shared library is called normally, but must not use PSLM itself or even call the methods of it's own object. The destructor is actually not called at all. Note that it's perfectly Ok to have a pointer to another object that is constructed during startup and deleted during cleanup.

startup() method is the place to do initialization, including loading additional libraries. Let's look at the startup method of SampleLibrary:

pslmError SampleLibrary :: startup() {
     PSLibContext ps(this); // Establish access to globals
     cleanOrder = 5; // Unload before libraries with clean order 4 and below on exit
     return psCLibrary.open();}
 

The first line of this method is the most important, but we'll come back to it in a moment. The second line lets you specified the order in which the libraries will be unloaded on program exit. If B depends on A, A should have a smaller cleanOrder. The last line loads the C library and returns success or error of that application.

startup() function can fail by returning a value rather than 0. In this case, the library being loaded is removed and the error is returned to the caller.

cleanup() method should free all the memory, closing network connections, unload dependencies and so on. However, if the isFinal argument is set to true it must not unload other libraries because the program is exiting and it will interfere with PSLM closing libraries correctly. Here is the cleanup method of SampleLibrary:

void SampleLibrary :: cleanup(bool isFinal) {
        PSLibContext ps(this);
        if (!isFinal)
                psCLibrary.close();}
 

Let's look at a regular method of SampleLibrary, together with the variables it's using:

class SampleBuf {
        char *buf;
public:
        SampleBuf(int size) : buf(new char[size]) {}
        ~SampleBuf() { delete[] buf; }
        operator char *() { return buf; }
};
SampleBuf myBuf(128);
int counter;
const char * SampleLibrary :: getCounter(int incVal) {
        PSLibContext ps(this);
        counter += incVal;
        sprintf(myBuf, "%d", counter); // Use LibC - another shared library
        return myBuf;}
 

Note that this method uses two global variables and one of them even has a constructor and a destructor. This is Ok, although global constructors and destructors can only do simple things. They shouldn't call PSLM and shouldn't use other shared libraries unless you are sure they are always loaded.

Look at the highlighted line, PSLibContext ps(this). Every exposed virtual method of a PSLM shared library must start with this line. The constuctor of a PSLibContext sets the context to that of the library passed as an argument. This is what enables a library to use it's global variables, virtual functions and exceptions. If you omit this line, you will get crashes, call random places in memory or even introduce hard-to-track memory corruption. Also, remember to delete the context before calling any callback in the main program and re-create it afterwards. If you get this right, you have mastered PSLM.

The last piece to consider is the library's PilotMain, which is very simple:

UInt32 PilotMain( UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags) {
        if (cmd == psLibLaunchCode)
                psLibInit(cmdPBP, new SampleLibrary()); // Never returns
        return 0;}
 

psLibLaunchCode is what PilotMain gets when the library is open. psLibInit takes a pointer to a subclass of PSLibrary. It never returns directly. Instead, it returns the control back to the program that opened the library.

8.4 Building a Shared Library Project

The following illustration, Figure 8-1, shows the CodeWarrior project for SampleLibrary:

Figure 8-1 The CodeWarrior Project for SampleLibrary

The CodeWarrior Project
Description of the illustration sampleSeg.gif

First, note that the usual CodeWarrior startup library (PalmOSRuntime_2i_A5.lib) has been replaced with cwStartup.lib from our distribution. You can use this patched startup library for any project, but it must be used to build a PSLM shared library. I tried to avoid requiring a custom runtime, but recent Metrowerks changes and especially PalmOS5 support made a patch necessary. Use cwStartup4B.lib if you are building a project using 4-byte integers. Finally, you might want to patch your own runtime if you are using a version of CodeWarrior newer than 8.3.

Both cwStartup.lib and pslm_lib.lib must be in the first segment of the application, otherwise it will crash when loaded. Other files can be in any number of segments. In this case, I included SampleLib.cpp and libc_stub.lib (which is a static helper for ANSI C shared libraries. Another important point is that a pslm library must not contain any UI resources, because they will be used instead the corresponding ones of the application. Be sure to exclude your Starter.rsrc from shared library targets.

The following illustration, Figure 8-2, shows the "PalmRez Post Linker" section of the SampleLibrary project:

Figure 8-2 The PalmRez Post Linker Section

This graphic displays the PalmRez Post Linker section.

A PSLM library is linked as an application, but we don't want it to show up as a Launcher icon. Change type and creator of the .PRC file to PSLM in order to hide it. Also, set the database name to whatever name you are planing to use when you load the library.

8.5 Calling a PSLM Library from Your Application

To call a shared library from your application, first use a template class to declare a proxy object for that library:

PSLibObject<SampleLibrary> sampleLib("SampleLibrary");

The quoted SampleLibrary is the Palm Database Name you specified in the project settings, while the quoteless one if the name of the class that exposes the APIs. You can make these two different if you want. You can load the library using:

sampleLib.open(true);

In the above statement, "true" argument means that a fatal exception will be displayed on the device if the library can not be open. For a nicer error handling, and especially if the library is optional, just do sampleLib.open() without arguments and process the returned Err value if not errNone.

Once opened, you can pretend that sampleLib is a SampleLibrary * and write code such as the following:

StrPrintF(buf, "Value after increment by 3: %s", sampleLib->getCounter(3));

This is actually not very convenient if you originally just had a static library that defined getCounter. Remedy this problem with preprocessor directives like this one:

#define getCounter (sampleLib->getCounter)
#define setCounter (sampleLib->setCounter)

At this point, you can use getCounter(3), same as with a static library.

Should you call sampleLib.close() to unload SampleLibrary? You can if you need to free the resources immediately. open() and close() keep a use count and only unload when it drops to 0. Note though that all the libraries will be automatically unloaded (ordered by increasing cleanOrder) when the program exits.

If you use sampleLib in more than one file in your program, you should load it in your AppStart and then declare it as follows in other files:

extern PSLibObject<SampleLibrary> sampleLib;

In the other extreme, you can have a function that loads a plugin, lets it do some work and then unloads it before returning. In this case, you can declare a local variable of type PSLibObject and even pass a dynamic argument as a library name to support user-defined plugins.

The last technique is linking statically to the code that was intended to be a shared library. If you add SampleLib.cpp to the project (comment out it's tiny PilotMain), you can do sampleLib.init() before open. This will call a statically linked default constructor of the template argument and then register the object as a fake shared library. One interesting result is if MAIN loads LIB1 statically and LIB2 dynamically and then LIB2 tries to load LIB1, it will get a static copy embedded in main and the dynamic LIB1 doesn't need to be installed on a device. This allows the main program to determine exactly which components are statically linked.

8.6 Building an Application Using PSLM

An application using PSLM must be linked with pslm_app.lib and it must reside in the first segment. Although this example uses cwStartup.lib, applications can use a regular runtime libraries and only shared libraries need a patched one. Figure 8-3 shows a PSLM sample application.

Figure 8-3 PSLM Sample Application

This graphic displays a sample PALM application.

8.7 Exceptions Across Modules

You are free to use exceptions inside the shared library, as long as they are also caught inside. Unfortunately, its currently not possible to throw an exception in a library and catch it in the main program. What you want to do, is catch the exception at the top level API method and store it as a private field in the PSLibrary subclass. Then add a non-virtual method that checks that field and re-throws an exception. Basically, non-virtual methods are always static. If you use them both in the library and it's caller, you must link them with both. For this case, it's the easiest to use an inline method for re-throwing the exception.

8.8 Cloaked Shared Libraries

Certain C++ features, such as global variables and exception handling, allocate large amounts of dynamic heap when the program is loaded into memory. PSLM has a feature that allows allocating a shared library's data segment in storage heap instead. To use this feature, add one more argument to the PSLibObject template:

PSLibObject<SampleLibrary, true> sampleLib("SampleLibrary");

Internally, this will cause PSLibContext to call MemSemaphoreReserve(true) in the constructor and MemSemaphoreRelease(true) in the destructor to temporarily un-protect storage heap while a method of the cloaked library is executing. In some cases, for example if a shared library returns a pointer to its global variable to the caller, you may need to do it yourself to modify the data. You can declare a variable of PMLock class on the stack to unprotect the storage heap for the duration of its scope.

Note that you can not get input from the user while the memory semahore is locked. Anything that calls EvtGetEvent directly or through another system call will hang. Therefore, cloaked libraries are only suitable for tasks that don't require user's input.

8.9 Patching the CodeWarrior Runtime

PSLM requires a patched version of CodeWarrior runtime libraries to link a shared library. The Oracle Database Lite build includes cwStartup.lib and cwStartup4B.lib, which are pre-patched versions of the runtime libraries that come with CodeWarrior 8.3. If you want to make your own patched runtime, you need to modify PalmOS_Startup.cpp.

This section is much more difficult than the rest of the document. You need some experience reading system-level code and applying other people's patches to follow it. Otherwise, you may want to stick with our pre-patched version or ask someone with the experience for help.

Let's start with a unified diff generated for CodeWarrior 8.3:

--- PalmOS_Startup_old.cpp      2002-07-19 18:41:26.000000000 -0700
+++ PalmOS_Startup.cpp  2002-09-08 15:51:29.000000000 -0700
@@ -396,6 +396,12 @@ 
 
 #endif /* SUPPORT_A4_CONST_GLOBALS */ 
 
+SysAppInfoPtr pslmGetAppInfo(
+       SysAppInfoPtr *rootAppPP, 
+       SysAppInfoPtr *actionCodeAppPP)
+       SYS_TRAP(sysTrapSysGetAppInfo);+
 +
 
 /*
  *     Main entry point for applications
  */
@@ -408,8 +414,9 @@
        SysAppInfoPtr   appInfoP;
        Int16                   abort_result = 0;
        Boolean                 globals_are_setup;
-       _CW_Features    features;
-#if SUPPORT_A4_CONST_GLOBALS   
+       _CW_Features    lFeatures;
+       static _CW_Features gFeatures;
+ #if SUPPORT_A4_CONST_GLOBALS  
        UInt32                  originalA4 = GetA4();
        MemPtr                  originalExtraP; 
 
@@ -435,18 +442,31 @@
     }
 #endif
        +
 
        /*
         *      Call the standard system code for allocating and initializing globals and
         *      setting up A5, and getting the command line arguments
         */
-       err = SysAppStartup(&appInfoP, &prevGlobalsP, &globalsP);
+       // PSLM - try to find and execute custom startup code
+#define psLibLaunchCode ((UInt16)0xC001)
+       typedef Err (*appStartup)(SysAppInfoPtr* appInfoPP, MemPtr* prevGlobalsPtrP, 
+                                                       MemPtr* globalsPtrP);
+       appStartup start = NULL;
+       SysAppInfoPtr uiP, curP;
+       appInfoP = pslmGetAppInfo(&uiP, &curP);
+       if (appInfoP->cmd == psLibLaunchCode)
+               start = (appStartup)appInfoP->extraP;
+       if (start)
+               err = start(&appInfoP, &prevGlobalsP, &globalsP);
+       else
+               err = SysAppStartup(&appInfoP, &prevGlobalsP, &globalsP);
        if (err) {
                ErrDisplay("Error launching application");
                return 0;
        } 
 
        globals_are_setup = (appInfoP->launchFlags & sysAppLaunchFlagNewGlobals) != 0;-
 
+       _CW_Features &features = globals_are_setup ? gFeatures : lFeatures;
        /* initialize runtime globals */
 #if SUPPORT_A4_CONST_GLOBALS   
        originalExtraP = appInfoP->extraP;

If you have a similar version of PalmOS_Startup.cpp, you can place this diff into a patch program. But a patch will fail if Metrowerks made a lot of code changes. Let me walk you through the changes so that you can still make a functionally equivalent patch.

First, we need to declare a prototype of a PalmOS system function that is not declared in regular Palm SDK. Put the prototype just before __Startup__:

SysAppInfoPtr pslmGetAppInfo(
        SysAppInfoPtr *rootAppPP, 
        SysAppInfoPtr *actionCodeAppPP)
        SYS_TRAP(sysTrapSysGetAppInfo);
/*
 *      Main entry point for applications
 */
extern "C" UInt32 __Startup__(void)

Next, 8.3 version of __Startup__ declares a local variable of type _CW_Features and then stores it in a global pointer that is used elsewhere. This is not good for PSLM, because it will continue calling functions in a shared library after __Startup__ is removed from the stack (by a longjmp in psLibInit). Find the declaration of the variable:

_CW_Features features;

Instead we need to declare both a global version (for PSLM) and a local version (for a sublaunch without access to globals in other projects):

_CW_Features lFeatures;
static _CW_Features gFeatures;

Now, find this line right after the call to SysAppStartup:

globals_are_setup = (appInfoP->launchFlags & sysAppLaunchFlagNewGlobals) != 0;

At this point, we know if the program has global access and if it uses a correct version of features:

globals_are_setup = (appInfoP->launchFlags & sysAppLaunchFlagNewGlobals) != 0;
_CW_Features &features = globals_are_setup ? gFeatures : lFeatures;

Consider the following call:

err = SysAppStartup(&appInfoP, &prevGlobalsP, &globalsP);

The following code shows what the call should be turned into:

#define psLibLaunchCode ((UInt16)0xC001)
        typedef Err (*appStartup)(SysAppInfoPtr* appInfoPP, MemPtr* prevGlobalsPtrP, MemPtr* globalsPtrP);

        appStartup start = NULL;
        SysAppInfoPtr uiP, curP;
        appInfoP = pslmGetAppInfo(&uiP, &curP);
        if (appInfoP->cmd == psLibLaunchCode)
                start = (appStartup)appInfoP->extraP;
        if (start)
                err = start(&appInfoP, &prevGlobalsP, &globalsP);
        else
                err = SysAppStartup(&appInfoP, &prevGlobalsP, &globalsP);

SysAppStartup initializes global variables and multiple code segments for normally loaded applications. PSLM libraries are loaded somewhat abnormally and, in PalmOS 5, SysAppStartup can no longer initialize them correctly without messing up the calling program. An equivalent process is now performed by the code inside PSLM and we must modify CodeWarrior runtime to call this custom function for a shared library launch code. To save space, the internal function only does the same work as PalmOS 1.0, so do npt disable the support for old devices in build options.