Shared Libraries in MagiC from V6.00 onwards
Note: Shared libraries were introduced already with MagiC Version 5.20. Due to a design error, they had to change the format of the library as of Version 6.00. As different "magic" IDs are used this can not cause crashes; it's just that the libraries for 6.00 will not be recognized by the old version 5.20, and vice versa. Due to several inadequacies in version 5.20 one should in any case only create libraries for 6.00.
Usually libraries with frequently used procedures are collected in their own object module and then linked with several programs during compilation. In that case each program receives a copy of the library, which is integrated firmly into the PRG file. On the other hand, shared libraries exist as separate files only once on the hard disk and can be used by several programs, even simultaneously.
Compared to hard-linked libraries, using shared libraries results in a series of advantages:
Naturally there are also disadvantages:
To start with, one needs the object module SLB_BIND for calling the two new DOS functions, for which one has to link the file SLB.H.
For each library used one declares a descriptor of the type SHARED_LIB and a function pointer of the type SLB_EXEC.
Each library is opened with Slbopen (opening and closing should only take place in user-mode), during which the following parameters are passed:
char *name | The name of the library, in capitals, including the extension (".SLB"). The library name is also the filename. |
char *path | If this parameter is not NULL, then first of all a search will
be made for the library (the path must end with '\' in MagiC 5.20;
this is no longer necessary in MagiC 6). The path should be an
absolute one, so that the shared library knows where it lies. If the
parameter is NULL or the library was not found in the specified path,
then a search is made in the XTENSION folder.
From MagiC 6 onwards the environmental variable SLBPATH will be evaluated. Like PATH it contains a list of search-paths, each separated by ';'. If the variable is defined no additional search will be made in the XTENSION folder. |
LONG min_ver | Minimum required version number of the library.
If a program needs, say, version 3 but the library present is only version 2, then ERANGE will be returned. The return value will be the actual version number of the library. |
SHARED_LIB *sl | Pointer to the descriptor. When the library is opened the descriptor will be entered here. |
SLB_EXEC *fn | Pointer to the function pointer. When the library is opened the function pointer will be entered here. |
Return values can be:
>= 0 All OK, version number of the library ERANGE Version number too low EACCDN Library already opened by this process EFILNF Library not found ENSMEM Insufficient memory free ... Other error-codes.
The library can now be used and finally closed again with Slbclose. This is not absolutely necessary as all open libraries are closed automatically at program termination, but it is good programming practice. On no account may a library be closed more than once, since the kernel can not recognize such errors.
Some libraries such as EDITOBJC.SLB, for instance, install new system calls, in this case the AES calls 210..217. For these libraries the function pointer is not needed. Otherwise all functions of the library are called via the function pointer. The library call function is declared in the following way:
typedef LONG cdecl (*SLB_EXEC)( SHARED_LIB sl, LONG fn, WORD nargs, ... );
As regretably Pure-C has an error here, I was forced to declare this functions as 'typedef LONG (*SLB_EXEC)( void , ... );'. Unfortunately this inhibits all type checking. So take care!
The call expects the following parameters:
The descriptor of the library A longword (!) for the function number A WORD, that specifies the number of arguments in WORDs (i.e. all "..." arguments) Further arguments, depending on the function.
The best way is to make the call via a macro, which should be defined in a header file for the library, e.g.:
JPEG.H:
#define SLBJPEG_WANDELN (a,b) (*slbjpeg_exec)(slbjpeg, 7L, 4, a, b)
With this slbjpeg_exec and slbjpeg will be ascertained at Slbopen, 7L is the function number for the call WANDELN (convert), 4 describes the length of the following arguments (<a> and <b> are two pointers => 2*4 bytes => 4 WORDs), and a and b are the arguments of the function WANDELN.
If the function is not present (the library contains a NULL-pointer for this function, or the function number is higher than the number of functions actually present), one will receive EINVFN as the function result (in fact this only works correctly from MagiC 6 onwards).
For this too there is a sample library SLB_DEMO, which contains all the required elements and descriptions. The best thing is to copy SLB_DEMO.C, LIBHEAD.S and SLB_DEMO.PRJ and then modify the files to suit your requirements. It is most important to ensure that bit 3 of the flag in the program header of a library is set; one can use PH_BIT3.TTP for this.
LIBHEAD is the header for a shared library. The pointer to the function names can be omitted, otherwise it points to a table of pointers with the names of the library functions. The number of functions must be stated correctly, as must the table of functions and the library name, which is identical to the filename. When adding a function one has to ensure that the function number is adapted accordingly, and perhaps that the version number is increased.
For publicly available shared libraries one has to ensure that documented function calls are never altered! They can either be supplemented with new parameters (the called function can inquire the number of parameters actually passed), or a new function number should be used.
NULL-pointers are also permissible for the function pointers; they return a EINVFN when the function is called.
The following functions for (de-)initialization are obligatory:
slb_init/slb_exit
These are called during loading and removal of the library respectively, and at that in supervisor-mode and in the context (process) of the library. Typically, slb_init loads a configuration file, allocates global memory for the library and opens a virtual VDI workstation. slb_exit writes back the configuration file, releases the memory again and closes the VDI workstation.
If slb_init opens a file, then the handle can only be accessed again at slb_exit, as all other calls of the library run in the context of the calling application.
From MagiC 6 onwards the library is passed a normal 'C' character string in the command line structure which contains the complete path of the shared library. If the shared library has to load configuration or RSC files, the path can be extracted and the filename of the configuration file assembled correspondingly.
If slb_init terminates by reason of a bus error, for instance, then the caller will get EXCPT as the result of the Slbopen call. In order to intercept an unplanned termination of the library, the kernel installs an etv_term handler for the library before calling slb_init/exit.
slb_open/slb_close
These are called at the opening or closing of the library respectively. Once the library has been opened, the order is:
slb_init() slb_open() slb_close() slb_exit()
Unlike slb_init/slb_exit, slb_open/slb_close run in the context of the caller and in user-mode, with the user-stack of the caller, even if the Slbopen call was performed in supervisor-mode.
The library can also allocate memory at slb_open, though this belongs to the caller and should be released again at slb_close. In order to permit allocation of this reserved memory to the caller, the library is also passed the current process descriptor at slb_open, slb_close and at every function call.
Warning: Due to a bug in 5.20, the passing of the PD to slb_open and slb_close only works from MagiC 6 onwards.
The kernel ensures that the open/close calls are nested correctly, i.e. a process can not open a library more than once.
The kernel ensures that the open/close calls are nested correctly, i.e. a process cannot open a library more than once.
Functions
Functions are not obligatory, so a library can hook in system calls also via the AES or DOS that are removed again at termination, yet generally functions are made available.
A function is called with the following parameters on the stack:
PD *pd | Process descriptor of the caller, corresponds to the associated slb_open()/close() |
LONG fn | Function number. Practical when several functions are amalgamated (identical function pointers in LIBHEAD) |
WORD nargs | Number of the following arguments, in WORDs. If a function has a variable number of parameters, one can ascertain the actual number. Very useful for extensions, without having to incorporate new functions. Example: If a function always expects a pointer, but optionally also a WORD, it will receive either 2 or 3 as nargs. |
... | The remaining parameters |
The functions are executed in the context of the caller and with its stack. As this call is generally made in user-mode, multitasking will not be interrupted even for longer operations. Depending on the function, the function result can be a LONG, a WORD, void etc.
A function may alter registers d0-d2 and a0-a1, all other registers have to be saved if appropriate. In particular, register a2 must not be altered, so that Pure-C routines may be called.
/* * * Body of a "shared library" * * Andreas Kromke * 22.10.97 * */ #include <portab.h> #include <tos.h> #include <tosdefs.h> #pragma warn -par typedef void *PD; char *mem; /* Here global memory */ /***************************************************************** * * The init-function is called once during loading the library. * For this it runs as a process of the library, i.e. files can * be opened and memory can be reserved that belong to the library * in each case. * Warning: The files opened in this way may _NOT_ be accessed * by the library functions, because these run in the * context of the caller. * * Warning: The init-function runs in supervisor mode, as a * library generally does not have a user stack. * Hence it must not use too much stack space (max. 1kB) * and may not run too long (because multitasking is * blocked in supervisor mode). * But if necessary a user stack can be allocated and one * can then switch to user mode. * *****************************************************************/ extern LONG cdecl slb_init( void ) { mem = Malloc(4096L); if (mem) return(E_OK); else return(ENSMEM); } /***************************************************************** * * The exit-function is called once during releasing the library. * For this it runs as a process of the library, i.e. files can * be opened and memory can be reserved that belong to the library * in each case. * * Warning: The exit-function runs in supervisor mode, as a * library generally does not have a user stack. * Hence it must not use too much stack space (max. 1kB) * and may not run too long (because multitasking is * blocked in supervisor mode). * But if necessary a user stack can be allocated and one * can then switch to user mode. * *****************************************************************/ extern void cdecl slb_exit( void ) { Mfree(mem); } /***************************************************************** * * The open-function is called once during opening of the library * by an application process. For this it runs as a process of the * caller, i.e. files can be opened and memory can be reserved that * belong to the caller in each case. * * The kernel ensures that each process does not open the library * several times, and that the library is always closed properly. * * Warning: The open-function runs in user mode, and actually with * the user stack of the caller. This means that the caller, * even when running in supervisor mode, always has to make * a sufficiently large usp available. * *****************************************************************/ extern LONG cdecl slb_open( PD *pd ) { return(E_OK); } /***************************************************************** * * The close-function is called once during closing of the library * by an application process. For this it runs as a process of the * caller, i.e. files can be opened and memory can be reserved and * released, that belongs to the caller in each case. * * Warning: The close-function runs in user mode, and actually with * the user stack of the caller. This means that the caller, * even when running in supervisor mode, always has to make * a sufficiently large usp available. * *****************************************************************/ extern void cdecl slb_close( PD *pd ) { } /***************************************************************** * * An example library function. * It is executed in the context of the caller, and actually with * the stack of the caller (depending on status usp or ssp). * * It is strongly recommended to call the functions of an SLB only in * user mode, so as to ensure compatibility to later implementations. * *****************************************************************/ extern LONG cdecl slb_fn0( PD *pd, LONG fn, WORD nargs, char *s ) { Cconws(s); Cconws("\r\n Key: "); Cconin(); return(E_OK); }
/* * * Header for a "shared library" * This is used in place of the start-code of PureC * * It is _imperative_ to ensure that bit 3 of the flags * in the program header is set, so that the library * does not reserve the whole of the available memory. * * Warning: The SLB-concept of MagiC 5.20 still had an error, * which I have fixed in 6.00. For this the makeup had to be * modified slightly. SLBs for MagiC 6 therefore do not run * with those for MagiC 5.20 and vice versa. * * Andreas Kromke * 10.2.98 * */ XREF slb_init XREF slb_exit XREF slb_open XREF slb_close XREF slb_fn0 TEXT DC.L $70004afc ; Magical value (5.20: $42674e41) DC.L name ; Pointer to name of library DC.L 1 ; Version number DC.L 0 ; Flags, at present 0L DC.L slb_init ; Is called after loading DC.L slb_exit ; Is called before removing DC.L slb_open ; Is called at opening DC.L slb_close ; Is called at closing DC.L 0 ; Pointer to procedure name (optional) DC.L 0,0,0,0,0,0,0,0 ; Unused, always NULL DC.L 1 ; Number of functions (5.20: .W) DC.L slb_fn0 ; Function #0 name: DC.B 'DEMO.SLB',0 END
slb_demo.slb .L[-S=256] ; Minimum stack = libhead.s ; Startup code for SLBs slb_demo.c (slb.h) pctoslib.lib pcstdlib.lib
/* * * Program for the manipulation of bit 3 in the program header. * MagiC needs this bit, which is generally 0, to assign to * the program only the minimum amount of memory that is needed, * i.e. only basepage+text+data+symbol+BSS. * Is needed in particular for all SharedLibraries. * * Andreas Kromke * 25.10.97 * */ #include <portab.h> #include <tos.h> #include <tosdefs.h> #include <stdio.h> /* ProgramHeader, Program header for executable files */ /************************************************************************/ typedef struct { WORD ph_branch; /* 0x00: Must be 0x601a */ LONG ph_tlen; /* 0x02: Length of TEXT segment */ LONG ph_dlen; /* 0x06: Length of DATA segment */ LONG ph_blen; /* 0x0a: Length of BSS segment */ LONG ph_slen; /* 0x0e: Length of the symbol table */ LONG ph_res1; /* 0x12: Required by PureC */ LONG ph_prgflags; /* 0x16: Bit 0: Don't clear heap */ /* Bit 1: Load into FastRAM */ /* Bit 2: Malloc from FastRAM */ /* Bit 3: Only t+d+b+s (MagiC 5.20) */ /* Bit 4,5,6,7: Memory protection */ /* Bit 8: Unused */ /* Bit 9: Unused */ /* Bit 10: Unused */ /* Bit 11: SharedText (MiNT) */ /* Bit 12: Unused */ /* Bit 13: Unused */ /* Bit 14: Unused */ /* Bit 15: Unused */ /* Bits 31..28: TPA size */ /* (times 128k + 128k: Min. heap size) */ WORD ph_absflag; /* 0x1a: Non-zero => do not relocate */ } PH; WORD main( WORD argc, char *argv[] ) { PH ph; WORD ret = 0; LONG err; WORD f; for (argc--,argv++; argc; argc--,argv++) { Cconws("File: "); Cconws(*argv); err = Fopen(*argv, RMODE_RW); /* Open for reading + writing */ f = (WORD) err; /* File handle */ err = Fread(f, sizeof(PH), &ph); if ((err != sizeof(PH)) || (ph.ph_branch != 0x601a)) { err = EPLFMT; goto nextone; } Fseek(0L, f, 0); /* File pointer to the start */ ph.ph_prgflags |= 8; /* Set bit 3 */ err = Fwrite(f, sizeof(PH), &ph); nextone: if (f > 0) Fclose(f); if (err < 0) { printf(" => Error %ld", err); ret = (WORD) err; } Cconws("\r\n"); } return(ret); }
Warning: The function Slbopen contains an additionnal parameter param. This no longer appears in the SLB.H. Best to just simply inore it.
/* * * Binding for the use of a "shared library" * * Andreas Kromke * 22.10.97 * */ #include <mgx_dos.h> /***************************************************************** * * Opens a "shared lib". * * Input: * name Name of the library, including extension * path Search path with '\', optional * min_ver Minimum required version number * Returns: * sl Library descriptor * fn Function for calling a library function * <ret> Actual version number, or error code * *****************************************************************/ LONG Slbopen( char *name, char *path, LONG min_ver, SHARED_LIB *sl, SLB_EXEC *fn, LONG param ) { return(gemdos(0x16, name, path, min_ver, sl, fn, param)); } /***************************************************************** * * Closes a "shared lib". * * Returns: * <ret> EACCDN, if library not opened * *****************************************************************/ extern LONG Slbclose( SHARED_LIB sl ) { return(gemdos(0x17, sl)); }
/* * * Binding for the use of "shared libraries" * * Andreas Kromke * 22.10.97 * Last change 19.2.99 - SLB_EXEC with cdecl corrected * * 19.2.99 * - SLB_EXEC mit cdecl korrigiert * 20.8.99 * - Slbclose korrigiert */ #ifndef LONG #include <portab.h> #endif typedef void *SHARED_LIB; /* old Version: typedef LONG (*SLB_EXEC)( void , ... ); */ typedef LONG cdecl (*SLB_EXEC)( SHARED_LIB sl, LONG fn, WORD nargs, ... ); extern LONG Slbopen( char *name, char *path, LONG min_ver, SHARED_LIB *sl, SLB_EXEC *fn ); extern LONG Slbclose( SHARED_LIB sl );