(c)  Copyright 1990 Commodore-Amiga, Inc.   All rights reserved.
The information contained herein is subject to change without notice,
and  is provided "as is" without warranty of any kind, either express
or implied.   The entire risk as to the use of this information is
assumed by the user.


Creating Your Own Library


Michael Sinz

On the Amiga, programmers can easily add their own function
libraries to the system.  The LIBS: directory of the Workbench
disk contains .library files.  These are disk-loadable function
libraries that are linked into the system only when needed by an
application.  For instance, the IEEE math functions comprise a
disk-loadable library.  

Disk-loadable libraries are sometimes called shared-code
libraries since their functions are callable from multiple
programs at once.  This means that all functions in the library
must be reentrant.  Also, any functions that the library calls
have to be reentrant too.   For instance, sprintf() from the
standard C link library is not reentrant. and should not be
called from a disk-loadable library.  In addition to being
reentrant, the library has to correctly employ semaphores to
protect any shared data structures.  

This article presents a working example of a disk-loadable
library and shows you how to create your own using Lattice C. 
You may also want to refer to the assembly-language example
library in the Includes and Autodocs Manual (Addison-Wesley, ISBN
0-201-18177-0, page G-13).  For the basics on how libraries work
on the Amiga, refer to the ROM Kernel Manual: Libraries and
Devices (Addison-Wesley, ISBN 0-201-18187-8, page 229).

Selecting Your Library Functions

The first step in creating your own library is to select the
functions you will use for entry points and come up with the
parameters the functions will need.  The parameters must be data
items that can fit into a processor register.  That is, you can
not pass a structure, but you can pass a pointer to it.  Our
example library will consist of exactly three functions, Cube(),
Count(), and FunStuff().  The parameters to the three routines
are:

       long = Cube (long num);
       d0           d0

       long = Count (void);
       d0

       void FunStuff(long num,char *text);
                     d0       a0


Selecting Registers for Each Function Argument

In this example, the Cube() function has one argument, the
Count() function has none, and the FunStuff() function has two. 
Here are the rules for selecting registers to use for the
function arguments:

o Don't select registers arbitrarily.  A thoughtful selection    
    makes a real difference in the performance of your library.
o Select registers starting at the low numbers.  (d0, d1, d2...)
o Select registers that best describe what the argument is.  If  
    the argument is a data item select a d register.  If the    
    argument is an address, select an a register.
o You must not select a4, a6 or a7 since these are special cases. 
    If you do the system will crash.


Make Your Function Prototypes File

For this example, the prototypes would be:
    long __saveds __asm LIBCube(register __d0 long num);
    long __saveds __asm LIBCount(void);
    void __saveds __asm LIBFunStuff(register __d0 long num, 
                                     register __a0 char *text);

Note the compiler directives __saveds, __asm and register __d0. 
The __saveds directive is used to define a subroutine that loads
up the global base pointer into a4 upon entry.  That way the
small-data model can be used for compilation.  The __asm
directive is used to define a subroutine which takes its
parameters in a specific register.  The register __d0 directive
actually tells which register to use for the parameter.


From the Prototypes, Make a .fd File

The .fd file is used to make #pragma statements for the compiler
such that programs can call your library.  It also is used by the
linker (BLink) to make the library table.  For this example the
.fd file would be:

    ##base _ExampleLibBase
    ##bias 30
    Cube(num)(A0)
    Count()
    FunStuff(num,text)(D0/A0)
    ##end

The first line describes the library base name.  The second line
is the standard table bias needed for a library.  The next three
lines give the function call names and the registers they use (if
any).


Set Up the Custom Init and Expunge Code For Your Library

The Init code should initialize anything special that may be
needed by the library.  This could include opening other
libraries that your library might be calling or initializing data
structures it may need.  In this example, there is a global
semaphore that is initialized during the Init routine and a few
other libraries that are opened.  

Expunge frees anything allocated by the Init code.  Our example
library closes the libraries that  it uses.  Refer to the file
Examplelib_Custom.c to see how this is done.

Note that these are custom routines specific to the library. 
These are called by the generic initialize and expunge entries
which are part of every library.  The generic initialization,
open, close, and expunge routines are listed in the libinit.c
file.


Write Your Library Functions

Of course our library functions will need some code to execute. 
The code for this example is listed in the file Examplelib.c.


