====================================================================== How to create an Amiga shareable library: ====================================================================== 1) Compile all modules that reference global data with the -ml option. Note that global data will be shared between all processes that open the library. 2) Add the __saveds keyword to any function that is callable from outside the library. 3) Create a .fd describing your library. A sample .fd file is included for reference. 4) Link with the following command: blink LIBFD [LIBPREFIX ] [LIBID ] TO FROM lib:libent.o lib:libinit.o where LIBFD specifies the name of the .fd file you created in step (3). LIBPREFIX specifies an optional prefix to add to the functions listed in the .fd file. The default is simply an underscore (_). If for some reason you don't want to use the #pragmas to access the library, you can use the old method of having assembly-language stubs that accept parameters on the stack like a typical C routine, pop them into registers and call the library function for you. (You must write the stubs yourself in assembler.) If you do this, the library routines cannot have exactly the same names as the entries in the .fd file, since these names will have been taken by the stub routines. The LIBPREFIX option specifies a common prefix to be prepended to the function names in the .fd file. You will be responsible for making sure that the actual function declarations in the library's .c source file match the full name, including any specified prefix. For example, if your library has a function called 'foo' and you specify a LIBPREFIX of _LIB, you should declare the function in your source code as LIBfoo. (Remember that Blink always adds an extra underscore to symbol names.) If you always use the automatically-generated #pragma statements to access the library, you can ignore LIBPREFIX. LIBID specifies an optional library ID string (See _LibID, below). LIBID is purely informational and is never required. TO specifies the resulting library name. libent.o contains the RomTag for the library, and MUST BE THE FIRST OBJECT MODULE LISTED. The source to libent.o (libent.a) is in the SOURCE subdirectory of disk 4 of the compiler distribution. You do not need to modify this file for your library, and probably should not. libinit.o contains the system-required library entry points Open, Close and Expunge as well as other initialization routines, and MUST BE THE SECOND OBJECT MODULE LISTED. The source to libinit.o (libinit.c) is in the SOURCE subdirectory of disk 4 of the compiler distribution. You may modify this file to add custom initialization code if you like, but you are not required to. modules are your module names. They may be in any order as long as they are specified after libent.o and libinit.o. ====================================================================== How to call the library: ====================================================================== Create pragmas for your library with the FD2PRAGMA program, provided on disk 3 of the compiler distribution. FD2PRAGMA will output a .h file containing #pragma statements for all the externally-callable functions in your library. Code that would like to call your library should #include this .h file before referencing any of the library's functions. When one of the library functions is called, the compiler will look at the #pragma for that function and determine which registers the library function wants the parameters in. It will load the proper registers, load your library base in register A6, and call your function. The user code should declare a variable of type 'struct Library *' named after your library base. For example, if your library is called 'mylib.library', and you have chosen to name your library base 'MyLibBase', the declaration in the user's code would look like: struct Library *MyLibBase; The library base can be an automatic or a global variable. Before calling any function in your library, the user must initialize the library base by calling the OpenLibrary function: MyLibBase = OpenLibrary("mylib.library", version); where version is less that equal to the number in the version field of the struct Library (see libinit.c). A version of 0 will open any version of the library. The user should always check to make sure that MyLibBase is not NULL, which would indicate that the library could not be loaded or that the revision was not acceptable. Once the library base pointer is loaded, the user program can call your library functions by name just like any other function. When the program is finished with your library, it should call CloseLibrary: CloseLibrary(MyLibBase); ====================================================================== How It Works: ====================================================================== 1) What the compile options do Modules compiled with -ml do not use register A6, except when calling other libraries, in which case it is restored immediately upon return. Therefore A6 always has a pointer to the current library base. Further, when __saveds is specified, the compiler generates the instruction LEA LinkerDB(A6),A4 in the function prolog. LinkerDB is a symbol generated by Blink that specifies the offset from the library base at which user global data may be stored. Thus, the above instruction sets A4 to point to the library's global data. Notice that the standard executable startup code, c.o, contains a reference to LinkerDB as well. When Blink is linking a library, it knows to interpret LinkerDB differently than when linking a normal program. 2) What the initialization code does The job of the LibInit function in a shareable library is to set up the library base. The LibInit() function defined in libinit.c will in addition copy global data from the place where LoadSeg loaded it (as defined by the Blink-generated symbol _Libmergeddata) to the end of the library base structure. Once there, any data relocations are patched up. From this point on, global data may be referenced relative to A4 since A4 has been set to point at the global data. 2) What Blink does Blink first reads the .fd file and determines the names and number of functions to the library. It creates a jump table of the form: +---------------------------------+ | __LibOpen | | __LibClose | | __LibExpunge | | ROMTAG - 2 | | user defined functions. | +---------------------------------+ The names of the user-defined functions are created by prepending the specified LIBPREFIX to the function names in the .fd file. The symbol _LibFuncTab points to the table, which is placed into the data section. Blink also defines the following symbols for libinit.o to use when initializing the library: _LibName name of library, as specified by the TO option to BLINK. _LibID id for library, as specified by the LIBID option to BLINK. This is a null string if no LIBID option is specified. RESLEN length of data section to be allocated as part of the Library structure. NEWDATAL length of initialized data section. ====================================================================== Library Format: ====================================================================== The library that blink produces looks like this: Code Hunk 1: (comes from libent.o) moveq #0,d0 ; This is in case someone tries rts ; to run the library as a program RomTag dc.w RTC_MATCHWORD ; RomTag information for the library dc.l _LibRomTag ; as described in the Rom Kernel manual dc.l endtag ; | dc.b RTF_AUTOINIT ; | dc.b VERSION ; | dc.b NT_LIBRARY ; | dc.b PRI ; | dc.l _LibName ; | dc.l _LibID ; v dc.l _LibInitTab ; End of RomTag information Code Hunk 2 - n: (comes from libinit.o and user modules) _LibInit _LibOpen _LibClose _LibExpunge user functions MERGED DATA _Libmergeddata ; symbol that tells LibInit where the merged ; data is initially loaded. _LibInitTab ; struct LibInitTab as specified in libinit.c length of global data pointer to function jump table NULL pointer to initialization code (_LibInit) USER GLOBAL DATA For a quick example, refer to the readme file.