This is a package which provides an easy method for an addin to call routines in another addin. This simplifies splitting code across multiple addins or creating a shareable library of utility functions.
In addition to exporting functions, addins can export other addresses (e.g. of text or pictures) or 16-bit or 32-bit values (e.g. constants).
The exporting addin is called the server library and the importing addin is called the client.
The core function is to maintain a database indexed by library name and entry point name and containing the bank address and virtual address (assuming the bank is mapped at 0x8000) of the exported function/address. Routines are provided to add new entries to the database and to read values from the database.
There are some additional capabilities designed to make life easier:
The package has been designed to work with the Rexdx kit (and has only been tested with that kit).
There is little the package can do to detect that someone has deleted an addin which has registered. In that case, clients of that library may attempt to call into random data/code!
This is still in development and future versions are likely to change the database format and, possibly, the API. If you are making serious use of this package please let me know so I can let you know of planned changes.
To be written. Read the comments in the code.
Clients and servers must both include dynlink.h and link with dynlink.lib.
dynlink.lib should be put in ../../lib/clibs
and linked using -ldynlink
.
Server libraries must export functions or values using the routines:
dl_export_function
-- used for functions that will be called using dl_library_call
(see below)
dl_export_address
-- used for other addresses (text, pictures, etc.)
dl_export_value_16
-- used for 16-bit numeric values
dl_export_value_32
-- used for 32-bit numeric values
Functions exported using dl_export_function
must be called by the client using
dl_library_call
.
They can have any prototype and return value.
Any additional arguments to dl_library_call
beyond the libname
and
func
will be passed to the called routine.
The return value wil become the return value of dl_library_call
.
If the server library is exporting a number of functions it may be more efficient (in code size)
to use the _fatal forms of the routines (e.g. dl_export_function_fatal
). These versions
never return an error (so the return value does not have to be checked) but will display an error
message on the users screen and terminate the add-in if an error occurs.
The _fatal versions are used automatically if DLFATAL
is defined when the dynlink.h
file is included.
However, note that using these routines means your add-in will also include some additional dl__
routines and DsPrintf and some routines that requires. If you are not already using DsPrintf
in your add-in the additional code may be more than the error checking code required for the
standard versions of the export routines.
In order to keep code space requirements to a minimum, server libraries can call the dl_export...
routines
with no additional preparation.
However, if code space in the server library permits, three additional routines can be called in order to
increase robustness.
dl_database_create
-- this routine creates the database if it does not already exist.
The dl_export...
routines assume the database exists and will return an error if it does not.
If the library server chooses not to call dl_database_create
, the installation process must make sure
the database is created in some other way (e.g.by using DLmaint or by first executing another add-in that
does call dl_database_create
).
Note: it is safe to call this routine if the database already exists -- the caller does not have to check whether the
database needs to be created.
dl_version_check
-- this routine checks that the (existing) database version is correct.
Note: you do not need to call this routine if you call dl_database_create
,
which includes the check automatically.
However, if you choose not to call dl_database_create
, this check is not performed.
dl_database_init
-- this routine removes all existing entries for the library.
It is recommended that the library server calls this routine after dl_database_create
or dl_version_check
(if used) and before
exporting any values. This means that if a previous version of the server was registered, possibly with different
entry point names, these are all deleted, avoiding clients calling into random locations in the Flash RAM.
None of these calls are required but, if space permits, they make the linking process more reliable.
Unfortunately there is little the dynlink package can do about servers which are deleted without removing their registry entries. If the user deletes the server library addin, the client will retrieve stale information and will probably end up calling into random parts of Flash RAM.
The basic client routines are:
dl_library_call
-- call an exported function
dl_import_address
-- read bank address and virtual address
dl_import_value_16
-- read 16-bit value
dl_import_value_32
-- read 32-bit value
dl_library_call
allows the client to call a function in a library
(exported using dl_export_function
),
passing in a list of parameters and
receiving a 16-bit or 32-bit returned value, without worrying about addresses, banks, FarCalls, etc.
NOTE: long (32-bit) parameters must be passed to dl_library_call as two 16-bit parameters,
high word first.
For example, if an exported routine was declared as:
int test_routine(long value);
It would be called using:
dl_library_call("LIBRARY","TEST_ROUTINE", (unsigned short)(value>>16), (unsigned short)(value));
If a library routine is to be called many times, dl_library_lookup
allows the lookup to happen once and then
dl_library_call_fast
can be used to call the routine without looking it up again.
Like the server case, robustness is improved by having the client call dl_version_check
before
calling any other routines. This is not required but it can detect version mismatch problems.
Unfortunately there is little the dynlink package can do about servers which are deleted without removing their registry entries. If the user deletes the server library addin, the client will retrieve stale information and will probably end up calling into random parts of Flash RAM.
When creating a library, it is often convenient to provide your caller with a library of stub routines which they call (with a documented API) amd which hide the calls to dl_library_call. This is not required and is purely optional.
One advantage of providing stub routines is that they can deal with any differences in the environment of the caller and the library. For example, when passing addresses to routines in a server library you must make sure either that the addresses are in RAM (e.g. on the stack) or you must pass the bank and offset so that the server routine can map the original address. The stub routine can hide that mechanism from the caller.
Stub routines are often used with wrappers. A wrapper is a function that is exported instead of the desired function and which then calls the desired function. For example, a wrapper may map the bank and offset provided by a stub routine (as in the example above) and then pass the mapped address to the desired function.
By convention, if a wrapper is being used, so that the exported routine does not have the normal calling interface, the exported symbol should be prefixed with a dot. This helps avoid callers calling the routine directly (not via the stub) and not handling the API differences.
You can see examples of stubs, wrappers and the leading dot in the GRCLIB shareable library.
Copyright (C) 2001, 2002 Graham R. Cobb. The package is distributed under the GPL (see the copyright notices and the COPYING file).
DYNLINK For Rex 6000 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
DYNLINK For Rex 6000 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
When the ZIPfile is expanded, it creates the library itself and
several informational files in the current directory and sources in
four subdirectories. These four subdirectories are intended to be placed within the Projects
directory of the Rexdk
environment and are explained in some more detail below.
The actual library consists of two files:
-ldynlink
on the zcc command line.
The package itself:
../../include/rex
.
../../lib/clibs
and linked with -ldynlink
on the zcc command line.
Dynlink database maintenance utility. Can be used to create the database, examine the database and delete the database.
The directory contains the source files, the makefile
, the .rex
file for the addin
and an addin1.out
file for the emulator.
Sample server library addin.
The directory contains the source files, the makefile
, the .rex
file for the addin
and an addin1.out
file for the emulator.
Sample client addin -- calls a routine exported by DLStest.
The directory contains the source files, the makefile
, the .rex
file for the addin
and an addin1.out
file for the emulator.
To see a real example of using the DYNLINK package, see grclib-so, in the GRCLIB package.
This demonstrates use of the client APIs (in stub routines) and the server APIs in a server library addin. It also shows how to create a server library that can be told to de-register itself when the user wants to delete it or move it (e.g. using Adder).
DYNLINK now includes its own FarCall mechanism.
This is re-entrant and only uses a single 16-bit word of "global" RAM storage (it uses the far_ret
word defined in the RexDK package). The rest of the FarCall data is held on the stack.
The built-in FarCall mechanism now permits arbitrary argument lists and 32-bit results to be supported.
dl__library_call, dl__farcall, dl__farret and the helper routine have been rewritten in assembler.
User-written helper routines are no longer required and no longer supported.
The following routines are now obsolete and will be removed in a later release:
dl_export_with_helper
dl_save_farcall
dl_restore_farcall
Server library initialisation can be simplified by using the new
dl_export_..._fatal
routines.
These will automatically display an error message and exit the add-in
if an error occurs, removing the need for the caller to check the status
of each export call.
However, this is at the cost of possibly increased code size as these routines
use DsPrintf and other Rex routines.
Helper routines (since obsoleted).
Example (GRCLIB).