Put Revision Information Into Header Files

The library revision number is important for keeping track of
versions and helps  insure future compatibility for the library. 
For this example, the revision information  appears in the header
files Examplelib_rev.h and Examplelib_rev.i.


Compile the Library

Once you have all the required files for a library, compile it. 
Then copy the resulting library file to the LIBS: directory. 
(Note that you can put the library anywhere you want so long as
you give the complete path name to the library in the
OpenLibrary() call.)

The lmk file for our example library is listed below.  Keep in
mind  that BLink uses the name of the binary file that you
compile as the name of the library.  For this example, the name
is Examplelib.library.

Test the Library

You should test and debug the library as a normal C linked
library before compiling it into the final disk-loadable form. 
Once the functions are debugged, create the .library version and
do the final testing.  A test program for the example library
called libtest.c is listed below.

When you are making changes to the disk-loadable version of a
library, you will need to cause a system expunge in order to
flush the old version of the library from the system so that the
new one can be tried. To do this you would first make sure all
programs have closed the old library, place the new version of
the library in the LIBS: directory, and finally, cause a system
expunge.  This can be done with the avail -flush command from the
CLI or with the Workbench debug-menu option, flushlibs.  Note
that you must use loadwb -debug in you startup script to get the
Workbench debug-menu.

The Example Library Files

Altogether there are 13 files in this example library.

1) examplelib.c
This is the place where the library function call code is.  This
can be more than one module, but for this example is just one.

2) examplelib.h
Header file for ExampleLib.c that is shared with
examplelib_custom.c in defining any and all global data.  Note
that global data that is modified needs to be protected via
semaphores.

3) examplelib_custom.c
This is the custom library initialization and expunge code.  The
routines in here are used to set up library-specific items upon
loading of the library and to clean up when an expunge occurs. 
The routines here are called by the standard library
initialization and expunge entries of the library
in the libinit.c file.

4) examplelib_custom.h
This is the prototype header for the examplelib_custom.c
initialization and expunge code.

5) examplelib_lib.fd
This is the .fd file (function definition) for the library.  It
defines the function names, their position in the library vector
table, and the registers that their parameters are passed in.  It
also is used to generate the #pragmas in the examplelib_pragma.h
file and to tell the linker how
to build the library itself.


6) examplelib_proto.h
These are ANSI style prototypes for the library functions.

7) examplelib_rev.h
This header file contains the C-language definitions of the
library version.

8) examplelib_rev.i
This is the assembly-language definitions of the library version.

9) libent.a
This is the Resident structure (sometimes referred to as a
RomTag) which is required to be in the first hunk of libraries or
other system-objects. It  identifies the object and tells the
system how to initialize it (i.e. the address of the
initialization code).  For more on the Resident structure, refer
to the ROM Kernel Manual: Libraries and Devices, page 234, or see
the exec/resident.h and .i files in the Includes and Autodocs
Manual.

10) endtag.a
This is just a label that is linked into the end of the file so
that the RomTag can point to the end of the RomTag hunk.

11) libinit.c
These are the library initialization, expunge, open, and close
routines required in all libraries.  In general, this file will
remain the same regardless of changes you might make to the
library.  The initialization and expunge routines here will call
the custom library initialization routines in the
examplelib_custom.c file.  Any special setup you might need would
be done in that file.

12) libtest.c
Example code to call the library routines.  This just tests the
library calls and shows that they work.

13) lmkfile
This is the  makefile rules for the Lattice LMK program.  This
will produce all of the files needed.  To run the lmkfile and
compile the library, type lmk at the CLI.  You must be in the
directory containing all the library files when you give the
command.




Important Compilation Notes

To make this library compile correctly you will need to modify
one of the files that came with Lattice C.  The file
INCLUDE:proto/exec.h needs to have the #pragma syscall changed to
#pragma libcall SysBase. This needs to be done to all of the
#pragmas in the exec.h file.

For example, you would change:

    #pragma syscall AllocMem c6 1002

to this:

    #pragma libcall SysBase AllocMem c6 1002

This change tells the compiler to load ExecBase not from
AbsExecBase but from the local SysBase which was initialized at
the start of the code.   This change is required in order to make
the -ml (make library) option of Lattice work correctly.  It also
reduces the amount of access to MEMF_CHIP type of memory since
AbsExecBase (location 4) will no longer be referenced.  This can
make for a speed improvement if SysBase is in MEMF_FAST memory
and you are doing complex graphics with lots of video-DMA; the
processor won't be competing with the graphics chips for access
to chip RAM.

