by Martin Penny
In previous parts of this series, I've covered the ARM instruction set and gone over a number of areas where assembly language is useful, giving examples along the way. I'll wrap things up with some notes on using BASIC's assembler for creating RISC OS programs; I'll go over a module and an application written with this series in mind. I won't include the full listings of the source files for either, as they're rather long; instead, the full programs are on Acorn User CD 11, and I'll quote just snippets here.
A little diversion here; I've make various comments about "26-bit" and "32-bit" modes on the ARM processors in the previous parts of this series, and have said "do this" and "don't do that" with respect certain pieces of code. The point is that, even on processors that offer a "32-bit" mode, RISC OS is largely "26-bit" code, and so are the vast majority of modules and applications. This means that programmers can currently still "get away with" writing "26-bit" specific code; however, RISC OS is likely to be revised to eliminate the existing "26-bit"-specific code, so programmers should keep this in mind. Taking care over writing new code will prevent having to rewrite large parts of it in the future in order to make it "32-bit clean", though this can be extremely difficult with modules.
To start off with, I'll jump in at the deep end, and briefly describe a simple module, "DemoMod". Modules can be used for a range of tasks, including one or more of the following list - device drivers, new "SWI" routines, "*" commands, CLI programs and RISC OS applications. Module programming is not the most straightforward of topics, but RISC OS has a lot of built-in routines to make life a good bit easier than might otherwise be the case; for example, "SWI" and "*" command name decoding can be left to RISC OS. Indeed, relying on RISC OS to do as much leg-work as possible makes sense, as you won't have to keep reinventing the wheel; it also means the "look and feel" of modules remains consistent.
As modules are relocatable, you cannot rely on any specific addresses for such things as labels or workspace; the only real exceptions to this are if your modules deals with "fast" interrupts - the ARM's "FIQ" mode - or if it uses application program space. Another side-effect is that program counter-relative ("R15"-relative) instructions need to be used more frequently; in general terms, it is best to use instructions like "ADR" rather than use "R15" directly, as the assembler is essentially infallible in this area - you and I aren't!
Anyway, to "DemoMod" itself; it is a very simple module, to the extent that it provides only one "*" command, with no new "SWI" routines or other code. Much of the source file concerns itself with setting up useful constants, ready for use at some time in the future, should the code be developed any further. Within the code for the module proper, most module header fields are left blank, as they are not relevant to this module. Most of the remaining code is set out ready for RISC OS to automatically read the module title, "*Help" text, "*" command list and "*" command "*Help" text. That leaves just one short section, given in figure 1, that actually "does something".
The explanation of the code is as follows. The PRMs give quite a lot of detail about what can and cannot be done during "*" command code; part of this says that registers "R7" to "R11" must be preserved during such code. They also go on to give details about using "SWI" routines, which can boiled down to this - that, as the "*" command code is called while the ARM is in its "supervisor" ("SVC") privileged mode, the "X" option must be used. Add to that the fact the contents of the "R14" register must be preserved before the first "SWI" is called, and that's why the "STM" and "LDM" instructions appear, and are as they are.
The "ADR" sets up "R0" as a pointer to the text to be printed on the screen, and calls the first "SWI" routine. If this indicates an error of some kind via the "V" bit, the second "SWI" is skipped, the registers restored and control is returned to RISC OS, for it to deal with the error. Otherwise, the second "SWI" routine is called, the registers restored, and control returned to RISC OS. With this example program, have a test, and have a play - that's why I've included the source code!
Okay, now on to "!DemoApp". As with "DemoMod", the source file is too large to fully reproduce here; again, a large part of it is taken up with numerous constants. Before anyone asks, yes, I know that it uses the BASIC assembler to create a message file and a "Templates" file; this is quite deliberate. Although the application doesn't (yet) support "MessageTrans", creating the message file along with the "!RunImage" has the advantage of ensuring the correct version of the message file being created for the current "!RunImage". The same reasoning also applies to the "Templates" file, especially with regards the icon numbers; the reason I started doing the window definitions this way was that I didn't, at the time, have a decent window editor. "!DemoApp" was derived from my application "!68Host", so there may be some "oddities" left in the code; I'll let you work out what's what!
One thing you'll find is the use of variable names instead of "proper" register names; this is perfectly fine and allowable, as long as those variables can be evaluated as suitable register numbers. For instance, "R8" and "R9" are used as temporary registers in "!DemoApp", and are referred to as "temp_a%" and "temp_b%" respectively.
As the application runs in application space - from the address "&00008000" upwards - the first step for the program is to define a stack. I've gone and segregated program code and data into separate areas, with part of the "!RunImage" reserved for the stack; hence "R13" ("arm_sp%") is set accordingly, with "R10" ("arm_sl%") marking the limit of the stack, though it is not used here. The third step is to set "R12" ("arm_ip%") to the area of memory used to hold commonly accessed data; this makes indexing such data via "LDR" and "STR". Once this has been done, control passes to the section headed by the "Initialise%" label, which registers the task with RISC OS. If, for whatever reason, someone tries running the program on an older version of RISC OS, the program generates a warning message, and quits. If, on the other hand, all is okay, the "CreateIcon%" code generates the icon on the Icon Bar.
The "CreateWindows%" code in figure 2 uses the "LoadTemplates%" subroutine - figure 3 - to load individual window templates, creating the windows as it goes. If the latter routine indicates an error - through the "V" flag - an error message is generated, and the program "unwinds" itself, deleting any windows created so far before quitting. These two sections of code together can be used as an example of error trapping within RISC OS applications; note particularly, that I've used arithmetic instructions to alter the "V" flag - a little bit of "future-proofing".
The "PollSetup%" code sets a few flags, and passes control on to the central task loop, at "PollLoop%", as given in figure 4. The loop starts of by setting "R14" to point at "PollLoop%"; this will be used to fake a "BL" later on. This is followed by the (usual) call to "Wimp_Poll". Null events are filtered out immediately after the call, for two main reasons - speed, and because the value "0" is used as an end-of-table marker. "R9" is set to point at the table containing message codes and handler addresses, and the loop at "PollMessageLoop%" works its way through this table. If the end-of-table marker is found - the message is not supported - the program loops back for another call to "Wimp_Poll"; on the other hand, if the message code is found, the address of the relevant routine is loaded into "R15", faking a "BL".
If the event in question is actually a message, control passes to the "UserMessage%" code; this works in a similar way to the "PollLoop%" code. However, it doesn't need to alter the contents of "R14", as this is still pointing at the right place in memory. "Quit" messages are filtered out as a first step, for the same reasons as null events; however, control passes to "PollExit%" rather than "PollLoop%". At this point, the windows and Icon Bar icon are deleted, and the program exits to RISC OS via "OS_Exit".
With regards the "standard" events, there is no "RedrawWindowRequest%" code - the event is not required by "!DemoApp" as its windows are simple - and the "CloseWindowRequest%" code is similarly simple. The "OpenWindowRequest%" code is a little more complex, but all it does is check the "re-centre" flag set by the "ModeChange%" code, and, if set, modifies the window. Keypresses are ignored and are passed straight back to RISC OS, and a number of other events are either filtered out or ignored.
I shan't include the "MouseClick%" code here, as it's rather long-winded and repetitive. It starts out by working out which window owned by the application has received the click; the Icon Bar is also checked, for capturing clicks on the application's icon. Control then passes to the sub-section dealing with clicks on the appropriate window, although "Menu" clicks are filtered off to a separate sub-section. If you check closely, you'll notice that Icon Bar "Menu" clicks are dealt with slightly differently - the base of the menu has to be no less than 96 OS units from the bottom of the screen. If the menu is opened a flag is set; the "MenusDeleted%" code clears this flag when the menu is closed. "Select" and "Adjust" clicks are not treated differently for either the Icon Bar icon or the main window, but they could be, should it be necessary - for instance, on the "left" and "right" icons. "Select" would trigger the expected function, while "Adjust" would trigger the opposite.
The "MenuSelection%" code - in figure 5 - tests to see which menu option has been chosen. "Info" is ignored, but, if "Quit" has been selected from the menu, the application sends itself a "PreQuit" message. The "PreQuit%" code - in figure 6 - receives all occurrences of this message, and deals with it accordingly. First off, it gets the task handle of the message sender in register "R2", then checks to see if it is a full system shutdown. If so, the program branches over to the "PreQuit_All%" code; if not, it moves on to the "PreQuit_Task%" code.
In the case of the latter - a "this task only" shutdown - the program checks to see if there is unsaved data, and, if so, warns the user. If the user doesn't want to quit, the "PreQuit" message is acknowledged, stopping the task shutdown. Alternatively, the program will end up sending itself a "Quit" message.
Going back a step, if it is a full shutdown, the program, as before, checks for unsaved data; if there is none, the program silently returns control back to RISC OS, and waits for the following "Quit" message. If there happens to be unsaved data, the shutdown is stopped, and the user asked what to do next. If the user wants to stop, the program does nothing more, allowing the user to then save any such data. On the other hand, if the user wants to continue shutting down, the sequence is re-started by sending a "Ctrl-Shift-F12" keypress to the task that started the shutdown.
This shutdown sequence will only work properly on RISC OS 3 - preferably 3.10 - or higher; if "!DemoApp" were to be modified to run under RISC OS 2 as well, extra code would have to be added to the "PreQuit" message handler to specifically cope with RISC OS 2.
That brings me nicely on to the subject of message handlers. They need be added only as required, so - apart from "Quit" and "PreQuit" - if the handlers weren't required, they could be removed from the relevant tables. An example would be the "DataOpen" message - this is recognised, but not coded, so could be removed quite happily.
One message that may be of interest is "HelpRequest"; "!DemoApp" supports it, and, though the code is again fairly bulky, a quick description of how it works goes something like this. It is easy to work out when one of the "regular" windows is involved, and hence branch to the sub-section that deals with that individual window. Within each sub-section, it is possible to use a scheme similar to that used by the event and message decoding routines to select the relevant text message for the icon in question - if a message is appropriate. RISC OS 3 also allows the menu to be the subject of this message, but RISC OS 2 doesn't. That is why the menu is filtered out last - by using "if it's not a regular window and it's not nothing" - and why there's a double-check to make sure the application's not running under RISC OS 2. Once the text message has been selected, it's copied into a buffer and sent to the "!Help" application.
The "WindowInfo" message falls into the same category; it's a message that not all applications support, but it looks nice when it is supported. "!DemoApp" supports this message through the "WindowInfo%" code, as given in figure 7. Within "!DemoApp", there is only one window that can be iconised, so the window handle check is easy. Assuming the "correct" window is involved, the incoming message is acknowledged to prevent confusion, and the return message is sent back.
The last two routines I'll mention specifically here are the short sections of code I use to report errors under RISC OS; they are given in figures 8 and 9. There are similarities, in that they both preserve any of "R0" to "R7" they corrupt, though they differ in the types of error boxes they produce. The "ReportError_OK%" code - not surprisingly - gives just an "OK" button, and is used for informative messages. On the other hand, the "ReportError_OKCancel%" code gives both "OK" and "Cancel" buttons, and used the "V" flag on return to indicate which of the two buttons has been selected. As mentioned earlier, arithmetic instructions are used instead of something like "TEQP" for updating the flags correctly.
That is that, for both this part, and the series. I hope you've found the series interesting, useful and informative, with not too much waffle or too many errors. If you have any comments, queries or questions, you can e-mail me on the address below, or post a message to part of the Internet's "comp.sys.acorn" news hierarchy - I'm usually lurking around the newsgroups.
Return to ARM Code Tutorial index
This CD and its design is Copyright © 2000 Tau Press Limited. It may not be copied or distributed without the prior consent of Tau Press. Failure to abide by this may result in prosecution. (That doesn't mean the contents are our copyright, just the linking pages that we created and the CD itself.)