Blitz2 Library Developers Documents September 1994 Mark Sibly and Simon Armstrong INTRODUCTIONS The following is a collection of articles, docs and so forth for those wanting to develop command libraries for Blitz2. Only those experienced in the ways of assembly programming will be interested. COPYRIGHT NOTICE To help those wanting to develop libraries a large number of source codes have been included in this archive. All code include is copyright and hence should not be duplicated in other files without the author's express permission. Authors can be contacted via the address given in the documentation for each library published in BUM7. Acid Software can be contacted either via email: at acid@iconz.co.nz or via fax on +649 358 1658. TOOLS OF THE TRADE Before starting on your own libraries several programs and files will need to be setup in their correct position. First the blitzlibs: volume which includes the userlibs, amigalibs, basic and system directories should be installed on harddisk with an "ASSIGN BLITZLIBS: pathname" added to the your system's startup-sequence. Along with the 4 directories in blitzlibs: (do NOT add any others) copy the res files included in this archive, in particular make sure the file blitzlibs:libmacs.res is able to be found by Blitz2. You will also want to create executable versions of the libsdev tools, the src has been included in the toolssrc directory of this archive. It is probably easiest to compile them to your c: directory. OVERVIEW Blitz2 has an expandable command set. When you startup Blitz2 it attempts to load the command set from the file Blitz2:DefLibs. If it cannot find this file it will load the command set from the Blitzlibs: directories. Users are able to create new command libraries and add them to the userlibs directory of blitzlibs:. While developing a library remove the deflibs file from the Blitz2: drawer so that Blitz will always load it's commands from the BlitzLibs: volume. You can also override the DefLibs commands by select ReLoadLibs from the compiler menu. After creating a new .obj file in your blitzlibs:userlibs drawer you can test your library select ReloadLibs and create a test program to use your new commands. If Blitz2 crashes with an "Out Of Memory" or some such error during loading of libs, you have obviously created an illegal library file and will have to remove it from Blitzlibs:userlibs before restarting Blitz2. Other things to make sure of when creating library .obj files is to never have runtime errors enabled and never include any BASIC tokens (the END statement in particular), both these will create illegal library files. Once your library is complete you can use the makedeflibs command to create a new Blitz2:DefLibs file so you can return to the fast loading Blitz2 you once new (Blitz2 is much quicker at loading DefLibs than scanning the entire BlitzLibs: volume). CONVERTING STANDARD AMIGA LIBRARIES... If you just wish to access an unsupported standard amiga library (such as those that hang out in your libs: drawer) you will need to convert it's corresponding .fd file. You will notice the .fd file contains parameter and syntax information for all the commands in the .library file. By running the fdconvert tool, you can add a corresponding Blitz library in the BlitzLibs:AmigaLibs drawer which will let you access these commands from Blitz2. Make sure the .fd library specifies the name of the actual library in the top line (same as DOS3 standard .fd files). The fdconvert program should assign a suitable library number for your new amigalib. Check the libnums.bb file in the ressrc file for what library number assigns have been made for amigalibs upto now (first available should be #217). After running fdconvert either create a new deflibs file with the makedeflibs tools or use ReloadLibs in the Compiler menu of Blitz2. All the commands that were listed in the fd file should now be part of Blitz2, the only difference is that they are all succeeded by an underscore character to avoid clashes with Blitz2 tokens. --------------------------------------------------------------------------------- Text file taken from BUM7's readme directory by Simon Armstrong --------------------------------------------------------------------------------- Notes for Library Developers Well, I've been a bit bamboozled by all the userlibs that have been arriving in the mail. The following are a few notes I've jotted down after spending a lot of time trying to tidy up what you guys have been sending in. 1. LibNums and general naming conventions. OK, first up, library numbers. Not a big deal as we are probably going to get rid of tokenising in Blitz3 so that 1. we don't get all the clashing that has been going on and 2. users can use what ever ascii editor they please. However in the mean time (and for ever more if the Amiga is still out of production by XMas).... Do NOT use library numbers above 40. Especially do NOT go sticking your libraries in between Acid ones (ELMORE!!!) we left the odd numbers for our own expansion not yours! To be generous I have allocated the following for those that feature on BUM7 and are working on new libraries: Elmore - 99 97 95, RI - 63 58 51, Neil - 50 49 48, Romulus- 47 46 45 Secondly, do not use words like len, start and so forth (RI!!!). As BASIC programmers like to use such words for variables it is a bit rude to find that someone has gone and called a command after your favourite variable name (don't don't don't add a command called i!), To be really snazzy you should try and use the library name (make it concise) in all your tokens like I have done in the display library, this makes things a lot easier to read, avoids token clashes (Elmore, any chance of a tokenclash tool?) and possibly keeps you on the straight and narrow. 2. Syntax and library content. Avoid extraneous commands at all costs. If you can use different forms of the same command DOIT. If the command is the same as using two or three commands don't bother. Rule of thumb I suppose is add parameters to make key commands more powerful rather than adding more commands. If you are just implementing small pieces of machine code to do miscellaneous jobs don't bother. Just stick the machine code in a statement and publish the statement. Commands added to Blitz2 should not be the sort that you can acheive in a few lines of code, they just make Blitz2 a more obese rather than ultra powerful language. With the advent of the ASMEXIT command machinecode routines can now be placed in functions and statements in a stable manner. To use ASMEXIT do not use UNLK A4 but simply replace any RTS commands with ASMEXIT, ensure that registers a4-a6 are restored before ASMEXITting. If you are adding commands that belong in Acid libraries, please ask me for the source for that particular library, and add them there, it makes my job a hell of a lot easier and means with new documentation users will know about your command cos they are looking in the correct section ot the manual. 3. A few other pointers... * OS2/3 specific commands should check the version of exec and exit cleanly if too old: MOVE.l 4,a0:CMP #36,20(a0):BCS doexit ;wo older than v36!!! * Use Blitz2 objects if they are suitable, best place to work out how they work is in the source code the acidlibs I have included in the libsdev archive. Hmmm, a quick lesson on this follows below. * always use ALibJSR $c002 and $c003 for allocating and freeing memory, never use the exec calls... 4. And please please please... Prefix the name of you library with your handle or name, those finding to their horror the source of their libraries published here please update your own source codes with the changes I've made. Keep a history file at the top of your library source, it makes keeping the documentation uptodate so much easier. Test your commands with bad parameters to make sure your error checking is cool, save a .test file with your library when you submit it so others can see how to use your commands, and try and follow the documentation style that I've been using in BUM7. IE, list all the commands at the top then describe them one by one. Keep your .res files in the root of blitzlibs:, reference all resident files in compiler options as blitzlibs:filename.res and use blitzlibs:userlibs as the path when creating object files. Getting a bit picky here I think. Oh DO take notice of the new keyboard shortcuts in the compiler menu. 5. And very importantly... Make sure you return A4 how you found it, it is the local variable base and if you stuff it you will not be able to use your commands inside functions and procedures (yes I know I am guilty of this often). Keep your library .obj files as small as possible. If you need a hunk of memory allocate it in the init routine, DON'T USE ds.l big. If you have a look up table try and generate it from allocated memory during initialisation rather than INCBIN the thing, DefLibs is too bloody big as it is, don't push it! Anything over 4K is no go unless it is major cool. Use moveq whenever possible. It is fast and sign extends to a long word. Use pc relative adressing as much as possible as Blitz2 does not optimise this. Use addq and subq also whenever possible. Anyway, thanks for everyone who has been adding commands, please don't take the above personally just trying to keep things tidy... Blitz2 Library Objects. Yes, bloody wonderful things. OK, say your library is based around a particular object or structure, hopefully something useful that will make Blitz2 programmers want to give you their girlfriends... First up I define a NewType similar to the other Blitz2 objects as listed in an appendix of the Blitz2 manual, and define a macro to make my assembly code much more readable: Macro s {SizeOf.sound\`1}(a3) End Macro NEWTYPE .sound data.l size.l period.w volume.w priority.w etc etc End NEWTYPE Of course I call my library acidsoundlib so it doesn't get mixed up with everyone else's sound libraries, assign it a number (see above) and define the name of my sound object in the dumtoke, This allows the programmer to use the free object, addr object() etc. on my object. #acidsoundlib=40 ;library number at top as constant !libheader {#acidsoundlib,init,1,0,0} ;header with init routine if needed !dumtoke{"Sound","",_toke} ;wo clash, only example ok? !astatement !args {#word,#long,#word} !libs {#acidsoundlib,$1380} !subs {_initsound,0,0} !name {"InitSound","Sound#,length,flags"} init:!nullsub {_initlib,0,0} _load:!nullsub{0,0,0} _save:!nullsub{0,0,0} _use:!nullsub{0,0,0} _free:!nullsub{0,0,0} !libfin{_toke,_load,_save,_use,_free,4,5} OK, we don't worry about the nullsubs this week. the last two numbers in the libfin macro define the default number of objects (before the user increases them via the compiler options) and the size of the object which is 2^n so Blitz2 can do a shift to calculate the location of the object any parameter is referencing. The 5 means our object can take up to 32 bytes. Hmmm, the !libs macro has the number $1380. There are some macros in libmacs for this but I always forget them. The high byte says which address register you want to point to the object, the low byte defines which parameter to use: #a0=$1000:#a1=$1100:#a2=$1200:#a3=$1300:#a4=$1400:#a5=$1500:#a6=$1600 #p0=$80:#p1=$81:#p2=$82:#p3=$83:#p4=$85:#p5=$85 So if I want the adress register a3 to point to the object# defined by the first parameter I use the code #a3+#p0 = $1380 If I had a copy sound command that needed source in a2 and destination in a3 I'd use the following setup: !astatement !args {#word,#word} !libs {#acidsoundlib,$1280,#acidsoundlib,$1381} !subs {_copysound,0,0} !name {"CopySound","SrcSound#,DestSound#"} And, gosh it's exciting... yes a2 and a3 will be preloaded all ready to go at the start of your routine. Use #word in !args for all parameters that refer to objects. OK, thats about it, oh the macro and newtype stuff, basically if you always pass the object number in say a3 set up the macro as I have done and whenever you want to reference a field in the object you can use it's name e.g. move.l !s{size},d1:move.l !s{data},d2 which is the same as move.l 4(a3),d1:move.l 0(a3),d2 Hmmm, other things: 1. Objects are always initialised with zero's on program startup 2. The free subroutine if you provide one will be called for all objects on program exit. 3. See banklib source for basic memory management. 4. Use other libraries objects, but always check if they are initialised. --------------------------------------------------------------------------------- Following republished from AmigaDownUnder article by Mark Sibly --------------------------------------------------------------------------------- HOW TO WRITE BLITZ BASIC 2 LIBRARIES ************************************ The Blitz 2 compiler is heavily library orientated. What this means is that many of the routines and functions used by the compiler are not actually stored in the compiler itself, but in completely separate library files. This set up allows new commands and features to be easily added to the Blitz 2 system, simply by creating a new library file. Libraries in Blitz 2 are usually written in Blitz 2 itself, and must be TOTALLY in machine code. Before we get into actually writing a library, let's first have a look at the basic layout of libraries in Blitz 2. Varieties of Blitz 2 Libraries ****************************** Blitz 2 makes use of 3 types of libraries - system libraries, Amiga libraries and command libraries. Let's have a closer look at these: * System libraries - These libraries are used internally by the Blitz 2 compiler and include such mundane operations as memory allocation, string manipulation and so on. System libraries will only ever be modified by Acid software, as they work in connection with Blitz 2 at a very low level. * Amiga libraries - These libraries allow Blitz 2 programmers access to any of the operating system libraries (eg: intuition.library,exec.library). * Command libraries - These libraries form the bulk of the Blitz 2 system, as they contain all the BASIC commands and functions available to Blitz 2 programmers. All of the Window, Screen, Blitting etc. commands are held in command libraries. To add new commands to Blitz 2, a command library should be created containing these new commands, and this is just what we'll be doing...soon... Where ARE the Blitz 2 Libraries to be found? ******************************************** When Blitz 2 starts up, it needs to be able to locate all the libraries available. It does this by first looking for a file called 'Blitz2:Deflibs' (for 'default libraries'). If this file is not found, Blitz 2 then goes and looks for a directory called 'Blitzlibs:'. If neither Deflibs or Blitzlibs: can be found, then Blitz 2 gives up and politely (?) exits. If Blitz 2 can find Deflibs, then it uses the contents of Deflibs as it's libraries. If Blitz 2 can only find Blitzlibs:, it then proceeds to scan any sub-directories of Blitzlibs:, and uses any files found as it's libraries. At the moment, there are 4 sub-directories set up within blitzlibs: - 'System', 'Amigalibs', 'Basic' and 'Userlibs'. It's worth pointing out here that Deflibs is simply all the separate Blitzlibs: libraries joined together as one continuous file. This was done to speed up the library loading process, and to make the whole system more user-friendly. A program called 'Makedeflibs' can be used to create the Deflibs file from the individual Blitzlibs: libraries. Getting started *************** First, you'll need to unarchive the 'blitzlibs.lha' file into a blitzlibs directory, and add yet another assign to your startup-sequence (eg: assign blitzlibs: blitzlibs). Next, get rid of 'Blitz2:deflibs' to prevent Blitz 2 from looking for it instead of blitzlibs:. This can be done simply by moving Deflibs from the Blitz2: directory to a different directory, or by renaming it. Next, crank up Blitz 2 to make sure everything's still going OK. You should notice Blitz 2 takes longer to startup, due to it having to load in the individual library files from blitzlibs:. Writing a Blitz 2 Library ************************* Before you can go ahead with writing your library, you'll have to first choose a 'number' for your library. Each library in Blitz 2 has it's own unique identifying number, so you must make sure the number you choose for your library does not clash with any other libraries. Acid software has decided, in it's infinite wisdom, that any libraries under development should be given a library number from 10...50. So, you'll probably choose 10 for your first library, 11 for your second, and so on... If you finish a library and are pretty proud of it and feel it would be of benefit to Blitz users in general, Acid software can be contacted to obtain a permanent library number. One more thing you'll need to write your library is the 'libmacs.res' resident file. This file contains a set of macros and constants necessary for writing libraries. If you're not familiar with resident files, refer to the Blitz 2 User guide section 10-1. Libmacs.res can be found in the Blitzlibs: directory, and before starting work on a library you'll need to load it in via the 'Compiler/Options' menu. OK, so you've got libmacs.res loaded and you've decided upon your library number. It's now time to get coding!... We'll start of with a quick example library that doesn't do all that much but should give you an idea of what's going on: ; ;examplelib ; ;An example of library writing in Blitz 2 ; ;A decidedly useless command to blink your LED on/off ; ;we're going to give our library the number 10... #mylib=10 ;***** First up, a header for the library... ***** !libheader{#mylib,0,0,0,0} ;***** now, the actual commands in the library ***** !astatement !args{#word} !libs{#graphicslib,#la6} !subs{doblink,0,0} !name{"Blink","number of blinks"} ;That's it! !libfin ;***** Now comes the code for the library ***** doblink ; MOVE d0,d2:BEQ 'skip:SUBQ #1,d2 'loop BCHG #1,$bfe001:JSR -270(a6) ;call graphics library 'Wait top of frame' DBF d2,'loop 'skip:RTS ;***** That's all for our library! ***** Once you've typed this in, save it, then create an executable by the name of 'Blitzlibs:userlibs/examplelib.obj'. To test out your library, use the 'Compiler/Reload Libs' menu item. This will force Blitz 2 to re-load all it's libraries from Blitzlibs:. Following this, your awesomely useful 'Blink' command should now be available to Blitz 2... ; ;Test of the new 'Blink' command... ; Blink 10 ;blink ten frames.... ; Now, let's go through the examplelib a line at a time: !libheader{#mylib,0,0,0,0} The !libheader macro should always appear at the start of a library. The libheader macro looks like this: !libheader{library number,initialize,return,cleanup,runerrs} The 'initialize' parameter allows you to specify a routine which gets executed when a Blitz 2 program is run. Likewise, 'finish' may specify a clean up routine when the program ends. If either of these is set to 0, then they are ignored. The 'return' parameter allows the library's initialise routine to return a value, in d0, which other libraries may access. If this parameter is 0, then this means the libraries initialization returns nothing. If this parameter is 1, this means the initialization routine returns a value in d0 which other libraries may access. We'll have a closer look at this when we get to the !libs bit... 'Cleanup' is similar to 'initialize', allowing the library to doing any tidying up when the program ends. The 'runerrs' parameter informs Blitz2 of where any runtime error checking code is located. We'll go into this later as well. After the libheader, the library then goes on to specify the actual commands in the library - in this case, there is only one command... !astatement !args{#word} !libs{#graphicslib,#la6} !subs{doblink,0,0} !name{"Blink","number of blinks"} The !astatement macro specifies that this command is a statement, as opposed to a function. In other words, Blink doesn't return any value. The !args macro gives the arguement template for our command. In this case, !args{#word} means that the Blink command takes only 1 parameter - a 16 bit word. We can specify as many parameters as we want here, and they may be any of the following types: #byte #word #long #quick #float #string Parameters are passed to library routines through the 68000 data registers D0 through D6 - the first parameter being passed in d0, the second in D1 and so on. If there are more than 7 parameters, extra parameters get passed on the stack. Also, string variables are passed through the use of a data register as a pointer to the string, and the length of the string on the stack. We'll cover stack usage in more detail a bit later on. Onto the !libs macro. This macro allows us to 'grab' data from other Blitz 2 libraries for use by our command... !libs{#graphicslib,#la6} In this case, we are asking for the 'graphics.library' library base to be placed in A6 before our blink routine is called. Remember the 'initialise' and 'return' parameters of the !libheader macro? Well, the Blitz 2 library 'graphicslib' uses these parameters to open the graphics library and return the library base in d0 for other libraries to use. We can also specify multiple libraries using the !libs macro. For example: !libs{#graphicslib,#la6,#intuitionlib,#ld6} Will give us graphics base in a6, and intuition base in d6. Now we get to the !subs macro: !subs{doblink,0,0} This simply contains a pointer to the actual code to execute once all parameters and library values have been placed in appropriate registers. The 2 '0' parameters here are used for runtime error checking purposes. More on this later. Finally, we get to name our new command: !name{"Blink","number of blinks"} The !name macro takes at least 2 text parameters. The first gives the actual name of the command, while the second specifies the 'help' text for the command. A third parameter may be given allowing a label to be associated with the text - this is of use when creating Blitz 2 objects (see below). Once we've finished specifying the format of our libraries commands, we use the !libfin macro: !libfin The rest of the program contains the actual machine code to perform the Blink command. FUNCTIONS ********* Blitz 2 functions are created almost identically to statements, except we use the !afunction macro instead of !astatement. The !afunction macro must be supplied with a single parameter indicating what type of result is returned by the function. Then, it's up to the actual function code to return the appropriate type of value in D0. Let's expand our library to demonstrate writing a function... ; ;examplelib ; ;An example of library writing in Blitz 2 ; ;A decidedly useless command to blink your LED on/off ; ;we're going to give our library the number 10... #mylib=10 ;***** First up, a header for the library... ***** !libheader{#mylib,0,0,0,0} ;***** now, the actual commands in the library ***** !astatement !args{#word} !libs{#graphicslib,#la6} !subs{doblink,0,0} !name{"Blink","number of blinks"} !afunction{#word} ;inidcates that this function returns a word. !args !libs !subs{doledstatus,0,0} !name{"LEDStatus",""} ;That's it! !libfin ;***** Now comes the code for the library ***** doblink ; MOVE d0,d2:BEQ 'skip:SUBQ #1,d2 'loop BCHG #1,$bfe001:JSR -270(a6) ;call graphics library 'Wait top of frame' DBF d2,'loop 'skip:RTS doledstatus ; BTST #1,$bfe001:SNE d0:EXT d0:RTS ;***** That's all for our library! ***** LIBRARY INITIALIZATION AND CLEAN UP *********************************** If your library needs to perform some kind of initialization or cleaning up, the 'initialize' or 'cleanup' parameters of the !libheader macro may be used. However, instead of simply pointing to some code, these parameters should point to a !nullsub macro. A WHAT!?!? Well, !nullsub is basically a combination of the !subs and !libs macros. The first 3 parameters of !nullsub are the same as those for !subs, while the rest allow you to pickup values from other libraries similar to the !libs macro. This set up allows you import data from an external library for use during initialization or cleanup. For example, let's say you need intuition base to perform your libraries initialization and cleaning up: !libheader{#mylib,init,0,cleanup,0} init !nullsub{doinit,0,0,#intuitionlib,#la6} cleanup !nullsub{docleanup,0,0,#intuitionlib,#la6} . . . !libfin doinit ;actual initialization code... docleanup ;clean up code... Note that libraries are initialized numerically, from higher numbered libraries to lower numbered. Therefore, if you have written 2 libraries of which one requires the returned value from the other, the library supplying the value should be of a higher library number than the one receiving. Also note that the !nullsub macro should appear between the !libheader and !libfin macros - NOT in the main code. THE STACK ********* Parameters are normally passed to library routines through data registers D0 through D6. However, in the case of string variables, or when a command requires more than 7 parameters, parameters may end up on the stack. To help in keeping track of stacked parameters, Blitz 2 copies the stack pointer to address register A2 before anything gets placed on the stack. This allows you to step through stacked parameters using the -(A2) addressing mode instead of having to play around with offsets from A7. For example, say you're writing a command which requires one string parameter. When the commands routine is called, the address of the string will be in D0, and the strings length will be on the stack. The routine could then pick up the strings length using: move.l -(a2),d1 Note that Blitz 2 Version 1.80 and higher will automatically null terminate all strings. Earlier versions only null terminate the last string of a parameter list. Also remember that if you're wanting to use A2 in this way, you must not use A2 in the !libs macro. RUNTIME ERROR CHECKING ********************** Blitz 2 libraries may also contain runtime error checking code. This code may be used to check that command (or function) parameters are within a legal range. Remember, this extra code will only be executed if the 'RUNTIME ERROR CHECKING' option within the compiler/options menu is enabled. Error checking code must always appear at the end of a library. This allows Blitz 2 to drop this code when runtime error checking is disabled. To add error checking to a library, you need to indicate where in the library the actual error checking code starts. This is done through the 'runerrs' parameter of the '!libheader' macro. Then, it's simply a case of using the '!subs' macro to indicate any error checking routines that needs to be called. If an error checking routine decides that a parameter is bad, a 'TRAP #0' should be executed with D0 pointing to an appropriate error message string. To demonstrate this, we'll alter our example library to make sure 'Blink' only allows parameters of 0...100: ; ;examplelib ; ;An example of library writing in Blitz 2 ; ;A decidedly useless command to blink your LED on/off ; ;we're going to give our library the number 10... #mylib=10 ;***** First up, a header for the library... ***** !libheader{#mylib,0,0,0,runerrs} ;***** now, the actual commands in the library ***** !astatement !args{#word} !libs{#graphicslib,#la6} !subs{doblink,doblinkcheck,0} !name{"Blink","number of blinks"} !afunction{#word} ;inidcates that this function returns a word. !args !libs !subs{doledstatus,0,0} !name{"LEDStatus",""} ;That's it! !libfin ;***** Now comes the code for the library ***** doblink ; MOVE d0,d2:BEQ 'skip:SUBQ #1,d2 'loop BCHG #1,$bfe001:JSR -270(a6) ;call graphics library 'Wait top of frame' DBF d2,'loop 'skip:RTS doledstatus ; BTST #1,$bfe001:SNE d0:EXT d0:RTS ;***** OK, time for runtime error checking code! ***** runerrs blinkcheck CMP #101,d0:BCC blinkerror:RTS blinkerror:MOVE.l #blinkerrortext,d0:TRAP #0 blinkerrortext:DC.b "Number of blinks must be 0...100",0:EVEN ;***** That's all for our library! ***** ; WARNING! WARNING! WARNING! ************************** Any library routines, whether they be statements, functions, runtime error checks or simply initialization code, MUST preserve address registers A4, A5 and A6. As well, a function MUST ALSO preserve address register A3. Just what are these registers used for? A6 : Blitz keeps track of A6 to prevent it having to be unnecessarily re-loaded. A5 : Blitz 2's global variable base. A4 : Blitz 2's local variable base. A3 : Blitz uses this to build temporary strings. THAT'S ALL FOLKS **************** Hopefully, you'll now have enough information to begin writing your own libraries. In the next installment of this series, we'll have a closer look at string variables in Blitz 2, creating Blitz 2 objects and more. In the interests of keeping this document as self contained as possible, I'll finish up with the source code to libmacs... Plesant programming, Mark Sibly