Since we are using SysBase instead of AbsExecBase, the following
line must be added to the
INCLUDE:proto/exec.h file:

    extern struct ExecBase *SysBase;

Anywhere near the start of the file is fine.  A complete listing
of all 13 modules in the example library is given on the
following pages.




/*
 *	1) examplelib.c - example shared library to be made by blink 5.04
 *
 * These are the actual library functions...
 */

#include <exec/types.h>
#include <exec/libraries.h>
#include <intuition/intuition.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>

#include <dos.h>

/*
 * This is the file that contains the few global data value
 * definitions...
 */
#include "examplelib.h"

/*
 * Define the place where the library base is...
 */
#define ExampleLibBase (getreg(REG_A6))

/*
 * Prototypes for the library functions
 */
#include "examplelib_proto.h"
#include "examplelib_pragma.h"

/*
 * We will define the prefix for our library vectors to be
 * _LIB so that the Cube call will call the real _LIBCube
 * vector in sted of calling the function directly.
 *
 * This is important as if the call to the function directly
 * was used, it would be impossible to "SetFunction" the function
 * and have ALL users of that function use the new one.
 *
 * Since C automatically adds the _ in front of labels,
 * we just need to have LIB defined...
 */

long __saveds __asm LIBCube(register __d0 long num);
long __saveds __asm LIBCount(void);
void __saveds __asm LIBFunStuff(register __d0 long num,register __a0 char *name);

/*  Blink will plunk globals into the library base: */
long Invocations = 314;

/* This helps seed the random number generator */
static ULONG RandomValue = (long)&RandomValue;

/*
 * First external routine
 */
long __saveds __asm LIBCube(register __d0 long num)
{
    return(num*num*num);
}

/*
 * Second external routine that does not take arguments...
 */
long __saveds __asm LIBCount(void)
{
long trivialtest;

    /*
     * Note that we call the LIBCube() function via Cube()
     * This makes this call go through the standard library
     * vectors.
     */
    trivialtest = Cube(5);

    /*
     * Since we will be modifying public data, we need
     * to protect it.  That is what the MyDataSemaphore is.
     * Before using any public data that could be modified,
     * we grab the semaphore.
     *
     * Note that using the semaphore method is much better
     * than Forbid()/Permit() for overall system performance.
     * However, you can not use the Semaphore if you need to
     * call the function from within a Forbid()...  (Since
     * ObtainSemaphore() could block...)
     */
    ObtainSemaphore(&MyDataSemaphore);
    Invocations++;

    /*
     * Note that we will copy the value into another "stack" variable
     * as after we release the semaphore the value may change.
     */
    trivialtest=Invocations;
    ReleaseSemaphore(&MyDataSemaphore);

    return(trivialtest);
}

/*
 * This routine is internal and uses the RandomValue global seed.
 */
ULONG RandomNext(void)
{
ULONG NewRandom;

	ObtainSemaphore(&MyDataSemaphore);
	NewRandom=RandomValue;
	RandomValue=(((NewRandom<<13)^(NewRandom>>11))+NewRandom)>>1;
	ReleaseSemaphore(&MyDataSemaphore);
	return(NewRandom);
}

/*
 * This is an internal routine that just draws some stuff in the
 * window's RastPort.
 */
void DrawStuff(struct Window *win)
{
short x;
short y;

	x=win->Width-win->BorderLeft-win->BorderRight-1;
	y=win->Height-win->BorderTop-win->BorderBottom-1;

	SetDrMd(win->RPort,JAM1);
	SetAPen(win->RPort,RandomNext()%4);
	Move(win->RPort,(RandomNext() % x) + win->BorderLeft,
			(RandomNext() % y) + win->BorderTop);
	Draw(win->RPort,(RandomNext() % x) + win->BorderLeft,
			(RandomNext() % y) + win->BorderTop);
}

/*
 * Third external call that does some fun stuff...
 */
