In the previous article there was mention of a file “wbinit.s” along with comment it was worth talking about by itself. Let’s talk!
One thing C programmers take for granted is a lot of work has already been done before even your first line of main() code is executed. Assembly programmers don’t have it nearly so easy.
On the Amiga there are two ways a program can start, via the textual AmigaDOS command line interface (early versions of Workbench had both Shell and CLI as I recall) or via the graphical Workbench interface. For assembly language programmers these act differently.
Some professional assemblers used to supply code for this but we are doing this ourselves, so why not look at what code should do to properly support both methods. Some details will be skipped (like saving of registers, etc) but nothing important.
AmigaDOS will load your program, set some 680×0 registers to allow your code to access the command line parameters and jump into your code. Workbench will load your program, and “send a message” to your program. You “reply” to the message to signal to Workbench you’re finished.
First step then is to work out if you’ve been launched from Workbench. AmigaDOS sets up a “CLI” BCPL pointer when it sets up a “process” (a superset of an Exec “task”), so you first use exec.library to find your own task and check if the CLI pointer is set.
sub a1,a1 CALLEXEC FindTask move.l d0,a4 tst.l pr_CLI(a4) beq fromWorkbench
The “Autodoc” for exec.library describes the FindTask call taking a name in the a1 register and returning a task pointer in the d0 register. Giving a NULL pointer for the name will find your own task. The instruction “sub a1,a1” is a space efficient way of making a1 zero (NULL), requiring just a single 16-bit (2 byte) instruction.
The include file “dos/dosextens.i” (previously “libraries/dosextens.i”) describes how the exec task control structure (called “TC_Struct” in exec/tasks.i) is extended to form the AmigaDOS “Process” structure. We will be using two fields in this structure, the first being “BPTR pr_CLI” which AmigaDOS sets when launching a process from the command line. Workbench leaves this as zero/NULL.
BPTR? A ‘B’ pointer you may ask. Long time Amiga programmers may drop their head, sigh, and in a low voice say “yeah” at this point.
The original AmigaDOS was written by an external contractor and based on a disk operating system called TRIPOS and is was written in BCPL. In BCPL pointers counted memory in 32-bit “long words” not bytes, so pointers for AmigaDOS had to be aligned to longword boundaries and bit shifted. Outside of AmigaDOS the OS used more normal byte offset pointers references as APTR, which I always thought of as Amiga pointer.
Anyway, if you are at all interested in the AmigaDOS command line (command line address and length placed in a0 and d0 registers when AmigaDOS passes control to your code) you can now check it as you’ve branched off for Workbench launches. How to parse a command line buffer, spaces, quotes, etc is beyond the scope of this article.
If you’ve been launched from Workbench you need to wait for and then fetch the message Workbench will send to your process. The process structure includes a reference to a message port used for this purpose (STRUCT pr_MsgPort). The exec.library WaitPort routine takes a message port pointer in a0 and returns a pointer to the first message received in d0, leaving the message “in” the queue. We then have to get (and remove) the message from the queue with GetMsg which again takes the message port address in a0 and returns the message in d0. This prevents us from “blocking” Workbench.
lea pr_MsgPort(a4),a0 CALLEXEC WaitPort lea pr_MsgPort(a4),a0 CALLEXEC GetMsg move.l d0,returnMsg
A word about system calls and register usage. The usual convention is that Amiga system calls should be treated as either “corrupting” or using d0, d1, a0 and a1 to return values back to your code, with d2 to d7 and a2 to a6 (technically a7 too, which is the 680×0 stack pointer) being preserved. This is why the code sets a0, makes a call, then repeats the line to set a0.
When your program ends, if your program has been launched from Workbench you need to let Workbench know you’ve finished. However you don’t want Workbench to unload you before your code actually “ends” (unlikely but possible) so you have to take precautions. The following assumes that the “returnMsg” variable is “close” to the exit code. The exec.library Forbid call (taking no parameters and providing no return code) will temporarily stop task switching. Normally this is not recommended. The ReplyMsg call takes a message pointer in the a1 register and replies to say “I’m done”.
CALLEXEC Forbid move.l returnMsg(pc),a1 CALLEXEC ReplyMsg
Whether from AmigaDOS or Workbench all that is left is to set a desired “return code” in the d0 register and “rts” out of your program. The process/task launching code will do it’s own cleanup and “permit” full multitasking to continue.
By following this process you can create small assembly language programs that work equally well from AmigaDOS and Workbench with no ill-effect either during the course of your program execution or after.
All this and a little more are contained in the “wbinit.s” file that we pulled in for the TDWrite “project” before.