C++ Shared Libraries With Multilink.

I've been working with Colin Lawrence getting shared libraries written in C++ using virtual functions in multiple code segments to function under Multilink. I should probably state at this point that Multilink is a tool to link Palm programs into multiple code segments. It is truly amazing, and I wish that the prc-tools people would incorporate the functionality into their product, since it makes many things possible which aren't by any other means. Such things as C++ virtual methods in shared libraries, crossing multi segment boundries. So, now that I've told you why you really need to use Multilink, let me tell you how to fix the bugs we've run across. (Note: If you have problems with Multilink, please let me know and I'll try to post the solutions here.)

Locked records in closed database

The most recent problem we ran into was a error from the emulator complaining about locked records in an closed database. We had no idea what this could be, and after tracing the lock count through the use of the "DmGetDatabaseLockState" function, we discovered that the runtime function multilink inserts at the start of the code had a companion function "void _shlib_end( void* globals );" that needed to be called at the end of the code. Unfortunately no-one was calling it. So we tried calling it in our "Err PalmShLibClose( UInt16 _refnum, UInt16 *_refCount)" method, but that failed for some reason.

A little later, I've found out why it failed. The code in _shlib_end looks like the following:

void
_shlib_end(void* globals) /* entryP-<globalsP */
{
    unsigned** jmptables;
    void* save_a4;
    void* save_a5;

    save_a4 = reg_a4;
    save_a5 = reg_a5;
    reg_a5 = globals;
    reg_a4 = globals;
    asm volatile ("sub.l #edata,%a4");
    globals = reg_a4;
    jmptables = MultilinkSegmentJmpTables;
    reg_a4 = save_a4;
    reg_a5 = save_a5;
    MultilinkUnloadCodeSegments(MULTILINK_APPL_ID, jmptables, MULTILINK_NJMPTABLES);
    MemPtrFree( globals );
}

However, when it gets compiled, the line "globals = reg_a4" seems to get optimized out (probably because the compiler doesn't understand that the previous line (asm volatile ("sub.l #edata,%a4");) modifies the value of the reg_a4 variable, and so the line would be a no-op. Therefore, a little later, when we get to the line "MemPtrFree( globals);", we aren't using the right globals, and so we get a Fatal Exception. I've come up with a bad "fix" for this, by adding the line "asm volatile ("move.l %a4,%d3");" after the line "asm volatile ("sub.l #edata,%a4");", to get a full _shlib_end of:

void
_shlib_end(void* globals) /* entryP-<globalsP */
{
    unsigned** jmptables;
    void* save_a4;
    void* save_a5;

    save_a4 = reg_a4;
    save_a5 = reg_a5;
    reg_a5 = globals;
    reg_a4 = globals;
    asm volatile ("sub.l #edata,%a4");
    asm volatile ("move.l %a4,%d3");
    globals = reg_a4;
    jmptables = MultilinkSegmentJmpTables;
    reg_a4 = save_a4;
    reg_a5 = save_a5;
    MultilinkUnloadCodeSegments(MULTILINK_APPL_ID, jmptables, MULTILINK_NJMPTABLES);
    MemPtrFree( globals );
}

which seems to work. A better fix would be to somehow declare the reg_a4 and reg_a5 variables as volatile or somethimg similar, so that they don't get optimized out. I should probably mention at this point that the command I used to recompile the MultiLink library was "m68k-palmos-gcc -c -Os -fno-exceptions -fno-rtti -Wall -Werror -DMULTILINK_NEW_HEADERS -o ../lib/m68k-palmos-multilinkslcrt0.o shlibcrt0.o".

Calling across segments

The problem that caused me to create this page was trying to call across segments from within a shared library. The first call into the shared library worked just fine, as long as the first call was located in the first segment, but any calls outside of that segment failed miserably. After a lot of tracking, we determined that it was because the globals, although being set up for us before our ShlibMain function, weren't being set up for us before any of our other entry points. (To be fair, there's no place for Multilink to set them up, since the PalmOS just jumps straight into our code.) So we need to set them up ourselves. The code to do that looks like this:

/**
 * Keep track of the application's a5 value.
 **/
extern MemPtr     app_a5;
register MemPtr   reg_a5 asm("%a5");

/**
 * Entry macro at the start start of all shared library entry points.
 **/
#define PALM_SHLIB_EP_INIT                                  \
    SysLibTblEntryPtr   entryP = SysLibTblEntry( _refnum ); \
    MemPtr              shlib_a5 = entryP->globalsP;        \
    MemPtr              temp_a5 = reg_a5;                   \
    reg_a5 = shlib_a5;                                      \
    app_a5 = temp_a5

/**
 * Exit macro at the end of all shared library entry
 * points.
 **/
#define PALM_SHLIB_EP_DEINIT                                \
    reg_a5 = temp_a5

/**
 * Entry macro at the start of all shared
 * library callbacks.
 **/
#define PALM_SHLIB_CB_INIT                                  \
    MemPtr          temp_a5 = reg_a5;                       \
    reg_a5 = app_a5

/**
 * Exit macro at the end of all shared
 * library callbacks.
 **/
#define PALM_SHLIB_CB_DEINIT                                \
    reg_a5 = temp_a5

#endif

Err MyShLibFunction( UInt16 refnum )
{
    PALM_SHLIB_EP_INIT;

    // put the body of the entry point here.

    PALM_SHLIB_EP_DEINIT;
}

I recommend making macros to do those tasks, since they'll be the same for every entry point, and you'll need to call them for all your entry points.

Now that I think about it a little more, I realize that this will mess you up if you try to call back to your application, so all of your application callbacks should probably go through another function that undoes the a5 modification before calling back to your app. This looks like it's going to get messy, but that's the price for living on the bleeding edge.