void __saveds __asm LIBFunStuff(register __d0 long num,register __a0 char *name)
{
register short flag=TRUE;
register struct Window *w;
register struct IntuiMessage *msg;
         struct NewWindow nw;

	nw.LeftEdge=num;
	nw.TopEdge=num;
	nw.Width=486-num;
	nw.Height=143-num;
	nw.DetailPen=0;
	nw.BlockPen=1;
	nw.IDCMPFlags=CLOSEWINDOW|INTUITICKS;
	nw.Flags=WINDOWDRAG|WINDOWDEPTH|WINDOWCLOSE|NOCAREREFRESH|ACTIVATE;
	nw.Title=name;
	nw.FirstGadget=NULL;
	nw.MinWidth=nw.Width;
	nw.MinHeight=nw.Height;
	nw.MaxWidth=nw.Width;
	nw.MaxHeight=nw.Height;
	nw.Type=WBENCHSCREEN;

	if (w=OpenWindow(&nw))
	{
		while(flag)
		{
			DrawStuff(w);
			WaitPort(w->UserPort);
			if (msg=(struct IntuiMessage *)GetMsg(w->UserPort))
			{
				if (msg->Class==CLOSEWINDOW) flag=FALSE;
				ReplyMsg((struct Message *)msg);
			}
		}
		CloseWindow(w);
	}
}
-------------------------------------------------------------------------
/*
 *	2) examplelib.h - example shared library to be made by blink 5.04
 *
 * These are the library globals...
 */

#include <exec/types.h>
#include <exec/semaphores.h>

/*
 * This is the semaphore that we will use to protect the data
 */
extern struct SignalSemaphore MyDataSemaphore;

/*
 * This is a global data variable.
 */
extern long Invocations;



------------------------------------------------------------------------------
/*
 * 3) examplelib_custom.c
 *
 * Custom library initialization goes here...
 *
 * This contains the custom LibInit and LibExpunge routines for
 * the library we are making...
 */

#include <exec/types.h>
#include <exec/alerts.h>
#include <proto/exec.h>

#include <dos.h>

/*
 * Include the prototypes for the custom init and expunge vectors...
 */
#include "examplelib_custom.h"

/*
 * Global data definition
 */
#include "examplelib.h"

/*
 * These are the libraries that we call from within our library.
 */
struct ExecBase *SysBase = NULL;
struct GfxBase *GfxBase = NULL;
struct IntuitionBase *IntuitionBase = NULL;

struct SignalSemaphore MyDataSemaphore;

/*
 * This is the custom LibInit code.
 *
 * This is where the library specific initialization code should be.
 *
 *  *** NOTE ***  Note that it is best to use a little stack
 *                as possible as you are being called from some
 *                other process's context.
 *
 *                Also note that the *FIRST* thing that must happen
 *                in this routine is to set up SysBase as shown...
 */
BOOL __saveds __asm CustomLibInit(register __a6 struct Library *libbase)
{
    /* Get SysBase set up... this MUST happen *FIRST* */
    SysBase=(struct ExecBase *) (* ((ULONG *)4) );

    /* Initialize our semaphore */
    InitSemaphore(&MyDataSemaphore);

    /*  Get the libraries we rely on */
    GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",33L);
    if (!GfxBase)
    {
        Alert(AG_OpenLib|AO_GraphicsLib,(APTR)libbase);
        /* !!! Bryce says nobody frees libbase ! */
        return ( FALSE );
    }

    IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",33L);
    if (!IntuitionBase)
    {
        Alert(AG_OpenLib|AO_Intuition,(APTR)libbase);
        /* !!! Bryce says nobody frees libbase! */
        /* !!! I'm not closing GfxBase, unless expunge is called! */
        return ( FALSE );
    }
    return ( TRUE );
}

/*
 * This is the custom LibExpunge code.
 *
 * This is where the library specific expunge code should be placed.
 *
 *  *** NOTE ***  This routine *MUST* *NEVER* *BREAK* *FORBID*
 *                That is, it must not do *ANYTHING* that could
 *                cause a Wait() to happen...
 *
 *                Also note that it is best to use a little stack
 *                as possible as you are being called from some
 *                other process's context.
 */
void __saveds __asm CustomLibExpunge(register __a6 struct Library *libbase)
{
    /* Close the libraries it uses: */
    if (IntuitionBase)
    {
        CloseLibrary((struct Library *)IntuitionBase);
        IntuitionBase=NULL;
    }
    if (GfxBase)
    {
        CloseLibrary((struct Library *)GfxBase);
        GfxBase=NULL;
    }
}

