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.)
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
".
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.