OSLib Developers' Guide
Purpose
This guide provides information and guidelines to developers who propose to make submissions for inclusion in OSLib.
Brief and cryptic notes about the source release of OSLib
It's 14Mb built. The part you use (headers + library) is 3Mb big.
This is 5.3. See ChangeLog for changes since 5.2.
Error Handling
The most important coding point to emphasise is that *all* errors are detected and reported, no matter how deeply nested we may be into the call stack or however small the function in progress. This is done by keeping to a strict discipline
Every function that everyone has ever written can return an error. Normally in RISC OS code this is done by returning an |os_error *|. In UNIX-style code, it is done by returning -1 and setting |errno|. Whatever the mechanism is, it *must* be checked when calling the function. Every function called *must* then have its error return checked. If there was an error, a |goto finish;| is executed immediately. (The only thing that can be done before "go to finish" is to assign the current function's error indicator.) At |finish:|, any local resources that may have been claimed (in C, chiefly malloced memory and file handlers, but under RISC OS also windows, fonts, vectors, etc) are released. The current function then returns the error back to its own caller.
In this way, when any function returns an error, the whole of the call stack is unwound to the top level where it can be handled cleanly.
This is a just a low-level way of emulating C++-style exceptions.
Regression Testing
The other important point is regression testing. When a change is made, everything is differed with the previous version to make sure that nothing has changed for the worse. For the objasm files, I've provided s;0 directories, but it should be done for headers too: that's easy because they are textual anyway, and you already have copies. The objasm diff checks that the *code* is the same. (The objasm files aren't used for anything else, but the code that writes them is the same as the code that writes the tiny files that make up the library as a whole, so it's important.)
This is your guarantee that everything you do will work.
Generating the library
Installation is roughly as follows: run SetVars; open DefMod; run !Make (twice) (you need yacc (or Bison) for this); copy the DefMod absolute into the OSLib directory, or into your path; run !Make in OSlib.
In Progress
There are sketches or starts or initial hacks at some more swi files in InProgress.
The comments in the objasm files "Registers available for scratch use" was a piece of work-in-progress. The idea was from Mark Wooding, I think: he suggested that instead of stacking values that must be saved over the SWI, it would be more efficient to save them in registers that were not otherwise used. (These are V1--V6 if the SWI will corrupt them, as APCS demands they are preserved by a function call, and any registers containing addresses of output arguments that are needed on entry to the SWI or corrupted by it.) This is commented out, not finished, and doesn't work. Feel free to give it a shot.
DefMod can make SrcEdit help files for either C or assembler---no-one wants these, though. I also started something to do with Pascal, as well as a start to native C++ support (so you wouldn't need the |extern "C"| directives, and potentially allow overloaded functions to call the same SWI).
NDEBUG
I think that defining NDEBUG would stop lookup.c working. That was probably a mistake.
DefMod and SWILib
DefMod is a RISC OS programme written in C, and it looks as though it uses OSLib. If it did, it could never have been written, as it logically precedes OSLib. So how was this done ?
It uses a hand-crafted set of macros called SWILib. This defines the functions provided by OSLib as macros expanding to calls to |_swi(x?)|---but just the SWI's that DefMod uses. (SWILib was the first version of OSLib. The first version of DefMod generated SWILib-style headers: macros rather than function declarations.) Do not change this, or you could potentially end up in a situation where the whole system could not get off the ground.
UNKNOWN types
I have been asked to explain the way the [UNKNOWN] types work. When a type (e g, wimp_menu, wimp_window) has a repeating final part, DefMod also generates some extra macros wimp_MENU (n) and wimp_SIZEOF_MENU (n). You can use the first to write out data structures in your code. It does show how flexible the C type system is, if you use it well.
Here's a menu definition I use in another of my proggies ...
wimp_MENU (2) Main_Menu =
{};"PlaceName",
wimp_COLOUR_BLACK,
wimp_COLOUR_LIGHT_GREY,
wimp_COLOUR_BLACK,
SKIP,
9*wimp_CHAR_XSIZE,
wimp_MENU_ITEM_HEIGHT,
wimp_MENU_ITEM_GAP,
{}{
wimp_MENU_GIVE_WARNING,
wimp_DEFER_SUB_MENU,
wimp_ICON_TEXT | wimp_ICON_FILLED |
wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT |
wimp_COLOUR_WHITE << wimp_ICON_BG_COLOUR_SHIFT,
"Info"
},
{
wimp_MENU_LAST,
wimp_NO_SUB_MENU,
wimp_ICON_TEXT | wimp_ICON_FILLED |
wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT |
wimp_COLOUR_WHITE << wimp_ICON_BG_COLOUR_SHIFT,
"Quit"
}
This is then used in calls like
wimp_create_menu ((wimp_menu *) &Main_Menu);
It needs to be cast. If you want to build a structure like this on the fly, you can write
and use references likewimp_menu *menu = malloc (wimp_SIZEOF_MENU (2));
... menu->entries [1].data AS indirected_text.text ...
to get at the text of the 1st icon.
So these macros can let you write some very complicated data structures right in the code, without needing to read them from a resource file, but still in a reasonably maintainable way.
Hierarchy
In general, you you must always make sure you know where a module fits in with other modules. There must be a strict hierarchy, with files only needing (the NEEDS statement) files in an acyclic fashion. That's not say things can't be introduced into the middle (JPEG needs OS, but PDriver needs JPEG, even though JPEG didn't exist till long after PDriver was first thought of.) DrawFile is another complicated one: does DrawFile need Font (Draw objects can contain font handles), or does Font need DrawFile (the Font manager can write font outlines to a Draw group). This is a tie really, and it's broken in an unpleasant way. It might have been better to invent a "graphics.h" to be included by both, but that would break the correspondence between swi files and modules.
Extensible Interfaces
There are lots of cases in RISC OS of extensible interfaces: Wimp messages can be defined by anyone, for example. It is not good in these cases to put all of them into a file (swi.Wimp): this would mean that the file would have to be extended all the time. Instead, the basic interface is provided in 1 file, and other people can write their own header files. This is what's done in swi.Gadget. No gadget types are defined there: that's all done in a distributed way. This is also why the wimp_message struct only has a few of the defined message data types in its data field. (It has all of the ones defined as "system" and "Wimp" types.) Ones defined by modules (e g, message_HELP_REQUEST, printing messages, alarm messages) are defined by their respective modules. Otherwise, every time a new module added a message, "wimp.h" would have to be updated. This means that some otherwise avoidable type casts are needed; oh well.
Help Files
DefMod writes HelpData and Index files for use by StrongHelp. These are all copied into a big archive by the Make process. (This has to be an archive only because there are lots more than 77 files). Then Alexander Thoukidides' ConvHelp can turn all of these into a StrongHelp help file. If you use Zap, you can bind F1 to mje_helpcontext, and then you point to a SWI name in your code and get help on it in 1 keystroke. This help is all linked together internally, and in 2 clicks you can get all the valid values for any constant.
Parsing
You need a version of YACC or Bison. The one I used was ported by me from the Berkeley YACC sources. I could have used Bison, but then OSlib would have fallen under the GPL. At that time, I wanted to keep the 'swi' files private, so that there wouldn't be incompatible extensions to widely-used modules; but I'm convinced now that that isn't a problem. The approach to the parsing is is unconventional in 2 ways:
- I didn't use Lex (or Flex): everything is done by the grammar in defmod.y. This was just so I didn't have to learn Lex as well as YACC, and to see if it could be done.
- String values are not put in a symbol table and carefully managed. Instead, the grammar passes round whole strings of up to 255 characters on the stack and copies them every time a character is appended. This is hopelessly slow and inefficient---but it works, and it's "fast enough". The limit is not checked in the code, despite what I said above. Sorry.