----------------------------------------------------------------------------
/* 4) examplelib_custom.h 
 *
 * The prototypes for the custom library init and expunge
 */
BOOL __saveds __asm CustomLibInit(register __a6 struct Library *libbase);
void __saveds __asm CustomLibExpunge(register __a6 struct Library *libbase);

----------------------------------------------------------------------------

/* 5) example _lib.fd            */

##base _ExampleLibBase
##bias 30
Cube(num)(D0)
Count()
FunStuff(num,name)(D0/A0)
##end

-------------------------------------------------------------------------------
/*
 *  6) examplelib_proto.h - Prototypes for the example library
 */
long Cube(long);
long Count(void);
void FunStuff(long,char *);

-----------------------------------------------------------------------------
/*
 * 7) examplelib_rev.h
 */

#define	VERSION		36
#define	REVISION	1
#define	DATE	"27 Nov 1989"
#define	VERS	"examplelib 36.1"
#define	VSTRING	"examplelib 36.1 (27 Nov 1989)\n\r"

-----------------------------------------------------------------------------
/*
 * 8) examplelib_rev.i
 */

VERSION		EQU	36
REVISION	EQU	1
DATE	MACRO
		dc.b	'27 Nov 1989'
	ENDM
VERS	MACRO
		dc.b	'examplelib 36.1'
	ENDM
VSTRING	MACRO
		dc.b	'examplelib 36.1 (27 Nov 1989)',13,10,0
	ENDM


-----------------------------------------------------------------------------
***
*
* 9) libent.asm - Modified Lattice Library Resident (RomTag) Structure
*
***
	include	'exec/types.i'
	include	'exec/resident.i'
	include	'exec/nodes.i'
	include	'exec/libraries.i'
***
*
* The following include is used to store the library version and name
* as follows:
*
* VERSION		EQU	36
* REVISION		EQU	1
* DATE		MACRO
*			dc.b	'27 Nov 1989'
*		ENDM
* VERS		MACRO
*			dc.b	'examplelib 36.1'
*		ENDM
* VSTRING	MACRO
*			dc.b	'examplelib 36.1 (27 Nov 1989)',13,10,0
*		ENDM
*
***
	include 'examplelib_rev.i'
***
*
* Library init priority.  For user (NON-ROM) libraries, should be 0
*
***
PRI	equ	0
***
*
* Define some the external references...
*
***
	xref	__LibName
	xref	RESLEN
	xref	_BSSBAS		; linker defined base of BSS
	xref	_BSSLEN		; linker defined length of BSS
	xref	_LibFuncTab
	xref	__LibInitTab
	xref	endtag
	xref	_LibInit
	xdef	__LibRomTag
	xdef	__VerString
***
*
* This is the first hunk and is where the RomTag is needed...
*
***
	SECTION	text,CODE	; romtag must be in first hunk
***
*
* We need to make sure that if some user tries to execute this
* file as an executable that we return an error...
*
***
	moveq	#20,d0
	rts
***
*
* Place the version string into the tag.  This makes the
* version displayable.  It also makes it possible to "TYPE"
* the library file to find out the version...
*
***
__VerString:	VSTRING
***
*
* The RomTag that is needed for a library
*
***
	cnop	0,2		; We need to word-align the RomTag
__LibRomTag:
	dc.w	RTC_MATCHWORD
	dc.l	__LibRomTag
	dc.l	endtag
	dc.b	RTF_AUTOINIT
	dc.b	VERSION
	dc.b	NT_LIBRARY
	dc.b	PRI
	dc.l	__LibName
	dc.l	__VerString
	dc.l	__LibInitTab
***
*
* Start of the merged data area that the linker puts
* into the library structure for the global data.
*
***
	section	__MERGED,data
	xdef	__Libmergeddata
__Libmergeddata	dc.l	0
	end


---------------------------------------------------------------------------
*
*   10) endtag.a  -       For RomTag EndSkip
*
        section text,data       ; so it is dead-last (data hunk)

        XDEF    endtag
endtag:

        END

---------------------------------------------------------------------------
/*
 * 11) libinit.c
 *
 * This is the standard library init...
 *
 * It calls the custom init routines in examplelib_custom.c
 * The routines in this file should not be changed.
 *
 * Portions of this code were generated from Lattice examples...
 */
#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/resident.h>
#include <exec/libraries.h>
#include <libraries/dos.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <string.h>

