.. _loadable_library: ************************* Binary Loadable Libraries ************************* **also called 'Library Blobs'.** Prog8 allows you to create binary library files that contain routines callable by other programs. Those programs can be written in Prog8, BASIC, or something else. They just LOAD the binary library file into memory, and call the routines. An example of a library file loaded in BASIC on the Commander X16: .. image:: _static/x16library.png :align: center (On the Commodore-64 and such, it works identically but you have to type the SYS addresses in decimal notation) Requirements ^^^^^^^^^^^^ Such a loadable library has to adhere to a few rules: It can't use zero page variables Otherwise it might overwrite variables being used by the calling program. For systems that have the 16 'virtual registers' cx16.r0-r15 in zero page: these 16 words are free to use. For other systems, only the internal prog8 zeropage scratch variables can be used. *note: this may be improved upon in a future version* No system initialization and startup code The library cannot perform any regular "system initialization" that normal Prog8 programs usually perform (such as resetting the IO registers, clearing the screen, changing the colors, and other initialization logic). This would disturb the state of the calling program! The library can (must) assume that that the calling program has already done all required initialization. Variable initialization The library still has to initialize any variables it might use and clear uninitialized "BSS" variables! Otherwise the code will not run predictably as prog8 code. So, the library must still have a "start" entrypoint subroutine like any outher prog8 program, that must be called before any other library routine can be called. Binary output and loaded into a fixed memory address The library must not have a launcher such as a BASIC SYS command, because it is not ran like a normal program. Also, because it is not possible to create position independent code with prog8, a fixed load address has to be decided on and the library must be compiled with that address as the load address. ``%output library`` ^^^^^^^^^^^^^^^^^^^ Most of the above requirements can be fulfilled by setting various directives in your source code such as %launcher, %zeropage and so on. But there is a single directive that does it correctly for you in one go (and makes sure there won't be any initialization code left at all): ``%output library`` Together with ``%address`` and possibly ``%memtop`` -to tell the compiler what the load address of the library should be- it will create a "library.bin" file that fulfills the requirements of a loadable binary library program as listed above. For older CBM targets (C64, C128 and PET) the library file *will* have a load address header, because these targets require a header to easily load files. For the other targets such as the Commander X16, the library will be a headerless binary file that can then be loaded given the correct load address. The entrypoint (= the start subroutine) that must be called to initialize the variables, will be the very first thing at the beginning of the library. Jump table ^^^^^^^^^^ For ease of use, libraries should probably have a fixed "jump table" where the offsets of the library routines stay the same across different versions of the library. Without needing new syntax, there's a trick in Prog8 that you can use to build such a jumptable: add a non-splitted word array at the top of the library main block that contains JMP instructions and the addresses of the individual library subroutines. Do NOT change the order of the subroutines in this table! Also note that the Prog8 compiler will insert a single JMP instruction at the very start of the library, that jumps to the start subroutine (= the entrypoint of the library program). Users of the library need to call this to initialize the variables, so it is a required part of the external interface of the library. Because the compiler will place the global word array jumptable immediately after this JMP instruction, it seems as if the very first entry in the jump table is the jump to the start routine. Look at the generated assembly code to see exactly what is going on. But the users of the library are none the wiser and it just seems as if it is part of the jump table in a natural way :-) Loading and using the library ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ These examples below assume the target is the Commander X16. Assuming the load address of the library is $A000: **From BASIC**:: BLOAD "LIBRARY.BIN",8,1,$A000 SYS $A000 : REM TO INITIALIZE VARIABLES, REQUIRED! SYS $A004 : REM CALL FIRST ROUTINE SYS $A008 : REM CALL SECOND ROUTINE, ETC. **From Prog8** The ``diskio`` module actually provides a convenience routine called ``loadlib`` that loads a Prog8-compiled library blob into memory. It internally automatically uses either regular load() or load_raw(), as required by the compilation target (so you don't have to bother with target machine differences if you want to write portable code):: %import diskio main { extsub $A000 = lib_init() clobbers(A) extsub $A004 = lib_func1() clobbers(A,X,Y) extsub $A008 = lib_func2() clobbers(A,X,Y) sub start() { if diskio.loadlib("library.bin", $a000) != 0 { lib_init() lib_func1() lib_func2() repeat { } } } } **From C**:: #include int main() { void (*lib_init)(void) = (void (*)()) 0xa000; void (*lib_func1)(void) = (void (*)()) 0xa004; void (*lib_func2)(void) = (void (*)()) 0xa008; cbm_k_setlfs(0, 8, 2); cbm_k_setnam("library.bin"); cbm_k_load(0, 0xa000); lib_init(); lib_func1(); lib_func2(); return 0; } **From Assembly**:: ; add error handling as desired. ldy #>libname ldx #$a000 jsr $ffd5 ; LOAD lda #13 jsr $ffd2 ; CHROUT jsr $A000 ; library init jsr $A004 ; lib func 1 jsr $A008 ; lib func 2 rts libname: .text "library.bin" Example library code ^^^^^^^^^^^^^^^^^^^^ Here is the small example library that was used in the example at the beginning of this chapter:: %address $A000 %memtop $C000 %output library %import textio main { ; Create a jump table as first thing in the library. ; NOTE: the compiler has inserted a single JMP instruction at the start ; of the 'main' block, that jumps to the start() routine. ; This is convenient because the rest of the jump table simply follows it, ; making the first jump neatly be the required initialization routine ; for the library (initializing variables and BSS region). %jmptable ( library.func1, library.func2, ) sub start() { ; has to be here for initialization txt.print("lib initialized\n") } } library { sub func1() { txt.print("lib func 1\n") } sub func2() { txt.print("lib func 2\n") } }