Path: utzoo!attcan!utgpu!watserv1!maytag!xenitec!tirith!ggk
From: ggk@tirith.UUCP (Gregory Kritsch)
Newsgroups: comp.sys.amiga.tech
Subject: Re: Making Shared Libraries (LONG)
Message-ID: <2751.653532429@tirith.UUCP>
Date: 15 Sep 90 22:17:09 GMT
Followup-To: comp.sys.amiga.tech
Lines: 630
In-Reply-To: butch@fergvax.unl.edu (FERGVAX Daily Operator)
In a message posted on 16 Sep 90 15:55:29 GMT,
butch@fergvax.unl.edu (FERGVAX Daily Operator) wrote:
FDO> Anyway, if anyone who has made a library for the Amiga and would
FDO>not mind sharing the how-to's of it all, I would very much appreciate
FDO>it, since I've tried and failed on several occasions to make one.
Okay, I've created several libraries successfully, and so I may as well
try to document what I've done.
I use Lattice C 5.05, and Lattice Asm for my work. I do NOT use the
blink library making utility, since it does not work as far as I can
tell. There are some things which must be done awkwardly, I'd be
interested in hearing of anyone's solutions.
The first part of creating a library is the first segment of the load
file, which pretty much has to be done with an assembler (to get data in
the code section). I'm going to insert an example file, and add
documentation to each step along the way.
All files , comments, and notes in this example are copyright (c) 1990
by Gregory Kritsch, All rights reserved, except where borrowed from
the v1.1 RKM Appendix library example, which is copyright (c) 1985
Commodore-Amiga Inc. By now, the amount of actual Commodore code is so
minimal, I don't think this really matters. Use as an example basis for
freeware products is permitted. EMail me (ggk@tirith.UUCP) for details
on using it in a commercial or shareware product.
*
* $Header: tirith:usr/dev/work/juliet/jms/rcs/lib.a,v 2.0 90/04/01 15:35:30 kritschg Exp $
*
include "base.i"
That file describes a few constants, and includes all the other files.
See below.
csect romtag,0,2,0,4
; code segment
; long word aligned
; use 4 byte absolute addresses
The RomTag must occur in the first HUNK_CODE segment in the load module.
Assigning it a unique section name and not using Small Code is probably
a good idea (don't let the linker merge it, just in case it moves it).
The closer you can get the RomTag to the beginning of the segment, the
(almost trivially) faster it will be found.
;--- external references
;--- standard library vectors
xref _LibInit
xref _LibOpen
xref _LibClose
xref _LibExpunge
xref _LibNull
These functions are written in C. If you're doing assembly, they should
probably go in this segment as well. These are the "standard" library
functions... the next set are "custom". Yes, the table was derived from
the _lib.fd file - remember, third column is comments.
I've added two '_', rather than one for a reason. It allows me to call
my own library internally through #pragmas and the library base rather
than directly. This means that SetFunction() will be fully, not
partially, effective.
;--- custom function vectors
xref __ARexxQuery () ()
xref ___mblog (fmt,a,b,c,d,e) (A0,D0/D1/D2/D3/D4)
xref __AddFlo (name,mode) (A0,D0)
xref __AddReq (file,pass) (A0,A1)
xref __FindScreen ()()
xref __FreeScreen ()()
xref __SetScreen (scr)(a0)
xref __AddArea (area) (A0)
xref __OpenArea (name,mode) (A0,D0)
xref __OpenFirstArea (mode) (D0)
xref __OpenNextArea (area,mode) (A0,D0)
xref __CloseArea (area) (A0)
xref __RemoveArea (area) (A0)
xref __ReadMsg (area,num,msg,text) (A0,D0,A1,A2)
xref __ReadMsgText (area,num,msg,text) (A0,D0,A1,A2)
xref __UpdateMsg (area,num,msg) (A0,D0,A1)
xref __PostMsg (area,msg,text) (A0,A1,A2)
xref __DeleteMsg (area,num) (A0,D0)
xref __GetField (body,field,buf,len) (A0,A1,A2,D0)
xref __FindField (body,name)(A0,A1)
;--- debugging
IFD DEBUG
xref _dprintf
ENDC
The debugging at this level essentially causes register dumps on every
function call entry and exit. There's not much else. Oh, btw, I'm
using lattice's __asm keyword, so my arguments are passed to the C
functions in registers, not on the stack. If you want (need) them on
the stack, you must put assembler stubs inside the library, or you could
specify that your functions take parameters on the stack (unlike nearly
every other library out there).
;--- beginning of the code
; if called from AmigaDOS, return error code 30
entry:
moveq.l #30,d0
rts
Really simple. Set d0 to some value and RTS, in case the user types
your library name in as a command. The RKM reccomends 0, I use 30 here
to cause a failure. Once I wrote a short routine to print out the
library idString (see below) and return.
This is the romtag. Notice that it is, uh, 4 bytes (I think) from the
beginning of the segment. Look in the file exec/resident.{h|i} for the
details on this structure. Be very careful not to mess it up.
;--- romtag and other resident stuff
romtag:
dc.w RTC_MATCHWORD
dc.l romtag
The first six bytes identify the RomTag. The first two bytes MUST be
RTC_MATCHWORD (which is "the official illegal instruction"), and the
following long word MUST be a pointer to the first byte.
dc.l endskip
This pointer MUST be within the same segment, according to the RKM. I'm
not entirely sure why, but I suspect it has to do with searching and not
finding an appropriate library (like if someone renames the .library file).
dc.b RTF_AUTOINIT
There are two bits defined for that field, RTF_COLDSTART and
RTF_AUTOINIT. RTF_COLDSTART is for libraries in the ROM or on the
KickTagPtr list, and indicates they should be inited during boot time.
Otherwise, I think they would be inited on the first OpenLibrary() call
for them, but I'm not sure.
The second is RTF_AUTOINIT. Since a RomTag can indicate just about
anything, not just a library or device, you don't always want Exec to
create a Library base structure for you. By setting RTF_AUTOINIT, the
RT_INIT field points to a four long word structure used to create the
library base. Otherwise, it points to a function to execute to
initialize the whatever. Some older libraries and devices don't use
RTF_AUTOINIT, and create the library base internally.
dc.b VERSION
dc.b NT_LIBRARY
Oh, by the way, with the assistance of the RKM, you can also use this
template to create an NT_DEVICE entry (just add two more standard
functions, BeginIO and AbortIO). Or, if you want to put some code or
something on the KickTagPtr list, you can set this to something else.
dc.b 0
That was the priority. Only really used if you're on the KickTagPtr
list (or in the ROMs).
dc.l libname
dc.l libid
libname is a nul terminated string, including the ".library". It must
exactly match the disk file name, except in case sensitivity. Note that
OpenLibrary() IS case sensitive.
libid is the "idString" of your library. It has a very rigid format in
the RKM:
'name version.revision (dd MMM yyyy)',13,10,0
I don't know of anything that depends on this, but you should follow it
anyhow.
dc.l init
This is the RT_INIT pointer discussed in RTF_AUTOINIT. Immediately
following is the init table.
init:
dc.l BaseSize
The POSITIVE size of your library base, AT LEAST sizeof(struct Library).
If you have extended your library structure to include private data, you
should MAKE SURE you use the correct size. I have an external program
that basically does a printf("%d\n",sizeof(struct JulietBase)). THIS IS
WHERE A MESSUP WILL HURT!
dc.l LibFuncTab
A pointer to an array of function entry points, terminated by a -1 (see
below).
dc.l LibDataTab
A pointer to an InitStruct data array. See the autodocs on
exec/InitStruct() if you're curious, exec/initializers.i is enough info
to build the table I think.
dc.l LibInit
A pointer to an init function to call.
;--- function table
LibFuncTab:
;--- standard functions
;--- debug trap versions
IFD DEBUG
dc.l LibOpen
dc.l LibClose
dc.l LibExpunge
dc.l LibNull
ENDC
;--- non-debug trap versions
IFND DEBUG
dc.l _LibOpen
dc.l _LibClose
dc.l LibExpunge
dc.l _LibNull
ENDC
;--- custom functions
dc.l __ARexxQuery () ()
dc.l ___mblog (fmt,a,b,c,d,e) (A0,D0/D1/D2/D3/D4)
dc.l __AddFlo (name,mode) (A0,D0)
dc.l __AddReq (file,pass) (A0,A1)
dc.l __FindScreen ()()
dc.l __FreeScreen ()()
dc.l __SetScreen (scr)(a0)
dc.l __AddArea (area) (A0)
dc.l __OpenArea (name,mode) (A0,D0)
dc.l __OpenFirstArea (mode) (D0)
dc.l __OpenNextArea (area,mode) (A0,D0)
dc.l __CloseArea (area) (A0)
dc.l __RemoveArea (area) (A0)
dc.l ReadMsg (area,num,msg,text) (A0,D0,A1,A2)
dc.l ReadMsgText (area,num,msg,text) (A0,D0,A1,A2)
dc.l __UpdateMsg (area,msg) (A0,A1)
dc.l PostMsg (area,msg,text) (A0,A1,A2)
dc.l __DeleteMsg (area,num) (A0,D0)
dc.l GetField (body,field,buf,len) (A0,A1,A2,D0)
dc.l __FindField (body,field) (A0,A1)
dc.l -1
;--- data to initialize the library base with
LibDataTab:
INITBYTE LH_TYPE,NT_LIBRARY
INITLONG LN_NAME,libname
INITBYTE LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED
INITWORD LIB_VERSION,VERSION
INITWORD LIB_REVISION,REVISION
INITLONG LIB_IDSTRING,libid
dc.l 0
Most of LibDataTab is probably directly from the RKM, since I don't
claim to fully understand InitStruct() [yet]. Probably doing a straight
copy is your safest bet.
;--- the name and id strings
libname:
dc.b 'jms.library',0
libid:
dc.b 'JMS II (12 May 1990)',13,10,0
Note the nul terminated strings. You should change them to suit your
own needs.
;--- word align for code
ds.l 0
The following "functions" are mainly to correct problems I had. Some of
them may not have been neccesary, I'm not sure. It works, I'm happy.
LibInit:
movem.l d1-d7/a0-a6,-(sp)
jsr _LibInit
movem.l (sp)+,d1-d7/a0-a6
rts
the LibInit function has a return value in d0. Everything else must be
preserved I think, including A0,A1,D1 (which normally aren't).
As noted below, there is (was?) a bug in Lattice 5.04, where using the
__asm key with too many registers caused it to clobber some before
taking working copies. I wish Lattice was a bit more intelligent with
the __asm keyword.
;--- kludge aroung LC 5.04 compiler errors with __asm keyword
; passing A0,A1,A2 and A6 results in A2 being clobbered
ReadMsg:
move.l a2,d1
jmp __ReadMsg
ReadMsgText:
move.l a2,d1
jmp __ReadMsgText
PostMsg:
move.l a2,d1
jmp __PostMsg
GetField:
move.l a2,d1
jmp __GetField
;--- kludge to preserve registers properly for expunge routine
IFND DEBUG
LibExpunge:
movem.l a0-a6/d1-d7,-(sp)
jsr _LibExpunge
movem.l (sp)+,a0-a6/d1-d7
rts
ENDC
The debugging traps exist for the register dump. It does, however, look
like I've removed that code partially from this lib.a version.
;--- debugging traps
IFD DEBUG
LibOpen:
jsr _LibOpen
rts
LibClose:
jmp _LibClose
LibExpunge:
bsr regdump
movem.l a0-a6/d1-d7,-(sp)
jsr _LibExpunge
movem.l (sp)+,a0-a6/d1-d7
bsr regdump
rts
LibNull:
jmp _LibNull
regdump:
bchg #1,$bfe001
movem.l d0-d7/a0-a7,-(sp)
pea fmtstr
jsr _dprintf
For the curious, dprintf() is a Lattice function that goes directly to
the printer, through the cia port and the debug.lib RawDoFmt() type
functions.
move.l #14965,d0
1$ dbra d0,1$
addq.l #4,sp
movem.l (sp)+,d0-d7/a0-a7
bchg #1,$bfe001
rts
fmtstr:
dc.b 'd0:$%08lx d1:$%08lx d2:$%08lx d3:$%08lx',13,10
dc.b 'd4:$%08lx d5:$%08lx d6:$%08lx d7:$%08lx',13,10
dc.b 'a0:$%08lx a1:$%08lx a2:$%08lx a3:$%08lx',13,10
dc.b 'a4:$%08lx a5:$%08lx a6:$%08lx a7:$%08lx',13,10
dc.b 13,10,0
ENDC
;--- align the endskip
ds.l 0
;--- end of this segment
endskip:
endskip MUST MUST MUST be in the same segment as the RomTag.
end
-- end of lib.a --
Now, base.i, for the curious. Fairly boring. BaseSize is determined by
an external program, of course.
;------------------------------------
; $Header: tirith:usr/dev/work/juliet/jms/rcs/base.i,v 2.0 90/04/01 15:44:50 kritschg Exp $
; $Author: kritschg $
; $Revision: 2.0 $
; $Date: 90/04/01 15:44:50 $
;------------------------------------
include "exec/types.i"
include "exec/libraries.i"
include "exec/lists.i"
include "exec/resident.i"
include "exec/initializers.i"
BaseSize equ $000008D4
VERSION equ 2
REVISION equ 0
SUBREV equ 0
-- end of base.i --
Now, library.c. This is the C functions for LibOpen, LibClose, LibInit,
LibExpunge, and LibNull. There are some strange things in here, that
aren't explained very well in the RKM docs. I'm cutting parts of this
file out, that are directly related to the discussion of creating a
library.
/*
* $Header: tirith:usr/dev/work/juliet/jms/rcs/library.c,v 2.0 90/04/01 15:35:51 kritschg Exp $
*/
#include "jms/jms.h"
Note that struct JulietBase is defined in jms/jms.h. It's not really
important, other than it begins as:
struct JulietBase {
struct Library Library;
ULONG SegList;
The struct Library MUST be the first field in the structure, this is the
only restriction.
For no apparent reason, you are passed your library base pointer in d0
and the result of LoadSeg() in a0. Every other library function has the
library base in A6. Remember, this is assuming RTF_AUTOINIT. If you
don't use RTF_AUTOINIT, I'm not sure what you get (if anything at all).
Note: Exec has, I think, done a Forbid() for you here, and will call
Permit() later for you. The RKM reccomends keeping this routine very
short. I tend to agree.
Return Value: You should return the pointer to your library base (or
NULL if the init failed), in D0 (which is the normal register for
Lattice C to put return values in).
struct JulietBase * __asm
LibInit(register __d0 unsigned long lb, register __a0 unsigned long Seglist)
{
register struct JulietBase *JulietBase;
JulietBase = (struct JulietBase *)lb;
JulietBase->Seglist = Seglist;
return(JulietBase);
}
This function is called when Exec has determined that it needs some
memory, typically from within AllocMem() when there is very little
memory available. As such, MAKE NO ASSUMPTIONS about the context you
will be called in. You may be a Task or a Process, so dos calls are
probably out. Calling AllocMem() is probably taboo as well.
Note: Once again, Exec has called Forbid() for you, and will call
Permit() for you as well. Keep it short and sweet.
Note: The RKM infers that Expunge will be called by AllocMem() whenever
it needs memory. My experience is that it will only be called by
AllocMem() IF THE OPENCNT IS 0. I hope CBM fixes this (sometimes,
Expunge could free data structures not in use, even though the library
is open).
Return Value: Return a NULL if your library is still in memory, or the
segment list (stored somewhere from the LibInit call) if you wish to be
unloaded.
long __asm
LibExpunge(register __a6 struct JulietBase *JulietBase)
{
long seg;
register struct Area *area, *next;
/* now expunge the library if possible */
if (JulietBase->Library.lib_OpenCnt == 0) {
Remove(JulietBase);
As soon as you call Remove, you're off the library list, and that pretty much
gaurantees that you'll want to return your seglist and be removed from
memory. Any conditions should be checked BEFORE THIS POINT.
seg = JulietBase->Seglist;
FreeMem((void *)(((long)JulietBase) - JulietBase->Library.lib_NegSize),JulietBase->Library.lib_NegSize + JulietBase->Library.lib_PosSize);
NOTE: YOU MUST CACHE THE SEGMENT LIST BEFORE CALLING FREEMEM(). As soon
as you call FreeMem(), the library base is considered entirely invalid.
return(seg);
} else {
JulietBase->Library.lib_Flags |= LIBF_DELEXP;
I'm not 100% sure, but the RKM has this clause, so I have it too. I
don't think I've ever seen it called (I don't think it can be).
However, if you have your own conditionals about expunges, you should
set LIBF_DELEXP if Expunge is called and you don't remove yourself.
return(NULL);
}
}
This is called by the OpenLibrary() function every time someone opens
the library. I think Exec does a Forbid(), but its definitely not as
critical. I do dos calls here.
Note: Increment the open count, and clear the LIBF_DELEXP bit in this
function.
Return Value: return the library base pointer.
struct JulietBase * __asm
LibOpen(register __a6 struct JulietBase *JulietBase)
{
JulietBase->Library.lib_OpenCnt++;
JulietBase->Library.lib_Flags &= ~LIBF_DELEXP;
return(JulietBase);
}
This is related to a call to CloseLibrary(). There are two possible
results from this function: a NULL, which means to just keep on going,
or the segment list, which means that you've called expunge and it
returned the seglist, indicating the library should be removed. The
CloseLibrary() call to LibExpunge() is done IF LIBF_DELEXP is set.
Note: Decrement the open count.
long __asm
LibClose(
register __a6 struct JulietBase *JulietBase,
register __a1 struct IOStdReq *ior)
{
JulietBase->Library.lib_OpenCnt--;
/* determine if we should try to expunge */
if ((JulietBase->Library.lib_OpenCnt == 0) && (JulietBase->Library.lib_Flags & LIBF_DELEXP)) {
return(LibExpunge(JulietBase));
} else {
return(NULL);
}
}
This is sort of a "reserved for future expansion" function. It should
return NULL, I think (either that or just be an rts), so this code below
may be wrong. I don't think it will be called under 1.3, although maybe
under 2.0...
void __asm
LibNull()
{
}
-- end of library.c --
Additional notes:
When compiling, I use lc1 -b0 to force absolute addressing. Needless to
say, A4 will not be properly set for relative addressing. There is no
c.o, so getting A4 might also be some fun. I think the lc2 -y may work,
but I'm note sure.
Specify the lib.a object module FIRST on the FROM line to BLink.
Be careful using SMALLCODE and/or SMALLDATA.
Don't link with any of the c*.o family. Also remember that since there
is NO c.o, so some things just plain won't work properly. You should
stay away from any complicated runtime library functions (like C files),
and should use pragmas for external libraries. Avoid amiga.lib like the
plague, I only include it for the dprintf() function.
Note that the best thing for other libraries (like dos, graphics,
intuition, &c) is to add struct MumbleBase *MumbleBase to your library
base structure, and create your own #pragma files using
YourBase->MumbleBase as the library base (instead of the Lattice
provided #pragmas, which use the global MumbleBase).
Globals aren't actually evil, just strongly disreccommended. Remember,
if you use globals, especially if they're volatile (ie they change) you
should put semaphores around access to them. Even stuff in your library
base may need to be semaphored. Remember that you CAN have the very
same function doing the very same thing at almost the very same time
within library code.
Declare your library base for all functions. An example declaration:
/*
* $Header: Bottom:usr/dev/work/juliet/msg/rcs/field.c,v 2.0 90/04/01 18:05:49 kritschg Exp $
*/
#include "jms/jms.h"
char * __asm
_FindField(register __a6 struct JulietBase *JulietBase,
register __a0 char *body,
register __a1 char *name)
{
Note that this function could call itself recursively as FindField(),
not _FindField(), if you include the normal pragmas to your own library.
This has the advantage that SetFunction() works for calls made inside
your library (which is a good idea).
Oh, a word about .fd files. They're really simple:
##base
* this is a comment: must start with an '_' for fd2pragma,
* this _ is removed (normal C vs. Asm thing).
* eg. ##base _JulietBase
##bias 30
* the offset of the first function, expressed as a positive rather than
* a negative. Just leave it at 30.
function(arg1,arg2,...)(r1,r2,...)
* argN are not actually looked at, but make them meaniful for humans
* reading the file.
* rN are one of the registers D0-D7/A0-A5 (A6 and A7 are already in use).
##end
* you MUST mark the end this way
If you have any questions, mail me.
FDO>Butch Rosecrans
FDO>butch@fergvax.unl.edu
---
Gregory Kritsch
Fido: 1:221/208.11110 [1:163/109.30]
UUCP: xenitec!tirith!ggk