/*
 * This includes the revision information
 */
#include "examplelib_rev.h"

/*
 * Include the custom init and expunge headers...
 *
 * The MUST be of the type:
 * BOOL __asm __saveds CustomLibInit(register __a6 struct Library *libbase);
 * void __asm __saveds CustomLibExpunge(register __a6 struct Library *libbase);
 *
 */
#include "examplelib_custom.h"

struct MyLibrary
{
    struct  Library ml_Lib;
    ULONG   ml_SegList;
    ULONG   ml_Flags;
    APTR    ml_ExecBase;        /* pointer to exec base */
    LONG    ml_Data;                /* Global data */
};

typedef LONG (*PFL)();   /* pointer to function returning 32-bit int        */

/* library initialization table, used for AUTOINIT libraries                */
struct InitTable
{
    ULONG   *it_DataSize;       /* library data space size         */
    PFL     *it_FuncTable;      /* table of entry points           */
    APTR    it_DataInit;        /* table of data initializers      */
    PFL     it_InitFunc;        /* initialization function to run  */
};

/* my function table (Generated by Blink) */
extern PFL _LibFuncTab[];

extern char __far RESLEN;
extern long __far NEWDATAL;	/* Generated by BLINK */

#define DATAWORDS ((long)&NEWDATAL)

/* We need to define this for the following table */
ULONG __asm _LibInit(register __a0 APTR seglist,register __d0 struct MyLibrary *libbase);

struct InitTable __far _LibInitTab =
{
    (long *)(&RESLEN+sizeof(struct MyLibrary)),
    _LibFuncTab,
    NULL,			/* will initialize my own data */
    _LibInit,
};

/* Need this to determine start of MERGED DATA */
extern long far _Libmergeddata;

/* Supplied by BLink */
extern char __far _LibName[];

/* Supplied as part of our libent.a */
extern char __far _VerString[];

/*
 * The following #define is to give the EXEC call #pragmas
 * (which were changed to "libcall SysBase" to use the
 * absolute location known also as AbsExecBase.  This is
 * needed during the base level expunge call...
 */
#define SysBase (*((ULONG *)4))

/*
 * This function is called when the library is first loaded.
 * It does the library structure initialization and calls
 * the custom library initialization routine which should
 * setup any custom library stuff and open any other libraries
 * that will be needed.
 *
 * Note that this routine can not change as the setup must
 * happen in this order.
 */
ULONG __asm _LibInit(register __a0 APTR seglist,register __d0 struct MyLibrary *libbase)
{
long *sdata, *reloc;
char *ddata;
long nrelocs;

    libbase->ml_SegList = (ULONG) seglist;

    /* init. library structure (since I don't do automatic data init.) */
    libbase->ml_Lib.lib_Node.ln_Type = NT_LIBRARY;
    libbase->ml_Lib.lib_Node.ln_Name =  _LibName;
    libbase->ml_Lib.lib_Flags = LIBF_SUMUSED | LIBF_CHANGED;
    libbase->ml_Lib.lib_Version = VERSION;
    libbase->ml_Lib.lib_Revision = REVISION;
    libbase->ml_Lib.lib_IdString = (APTR) _VerString;

    /* The +4 is a wasted long word, where _Libmergeddata is. */
    ddata = (char *)&libbase->ml_Data+4;
    sdata = (long *)&_Libmergeddata;
    memcpy(ddata, (char *)sdata, DATAWORDS*4);

    sdata = sdata + DATAWORDS;
    nrelocs = *sdata++;
    while (nrelocs > 0)
    {
        reloc = (long *)((long)ddata + *sdata++);
        *reloc += (long)ddata;
        nrelocs--;
    }

    if (CustomLibInit((struct Library *)libbase)) return((ULONG)libbase);
    else return(NULL);
}

/*
 * This is the entry point that is called when the system
 * wishes that the library free itself.  The library should
 * only free itself if there are no outstanding open libraries.
 * This routine calls the custom expunge routine where any
 * custom resources that need to be freed are.
 *
 *  *** NOTE ***  This routine *MUST* *NEVER* *BREAK* *FORBID*
 *                That is, it must not do *ANYTHING* that could
 *                cause a Wait() to happen...
 *
 *                Also note that it is best to use a little stack
 *                as possible as you are being called from some
 *                other process's context.
 */
