home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Fish 'n' More 2
/
fishmore-publicdomainlibraryvol.ii1991xetec.iso
/
fish
/
programming
/
libtool_463
/
jimsexample
/
article
< prev
next >
Wrap
Text File
|
1991-03-09
|
15KB
|
296 lines
For Amiga World Tech Journal. Nov 6, 1990. Set your editor's TAB width to 8
characters (this file contains ASCII graphics).
From: Jim Fiore
dissidents
730 Dawes Avenue
Utica, NY 13502
(315) 797-0343
Shared Libraries for the Lazy
by Jim Fiore, dissidents
BIX: jfiore
If you've done any significant programming on the Amiga, you are no doubt
familiar with the concept of a shared library. Shared libraries are an
integral part of the Amiga operating system. In essence, a shared library
is a collection of routines which any application can utilize. Since the
Amiga uses a multi-tasking operating system, it makes sense to offer library
support as part of the operating system, rather than relying on typical link
libraries. By doing so, applications using the functions do not require
duplicates of the code. This saves system memory for other purposes (ie,
other tasks, or perhaps larger data files in ram). Any collection of routines
which might be used by several programs should be considered for conversion
into a library. Classic examples of system libraries include the Intuition
and Graphics libraries. In this way, graphics and user interface code is
shared among several applications.
You may also wish to break up a larger program into a series of libraries
instead of using overlays. In this way, libraries can be loaded as needed,
keeping the core of the program relatively small. Finally, libraries can be a
good way of sharing function capabilities with other people without sharing
source code, or requiring a specific language or compiler. Given the
pervasive use of libraries and their myriad advantages, learning how to
create your own libraries is a useful skill. There have been a few techniques
and examples shown in various places over the past few years. Personally, I
have always found them lacking in a few specific areas. It seemed that you
either had to maintain a large number of files in order to create the
library, or the scheme was very much tied into a particular language or
compiler. In this article, we'll look at what is perhaps the simplest way of
creating a library. It doesn't require a lot of maintenance, and it can be
used with a variety of languages, including assembly and C (both Manx and
Lattice). All you will need to create (and ever modify in the future) is the
functions of interest, and a function description file (sometimes referred to
as ".fd files"). Our example will turn a set of ordinary C language functions
into a shared library using the Manx C compiler, but you could just as easily
use the Lattice compiler or assembly language.
Before we look at the example, it will help if we understand the structure of
a shared library. A library may be broken into four main parts: a Library
Node, a function jump table (often referred to as a vector table), the set of
functions, and the global data for the library. In memory, a library looks
something like this:
----------------------------
------>| |
| | Functions |
| --->| |
| | ----------------------------
| |
| | ----------------------------
| ----| |
-------| Vector Table |
| |
---------------------------- <---- The Library Base address
| |
| Library structure |
| |
----------------------------
| |
| Library data |
| |
----------------------------
Let's look at the Library structure first. A Library structure is defined as
follows:
struct Library {
struct Node lib_Node;
UBYTE lib_Flags;
UBYTE lib_pad;
UWORD lib_NegSize;
UWORD lib_PosSize;
UWORD lib_Version;
UWORD lib_Revision;
APTR lib_IdString;
ULONG lib_Sum;
UWORD lib_OpenCnt;
};
The Flags field is used by Exec to keep track of what is happening with the
library. The NegSize and PosSize fields indicate the size (in bytes) of the
library, on either side of the library base. The Version and Revision fields
are used to indicate future changes and updates to the library. The IdString
is a pointer to a null-terminated ASCII string giving further information
about the library. Sum is the library checksum which is used by Exec to
ensure library integrity. The OpenCnt field holds the number of tasks which
have opened the library so far. Every time a task calls OpenLibrary() for
this library, this field is incremented. Every time the complementary
CloseLibrary() function is called, this field is decremented. If the Open
Count is zero, Exec may remove the library in order to free up ram.
The other major item of interest is the vector table. This is comprised of a
group of 6 byte entries, one entry for each function in the library. Each
entry consists of a jump instruction (2 bytes) followed by the absolute
address of the function being called. Each function call then, is a multiple
of 6 bytes behind the library base. For example, the seventh function in the
table would be at location LibraryBase-42. These 6 bytes multiples are known
as Library Vector Offsets, or LVOs, for short. Along with the normal
application functions, there are four mandatory functions which all shared
libraries must have. Consequently, the first application function is always
the fifth one in the list, at position 30 (referred to as the Bias, in a .fd
file). The four special functions are: Open, Close, Expunge and Reserved. The
The Open and Close functions are called for each OpenLibrary() and
CloseLibrary() call. The Expunge routine is used for final cleanup if the
Open Count has reached zero and Exec has decided to remove the library. The
Reserved function is for future use. Presently, all it should do is return 0.
In order to properly load a library, Exec needs a RomTag structure. In
assembly, a RomTag looks something like this:
RomTag:
dc.w $4AFC ;Voodoo magic RomTag word.
dc.l RomTag
dc.l endRom
dc.b NO_AUTO_INIT ;Auto-initialize, or not.
dc.b VERSION ;Library version, as used in OpenLibrary().
dc.b NT_LIBRARY ;Type. This is a Library.
dc.b PRIORITY
dc.l Lib_Name ;Pointer to Name string.
dc.l Lib_Id ;Pointer to ID string.
dc.l Lib_Startup ;Pointer to initialization routine.
endRom
Normally, Priority is 0. NT_LIBRARY is defined as 9, and we'll be using
a non auto-initialized library (0) since this saves space and reduces load
time.
If you were building a library from scratch, besides the functions of
interest, you would need to create, compile, assemble and link the RomTag,
the Library structure, the function table, the initialization routine, and
the Open, Close, and Expunge routines. Some of this may be reused from
library to library, but there is still a reasonable amount of work involved.
To circumvent this, you can use the LibTool utility. LibTool was created by
Jeff Glatt of dissidents. (LibTool and its associated documentation and
examples are copyrighted, but are freely redistributable. In other words,
feel free to use it at any time, or give it to friends. You just can't sell
it to anyone for profit.) LibTool will create all of the auxiliary items
you'll need for your library (including pragmas and header files) from a
single .fd file that you create. Using LibTool, you'll only need to make two
files in order to create a library: the functions of interest, and the .fd
file.
At this point, it's time to consider the code which will be turned into
the library functions. There are a few rules which you should remember.
We'll be using C, but these items are pertinent to any language. First,
your code should be re-entrant. This means that it does not make use of
global variables. All variables should either be held on the stack, or should
be allocated as needed. The reason for this is that it is possible for
several tasks to open and use a library simultaneously. If two tasks are
calling the same function at about the same time, it is quite possible for
one task to alter a global variable before the other task is finished with
it. The results are chaotic at best. In contrast, it is perfectly acceptable
to use global constants. Since constants are not altered by a function, there
is no problem with one task stepping on another task. Examples of acceptable
constants would be fixed strings and look-up tables (such as a sine wave
table). The second item to remember is that if your library needs to use the
functions contained in other libraries, these other libraries must be opened
(and eventually closed) from within your library. For example, you might
wish to create a DrawBox() function based on the system functions Move() and
Draw(). Since these two functions reside in the graphics library, graphics
must be opened as part of your library's initialization. Graphics will then
be closed as part of your library's expunge routine. The final item is that
you should not use printf() style functions from within the library. The
library will not have access to stdin, stdout, or stderr. The easy way around
this is to have library functions which return error codes, which can then be
interpreted by the calling program.
For our example, we're going to make a library which implements rectangular
to polar and polar to rectangular conversions. The library will consist of
four functions: Mag(), which takes the real and imaginary rectangular
components and returns the polar vector's magnitude; Ang(), which takes the
rectangular components and returns the vector's angle in degrees; Real(),
which takes the polar magnitude and angle in degrees, and returns the real
rectangular component, and Imaj() which takes the same polar arguments and
returns the imaginary rectangular component. I chose these functions because
they're reasonably useful, and fortunately, very quick to code. In any case,
the you don't have to do much typing since all of the example code is found
on disk.
These functions require the use of floating point math. We have several
choices. For the sake of expediency, we'll use Motorola fast floating point.
In order to make our functions, we'll need routines found in mathffp.library
and mathtrans.library. Consequently, we have to open these two libraries as
part of our initialization, and we need to close them as part of the expunge
"clean up". If you look at the library code, AWlib.c, you can see our four
short functions. We also have two other functions, myInit() and myFree().
myInit() will be called as part of the initialization routine. It returns a
BOOL value (TRUE or FALSE). All it does is open the required math libraries.
If it can't open the libraries, myInit() returns FALSE. This will prevent our
library from loading. myFree() is called as part of the expunge routine. It
simply closes the libraries which were initially opened. If we needed to
perform a certain amount of work each time the library was opened or closed,
we could add our own custom functions there, as well.
Much of the drudgery of creating the library is going to be taken care of by
LibTool, in conjunction with a specialized function description file. LibTool
will create a library startup module which will contain the RomTag, the
Library structure, the function table, the four mandatory functions, wedges
into the various routines (eg, the references to our myInit() and myFree()
functions), the proper version and revision numbers, strings, and the like.
Also, this module will automatically open Exec, Dos, Intuition, and Graphics
for you, since they are used so often (and are most likely already present in
the system). Therefore, if you're only calling functions from these system
libraries, chances are that you won't even need Init() and Free() routines.
To aid in the construction of your applications which call your new library,
LibTool can also create a header file, pragma statements, and C "glue"
routines. The glue routines are used to pull C arguments off of the stack and
stuff them into the proper registers for the library. It is possible to
create libraries with LibTool that expect arguments on the stack. The
resulting glue is smaller for C applications, but this does make it harder to
access the library from other languages. Of course, the whole issue of glue
routines is bypassed if pragmas are used. By using pragmas, the C compiler
will move the arguments into the registers, and no glue is needed.
The .fd file is an extension of the ordinary .fd files which most Amiga
programmers are familiar with. LibTool recognizes extra commands which allow
it to create the complete Library Startup module. Here is our .fd file:
##base AWBase *the name of our base used by C glue code and C PRAGMAS
##name AW *the name of our library (ie, aw.library)
##vers 1 *version #
##revs 0 *revision #
##init myInit *to be called once (upon loading the lib)
##expu myFree *to be called once (upon expunging the lib)
##libid Amiga World TJ lib (ver 1.0)
##bias 30 *first function is always at an offset of -30 from lib base
*Here are all of the lib functions callable by an application
*They all return doubles
##ret double
Mag( real, imaj )
Ang( real, imaj )
Real( mag, ang )
Imaj( mag, ang )
##end
Notice that virtually everything you need to describe your library (and
update it in the future) is found in this one file. Each specialized item is
given its own command. Of particular interest are the ##init and ##expu
commands which are followed by the names of our initialization and expunge
routines. Commands are available for the previously mentioned Open and Close
routines, if required (##open, ##clos).
Using Manx 5.0 from the CLI, you make the library as follows:
LibTool -cmho glue.asm AWlib.fd
This creates the library startup code (AWlib.src) using C style syntax (ie,
it prepends an underscore to the function names), the C header file (AWlib.h)
for the applications, and a C application glue file (glue.asm) which will be
assembled, and then linked with the application.
as -cd -o LibStart.o AWlib.src
This assembles the library startup module. Note that we are using large code
and data so that we don't have to worry about saving register A4, as you
would with the small model.
cc -mcd0b -ff AWlib.c
Here, we compile the library code, again using the large model. We are
suppressing the standard Manx startup (.begin), and using fast floating point
math.
ln -o libs:AW.library LibStart.o AWlib.o -lmfl -lcl
Finally, the library is created by linking together the needed parts. It is
VERY important that LibStart.o be the first object module in the list.
The applications which call the library are compiled and linked in the
standard manner. If you are using glue files (as we are here), glue.asm must
be assembled and then linked with the application program. Our application,
AWlib_app.c, simply opens the library, and calls each of the functions in
turn.
As mentioned earlier, LibTool can be used with the Lattice compiler, and with
assembly language development systems. It can also be used for other
purposes, including the creation of BASIC .bmap files, and in the
construction of devices. Further details concerning LibTool are found on the
disk, along with other examples.
Hopefully, this little foray has enticed you into creating some of your own
shared libraries, and eased the programming burden as well. Have fun.