home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Otherware
/
Otherware_1_SB_Development.iso
/
amiga
/
programm
/
programi
/
saslib.lzh
/
saslib.doc
< prev
next >
Wrap
Text File
|
1992-02-02
|
19KB
|
500 lines
Amiga shared libraries under SAS/C - the easy way
=================================================
Questions, comments, etc. can be directed to:
David Jones
6730 Tooney Drive
Orleans, Ont.
K1C 6R4
CANADA
dej@qpoint.amiga.ocunix.on.ca
David_Jones@p8,f109.n163.z1.fidonet.org
David Jones @ Fidonet#1:163/109.8
IMPORTANT NOTICE:
----------------
The files in this archive are released under the terms and conditions
of the Free Software Foundation's General Library Public License.
Release 2 (June 1991) or any later version shall apply.
This license is included in this archive under the name "gpll.doc".
Read the terms and conditions of this license before using this archive!
In particular, there is NO WARRANTY of any kind associated with this
software.
The problem:
-----------
You want to create a library that can be opened by OpenLibrary - an
Amiga shared library. You've looked in the RKMs and you investigated
the ability of the SAS compiler to make libraries. And you've pulled
your hair out. And screamed. And...
Stop. This is what you've been looking for. It's foolproof. The routines
in this file have been designed to be easy to interface to. The library
code isn't the most compact or the most efficient, but the process is
PAINLESS.
Read on and I guarantee you'll have a library easier than you've ever
had it before. NO knowledge of assembly language is required, although
it helps for debugging. If you have any problems, feel free to vent your
frustrations to the mail addresses above.
One final thing: ignore everything about library generation in the
SAS manual unless I say otherwise. It's a jungle in there.
Before you start:
----------------
You will need the following in addition to the files in this archive:
SAS/C compiler for AmigaDOS, Version 5.x
A68K, by Charlie Gibbs
I would have liked to use the SAS assembler, but it is my opinion that
it is broken. So is A68K, but at least it's usable. The SAS assembler
isn't.
Some hints when writing your code:
---------------------------------
Try to write the code as a linker library first. As long as it's
incorporated into the body of programs that call it, CPR can list
its source and you can easily debug it. Once it's resident in the
Exec library list, it can be VERY difficult to do source level debugging.
Get your functions working BEFORE making it into a shared library.
Remember that you're not writing an application; you're writing a library.
Don't call any of the SAS I/O or memory functions unless you know what
you are doing. They depend on globals that are in the standard SAS/C
startup code but are not in the library code, and for good reason.
Libraries aren't processes. If the linker complains about globals that
you've never heard of, then look for calls to SAS library functions.
By the same token, be kind to the OS. Do not assume that you're a
process free to call DOS functions unless you specifically want your
library to be callable from processes. Do not call ANY functions that may
directly or indirectly do a Wait() inside the init and expunge functions
(discussed later).
Now, to create the library:
--------------------------
The files in this archive constitute the support required to create
shared libraries. Here is the contents of the archive:
dejmac.i
exec.offsets
libhdr.a
These files are assembler files that create the library interface
code. You do not need to change any of these.
libinf.a
This is the file that you will need to change. It gives ALL of
the information about the library. Although it's assembly, I
will take you through it step by step.
makefile
This is the makefile for LMK. It gives the necessary compiler
flags and link files.
demo.fd
demo.library
demotest
demotest.c
demo_pragmas.h
squareme.c
These files form a demonstration library.
Designing your library interface - register selection
-----------------------------------------------------
Assuming that you have the routines for your library already written
(you DO have a linker version, don't you?), you will now have to choose
68000 microprocessor registers for the arguments in your functions.
That's the way the Amiga libraries work - in registers.
If you know 68000 assembler well, then go ahead and assign registers to
your arguments. If you don't, read on.
The 68000 has two sets of registers: address registers and data registers.
The address registers are named A0 through A7 and the data registers are
D0 to D7. As a general rule, data registers D0-D5 and address registers
A0-A3 are good choices for library arguments. To assign registers to
your functions, follow these rules.
1. Assign registers to your arguments starting from the lowest numbered
registers and working up.
2. If the argument is a pointer to something, then use an address register.
It is best not to use address registers for BPTRs.
3. If the argument is a byte, word or longword, then use a data register.
Single-precision floating point numbers require one data register.
Double-precision numbers cannot be passed directly, as far as I can
determine. Pass them by reference (using pointers).
4. If you exceed the recommended register allocation (more than six
data arguments and/or more than four pointer arguments) then maybe
you should write less complex functions! You CAN use data registers
D6 and D7 and you CAN use A4 and A5 if you are running SAS/C 5.10
or better.
5. Be sure your compiler is passing what you think it's passing!
Take arithmetic promotion into account.
For example, let's say I have the function
int foo(struct bar *a, int b, char *c, int e, int f)
I would assign registers as follows:
argument register
------------------
a A0
b D0
c A1
e D1
f D2
Preparing the prototype and .fd files
-------------------------------------
It is a good idea to prepare prototype files for your functions.
Prototypes allow the compiler to do type checking for you. If you
aren't using prototypes, try it. You'll probably like it.
When it comes to libraries, you have to be careful with prototypes.
There are two ways that you can call a function in your library
from within your library: through the library register interface
(through A6) and directly, as a normal C program would. It is best for
you to call your function through the library.
Note that you may have functions in your library code that are not
directly callable from a client of your library. These functions can
be prototyped in the normal way.
For functions callable from outside the library, prototype them like
you normally would. Do NOT put any register designations in the
prototypes. For examples on how to do library prototypes, examine
your include files from Commodore.
The next file you will need to prepare is the .fd file. The .fd file
is what the fd2pragma program uses to construct the pragmas that the
SAS/C compiler uses to interface directly to the library. In addition,
the .fd file is the Amiga standard now for defining library functions.
If you include the .fd file in your library release, then users of any
compiler system that reads .fd files can use your library. It's so
easy to prepare a .fd file that I strongly recommend you do so.
As an example of an .fd file, see "demo.fd". You can also use the
.fd files that come on the 1.3 Extras disk if you have them.
Any line in a .fd file that begins with '*' is a comment.
The first line in the .fd file begins with '##base'. This line tells
the system what the name of your library base variable is. The
system libraries all have standard base variable names, such as SysBase,
DOSBase, GfxBase, etc. Pick a name for your library's base and put
it on this line. I have '_DemoBase'. Note that the base name should
start with an underscore, as your C compiler prepends an underscore
to all variable names.
The next line in the file is '##bias 30'. Don't change this unless you
know what you are doing.
What follows is a list of the functions in the library in the format
Name(arg,arg,arg)(reg/reg/reg)
where:
Name is the name of the function.
arg is a name for the argument. This name is never used, it is
only for documentation purposes.
reg is the 68000 machine register that you assigned to the
argument.
Notes:
The value a function returns (if any) is not given in the .fd file.
Argument descriptors are separated with commas.
Register names are separated with slashes or commas. To determine
whether a slash or a comma is required, do the following:
"Compare" the registers numerically. Any address register is
"greater" than any data register. For example, D5 > D2, A3 > A1,
and A0 > D7.
If the register on the right is greater than the register on the
left, then use a slash. Otherwise, use a comma. See the example
below.
Register names are in uppercase (A0, not a0).
Don't use any spaces on the line.
As an example, here's our foo function from above:
foo(a,b,d,e,f)(A0,D0/A1,D1/D2)
The last line in the .fd file should be '##end'.
To test your .fd file, try using fd2pragma. Just give the command line:
fd2pragma <fd file> <pragma file>
If there's a serious problem with your .fd file, fd2pragma will tell
you about it.
Preparing your source code
--------------------------
I suggest you create a C source file to handle the interface to the
Exec library system. The file squareme.c is a working example.
Although the library code opens Exec and Dos for you, you must still
declare the library bases. This is different from application programming
(where the bases are declared in the startup code). In addition, you
must declare LibBase as a pointer to a Library. LibBase is used by
the interface code so don't change its name.
If your library calls itself, then also declare your base variable,
using the same name you used in the .fd file (but without the
underscore).
In addition to the functions in your library, you will have to write
the following functions. Declare them EXACTLY as you see here. The
file squareme.c contains examples of these functions. Failure to follow
these instructions or any that follow could lead to aggravating bugs.
struct Library *__saveds LibInit(void)
This function is called when your library is first loaded by the
system. All of the Amiga library housekeeping stuff is done by
my startup code. What this function is for is if you have to
initialize things before the library is used. For example, if you
maintain a linked list of something, then NewList() it here.
What this function MUST do is return the library base if your
initialization performed successfully. The library base is stored
in LibBase, which is set up correctly before this function is called.
If you call your own library from within itself, then you should
set up your library base to the value of LibBase. In my example,
I have set up DemoBase to the value of LibBase. Then I return LibBase.
struct Library *__saveds LibOpen(void)
This function is called when the library is opened by someone. All
Amiga housekeeping is already done, so all this function does is
perform any process-specific initialization. For example, you might
want to record the process structures of your openers so that you
can maintain a separate data area for each.
What this function MUST do is return the library base if any
initialization completed successfully. See my example.
void __saveds LibClose(void)
This function is called when someone closes your library. Again,
all Amiga housekeeping is done for you. This function is used to
clean up after anything you set up in LibOpen.
This function doesn't have to do anything if it doesn't want to!
All it has to do is return! But it DOES have to be in the file,
even if all it does is return.
BOOL __saveds LibExpunge(void)
This function is called when Exec wants to remove you from the system.
My startup code will free the resources associated with the library
base. All you have to do is free the stuff you allocated in
LibInit.
This version of LibExpunge allows you to abort the expunge if
necessary. For example, some libraries may maintain data that must
be flushed to disk before an expunge. In this case, you may not
want to expunge, even if Exec thinks you can. In this case, return
FALSE from this function. If you can expunge, return TRUE.
Again, if there is nothing to free, this function is allowed to
simply return TRUE!
Notes:
All these functions are declared using __saveds. THIS IS ESSENTIAL!
*** IMPORTANT NOTICE RE: FORBID IN LIBRARIES ***
Exec performs a Forbid() before calling the Init, Open, Close or Expunge
functions. However, the Operating Systems Development Group at Commodore
has indicated that it has never been guarenteed that a library will not
break a Forbid() in open or close. The exception to this rule is the ROM
library set - any ROM library that existed in 1.3 or earlier will not break
a Forbid().
This means that you cannot open or close a non-ROM library (ARexx included)
if you do not want to break Forbid() yourself. In particular, since
Expunge is never allowed to break a Forbid, you must not close a non-ROM
library inside Expunge.
Breaking a Forbid inside a library's Open or Close functions requires
careful mutual exclusion management. The library controller included
in this release of saslib performs such management for you.
Whether you break Forbid or not is up to you, but be aware of it if your
code depends on Forbid! Saslib doesn't care. The end result is that
operations which were previously forbidden, such as flushing a queue
out to disk, no longer are.
Now to prepare the source code itself. You will have to code your
functions to use assembly language interfaces. Assuming that you
already have these functions written and that register assignment has
been done, this is easy:
1. Prepend a 'LIB' to the name of every function callable from outside
the library. If you call functions from within your library, this
will prevent problems associated with having assembler registers in
the prototypes and pragmas at the same time. If you don't know
what I'm talking about, then you are lucky. As an example, I have
named the SquareMe function 'LIBSquareMe'.
2. Immediately after the return type of your function, but before
its name, put the keywords '__saveds __asm' exactly as you see them
here. See my example for details.
3. Before each argument in the function header, put the keywords
'register __xx' where xx is replaced with the register name you
assigned earlier to that argument. Note that the letter in the
register name MUST be in lowercase.
Here's our foo function as an example:
int __saveds __asm foo(register __a0 struct bar *a, register __d0 int b,
register __a1 char *c,
register __d1 int e, register __d2 int f)
Repeat the above for each function callable from a client of your
library. You do not need to change private functions called from
within your library.
Preparing libinf.a
------------------
lininf.a is the file that describes your library. Yes, it's in assembler,
but you don't need to know assembly to modify it. Just do the following:
1. On the line with '_LibName', replace 'demo.library' with the name
you want your library to be opened with.
2. On the line with '_LibID', replace the ID string with a string
identifying your library. The format is
nameof.library major.minor (dd.mm.yy)
See the example.
3. On the LibVersion and LibRevision lines, place version and revision
numbers. For example, if you want your library to be version 4.2,
set LibVersion to 4 and LibRevision to 2. If you don't know
what version you want, use 1.0.
4. Where it says 'put pointers to your functions here', do just that.
Create a line that says 'DEF <name>' where name is the name of
your function, not including the 'LIB'. IMPORTANT!!!! The names of your
functions must appear here IN THE SAME ORDER as they appear in the
.fd file!
Preparing the makefile
----------------------
Now on to the makefile. I have defined lines for the compiler and
assembler. You should replace the path to the assembler 'Bin:a68k/a68k'
with what it is on your machine. If you know how to use the assembler,
then you can tweak the command line arguments but they work fine as
they are.
For the compiler, I have disabled stack checking. This is important.
Your library will be called from many processes each with its own stack.
Which stack do you expect to check? Other than this, you can define
most other C flags any way you want.
The make instrucions for demo.library link the object file (squareme.o)
to libhdr.o and libinf.o IN THAT ORDER. Replace squareme.o with the
name(s) of the object files you will be producing. Leave the other
object file names as they are. If you have any linker libraries you
would like to use, feel free to use them.
The dependency for squareme.o can be anything you want. Just be sure
to have stack checking turned off.
The dependencies for libhdr.o and libinf.o should be left as they are.
Finally, you should make an entry for your pragma file. Use fd2pragma
to generate the pragma file from the .fd file. Putting this in the
makefile ensures that the pragma file is kept up to date if you ever
change the .fd file.
Of course, you should change all references to demo.library to whatever
you are calling your library.
That's it!
And now the fun part
--------------------
Do an LMK. Ideally, everything should compile, assemble and link just
fine. If it does not, check your files over for small errors. If you
still have problems, write me.
The result will be a library file that you can copy to libs:. Try
it out! If you have xoper or a similar utility, use it to verify that
opening, closing and expunging work correctly.
Now, who said it was hard to make an Amiga library?
ARexx function libraries
------------------------
ARexx allows you to add functions to the language through the use of
libraries. Details are in the documentation that comes with pre-2.0
versions of ARexx and in the Programmer's Guide to ARexx, published
by Commodore.
You can use saslib to create ARexx function libraries. To do so:
1. Remove the semicolon from the INCLUDE_REXX line at the bottom of
libinf.a.
2. Declare your query function in C like this:
ULONG __saveds __asm LIBRexxQuery(register __a0 struct RexxMsg *rm,
register __a1 char **rv)
3. Be sure to reserve an offset for the query function in the dispatch
table. Usually, the query function resides at offset -30, which
corresponds to the first entry in the table.