ULONG __saveds __asm _LibExpunge( register __a6 struct MyLibrary *libbase )
{
ULONG seglist = 0;
LONG  libsize;

    libbase->ml_Lib.lib_Flags |= LIBF_DELEXP;
    if ( libbase->ml_Lib.lib_OpenCnt == 0 )
    {
        CustomLibExpunge((struct Library *)libbase);

        /* really expunge: remove libbase and freemem */

        seglist = libbase->ml_SegList;
        Remove( (struct Node *) libbase);

        libsize = libbase->ml_Lib.lib_NegSize + libbase->ml_Lib.lib_PosSize;
        FreeMem( (char *) libbase - libbase->ml_Lib.lib_NegSize,(LONG) libsize );
    }

    /* return NULL or real seglist */
    return ( (ULONG) seglist );
}

/*
 * This is the entry point that is called when the library is
 * opened by an application.  It mainly changes the OpenCount
 * and the delayed expunge flags...
 */
LONG __asm _LibOpen(register __a6 struct MyLibrary *libbase)
{
    /* mark us as having another customer */
    libbase->ml_Lib.lib_OpenCnt++;

    /* clear delayed expunges (standard procedure) */
    libbase->ml_Lib.lib_Flags &= ~LIBF_DELEXP;

    return((LONG)libbase);
}

/*
 * This is the entry point that is called when the library is
 * closed by an application.  The main use here is to change the
 * open count and to check for delayed expunge calls...
 */
ULONG __asm _LibClose( register __a6 struct MyLibrary *libbase )
{
ULONG retval = 0;

    if (( --libbase->ml_Lib.lib_OpenCnt == 0 ) &&
                    ( libbase->ml_Lib.lib_Flags & LIBF_DELEXP ))
    {
        /*
         * no more people have me open,
         * and I have a delayed expunge pending
         */
        retval = _LibExpunge( libbase ); /* return segment list */
    }

    return (retval);
}


---------------------------------------------------------------------------
/*
 *  12) libtest.c
 */

#include <proto/exec.h>

#include "examplelib_proto.h"
#include "examplelib_pragma.h"

struct Library *ExampleLibBase;

main()
{
    if (ExampleLibBase = OpenLibrary("examplelib.library", 0L))
    {
        FunStuff(8,"Test with num==8");
        printf("ExampleLibBase at $%lx\n", ExampleLibBase);
        printf("Cube 5 = %ld\n", Cube(5));
        printf("Invocations: %ld\n", Count());
        FunStuff(12,"Test with num==12");
        CloseLibrary(ExampleLibBase);
    }
    else printf("examplelib.library did not open...\n");
}

------------------------------------------------------------------------
#
# 13) lmkfile
#
# These are the files for the library.
# You MUST have a xxx_custom.o for the init/expunge part of the library
#
OFILES=examplelib_custom.o examplelib.o

#
# These are files needed for the library initialization
# They should not need to be changed other than the headers
#
LIBOFILES=libent.o libinit.o

#
# These are the flags needed to compile library routines...
#
CFLAGS=-ml -cfist -v -d2

#
# The flags needed to assemble the RomTag and EndTag...
#
AFLAGS=-iINCLUDE:

#
# Default rules...
#
.c.o:
	lc $(CFLAGS) $*

.a.o:
	asm $(AFLAGS) $*

#
# The whole world...
#
all: examplelib_pragma.h examplelib.library libtest

#
# This is a little LMK trick for blink...
#
# Note that the LIBID is a place to put copyright notices or other
# information that you may wish to have as part of the library...
#
examplelib.library: $(OFILES) $(LIBOFILES) examplelib_lib.fd endtag.o lmkfile
	blink <with <
LIBID "Example Library of no real use..."
LIBPREFIX _LIB
LIBFD examplelib_lib.fd
TO examplelib.library
FROM $(LIBOFILES) $(OFILES)
LIB lib:lc.lib endtag.o
SMALLCODE SMALLDATA NODEBUG
<

examplelib_pragma.h: examplelib_lib.fd
	fd2pragma examplelib_lib.fd examplelib_pragma.h

libtest: libtest.c examplelib_pragma.h
	lc -L -d2 libtest.c

libent.o: libent.a examplelib_rev.i

libinit.o: libinit.c examplelib_rev.h

endtag.o: endtag.a

