home *** CD-ROM | disk | FTP | other *** search
- The exception library
- --- --------- -------
- When writing code in BASIC, we are used to installing error-handlers
- to cope with SWI's that generate errors. In C this is less common,
- mainly because of the problem of writing nested error-handlers in a
- modular way. Nevertheless, there are still advantages to doing so.
-
- In RISC O S, any system call can return an error indicator, of type
- |os_error *|, instead of the results that are expected under normal
- termination. If the normally-terminating SWI could be considered to be
- of type
-
- Entry -> Exit,
-
- where Entry is the type of its entry conditions and Exit the type of its
- exit conditions, then the SWI as provided must be considered to be of
- type
-
- Entry -> Union (Exit, os_error *).
-
- In general, both Entry asnd Exit are compound types: for example, the
- type of pdriver_draw_page() is
-
- Struct (int, os_box *, int, char *, int) -> Struct (bool, int)
-
- (which would be rendered into C as void pdriver_draw_page (int, os_box
- *, int, char *, bool *, int *). However, this ignores the fact that it
- might return an error instead; so the real type is
-
- Struct (int, os_box *, int, char *, int) ->
- Union (Struct (bool, int), os_error *)
-
- which is rendered as os_error *xpdriver_draw_page (int, os_box *, int,
- char *, bool *, int *).
-
- The problem with using functions of this form is that every single
- return value must be checked, and appropriate action taken if an error
- has been returned. This leads to code like
-
- if ((error = xpdriver_draw_page (1, &req, page, NULL, &more,
- NULL)) != NULL)
- goto finish;
-
- (If you don't like 'goto,' then feel free to imagine there is a call to
- an error-handling function there instead. The main argument is not
- affected by this.) In effect this "wastes" R0 (the register in which the
- result is returned), and the following test, on a condition which hardly
- ever occurs.
-
- RISC O S, and the C run-time kernel, provide a mechanism to avoid
- this. By calling the non-X form of the SWI, we can ensure that the
- normal flow of control of a programme is interrupted when an error
- occurs, without having to test for it explicitly. This also means that
- the return value of the function can be used to return the most useful
- output argument. (Which one this is is a matter of taste, and OSLib
- makes this decision in a hopefully-useful way.)
-
- Therefore, the call can be written as
-
- more = pdriver_draw_page (1, &req, page, NULL, NULL);
-
- To do this requires the installation of an error handler. The C run-
- time system does some of this for you. An exception library, the x_
- module, does the rest.
-
- To use the exception library, code is written in this form:
-
- { x_exception x;
-
- /statements (1)/
-
- x_TRY (&x)
- /statement (2)/
- x_CATCH (&x)
- /statement (3)/
-
- /statements (4)/
- }
-
- The first point is the declaration. To catch an exception locally,
- the C run-time system needs some space to store various things. This is
- provided in a structure called an |x_exception|. This must be declared
- like this---on the stack, not static---or nested error handlers won't
- work.
-
- |x_TRY| may be thought of as having a similar syntax to |do|;
- |x_CATCH()| to |while|. Both must be present, at the same lexical level,
- just as above---if an |x_TRY| is not balanced by an |x_CATCH|, the
- effects are highly unpredictable. They must both be provided with
- pointers to the same |x_exception| structure. The first statement is
- usually a block (in {...}), called the try block, though it could be a
- single statement. Exceptions thrown from here cause control to be
- transferred to the statement after |x_CATCH()|, called the catch block.
- If there are no exceptions, control does not enter the catch block at
- all. If any exceptions occur in the statement not in the try block (in
- the catch block or outside the |x_TRY ... x_CATCH| construction
- altogether), they are just thrown as normal (i e, passed up to enclosing
- functions).
-
- After control has passed through |x_TRY()|, |x_RETHROW()| may be used
- to propagate the exception further. This would normally be the preferred
- course.
-
- This means that within the try block, you are free to put calls to
- non-X SWI's. Any SWI error, escape or stack overflow error (any of which
- can happen to correct programmes) will be caught and handled locally,
- without wasting code on explicit error checking. RISC O S does it for
- you!
-
- Normally, any exceptions caught should be rethrown, as in the example
- code below, or the code that called you will be unaware of the error
- that has occurred. This is an important rule: without it, functions
- could end up allocating resources that they would not free, and the
- whole purpose of the module would have been subverted. If you think of
- the whole calling tree at the moment of the exception, control will pass
- to the innermost-nested function that has a catch block; this function
- should do whatever tidying up it needs, and then rethrow the exception
- to the next enclosing one, and so on until the stack is flat and all
- interested functions have had a chance to act.
-
- The stack does not have to be completely flat, however: normally, a
- command-line driven programme would just report the error and ask for
- another command, and a Wimp-based one would use Wimp_ReportError and
- poll again. The application writer has complete flexibility.
-
- Blocks written as above may be nested, either in the try block or in
- the catch block, and only functions that need to do tidying-up after
- themselves need to use |x_TRY| and |x_CATCH|. Functions that they call
- can just "get on with the job:" if they call a SWI, or any other
- function, that raises an exception, the exception will just "bubble
- back" through all the error handlers, back to the level required by the
- application writer.
-
- There are other utilities to round out the module:
- |x_THROW_LAST_ERROR| throws the last error detected by the run-time
- system. It is normally used after a library call fails. |x_THROW| throws
- a given error, in just the same way that the SWI's do (it is just
- os_generate_error(), really: either way be used), and x_LOCAL_ERROR
- declares one for such use. For example,
-
- if ((file = fopen ("DataFile", "w")) == NULL)
- x_THROW_LAST_ERROR ();
-
- /at this point, we know that |file| is a valid handle/
-
- Code to open a file, write to it and close it again would look like
- this:
-
- FILE *f;
-
- if ((f = fopen ("DataFile", "w")) == NULL)
- x_THROW_LAST_ERROR ();
-
- x_TRY (&x)
- { if (fprintf (f, "Hello, World!\n") < 0)
- x_THROW_LAST_ERROR ();
-
- /lots of complicated output to a file, which might go wrong/
- }
- x_CATCH (&x)
- ;
-
- if (fclose (f) == NULL)
- x_THROW_LAST_ERROR ();
- x_RETHROW (&x);
-
- Note the structure:
-
- do something that changes the global state (open file, allocate
- memory, etc)
- TRY
- operate on the changed state (write to file, use memory)
- CATCH
- ;
- restore the state (close file, free memory)
- rethrow the error
-
- Often, as here, the catch block is empty, since the tidying-up code is
- the same in the error case as in the correct case.
-
- x_LOCAL_ERROR() allows a static error block to be constructed: this
- can then be thrown as a new error. The effect is exactly the same as if
- a SWI was called and threw the error. It should be used at the top level
- of a source file. For example, code to check for a text file might look
- like this:
-
- x_STATIC_ERROR (Error_Wrong_Type, 1, "File must be of type Text")
-
- fileswitch_object_type obj_type;
- bits file_type;
-
- if ((obj_type = osfile_read_stamped_no_path ("Fred", NULL, NULL,
- NULL, NULL, &file_type)) != fileswitch_IS_FILE)
- osfile_make_error ("Fred", obj_type);
-
- if (file_type != osfile_TYPE_TEXT)
- x_THROW (Error_Wrong_Type);
-
- /... if we are here, we have a text file. Conversely, if the file
- was not a text file, the right error has been thrown back to
- the caller .../
-
- There is also x_GLOBAL_ERROR, which does the same job but without the
- 'static': it should be used where the error blocks that a source file
- uses are part of its interface. In this case, the identifier should also
- be declared as an |os_error *| in the source file's accompanying header
- file.
-
- There are also functions x_EXIT(), which exits a programme, printing
- an error message to |stderr| if one has occurred, and x_REPORT_EXIT()
- which exits a programme, calling wimp_report_error() if an error has
- occurred. These should only be used in the scope of the |x_exception|
- structure|, after a use of |x_TRY|.
-
- There are also functions |x_alloc|, |x_calloc|, |x_free| and
- |x_realloc| which are like |malloc|, |calloc|, |free| and |realloc|, but
- throw an exception on failure. (It's unfortunate that the relationship
- between |x_malloc| and |malloc| is the opposite of that between (say)
- |xosmodule_alloc| and |osmodule_alloc|, but it's a logical consequence
- of the module naming convention.)
-
- Conclusion
- ----------
- Any SWI can in principal generate an error at any time, for any
- reason. These errors are usually associated with the exhaustion of some
- resource (memory, file handles, disc space, etc), and code that checks
- error-returning SWI's, and in turn returns the error pointer back to its
- caller, is time-consuming and inefficient. Using a handler, and error-
- generating SWI's, saves 1 or 2 instructions per SWI veneer (the veneer
- doesn't have the 'BVS %99; MOV R, #0' or 'MOVVC R, #0'); and typically 2
- at each function call ('CMP R0, #0; BNE R0, finish' in the example
- above). It also enables more readable code to be produced. For example,
- if you check all return codes, a printing loop might look like this:
-
- if ((error = xpdriver_draw_page (1, &req, page, NULL, &more,
- NULL)) != NULL)
- goto finish;
-
- while (more)
- { if ((error = xcolourtrans_set_gcol (os_COLOUR_BLACK,
- colourtrans_SET_FG, os_ACTION_OVERWRITE, NULL, NULL))
- != NULL)
- goto finish;
-
- if ((error = xfont_paint (0, t, NONE, 0, 0, NULL, NULL, 0))
- != NULL)
- goto finish;
-
- if ((error = xpdriver_get_rectangle (&req, &more, NULL)) !=
- NULL)
- goto finish;
- }
-
- finish:
-
- whereas if you can call x-clear SWI's, you can write instead:
-
- for (more = pdriver_draw_page (1, &req, page, NULL, NULL);
- more; more = pdriver_get_rectangle (&req, NULL))
- { colourtrans_set_gcol (os_COLOUR_BLACK, colourtrans_SET_FG,
- os_ACTION_OVERWRITE, NULL, NULL);
-
- font_paint (0, t, NONE, 0, 0, NULL, NULL, 0);
- }
-
- The first example above compiles to 52 instructions; the second to 44,
- for a saving of an enormous 20% in code size (and knock-on effects for
- callers). This could also be expected to translate into a 20% speed-up
- as well, since all the extra instructions have to be executed. The code
- is also substantially easier to write, understand and modify.
-
- The x_ module does not interfere in any way with errors that are not
- expected: namely, abort, arithmetic exception, illegal instruction, bad
- memory access, termination request. These should probably only be
- handled at the very top level in order to provide a last-ditch error
- handler: it could do something like saving all unsaved data into a scrap
- file.
-
- The C library throws an escape condition when the user presses
- Escape, but this behaviour is normally suppressed by the Wimp, except in
- special cases like printing. By enabling and disabling escapes around
- Wimp_Poll, an application could be easily written to abort its current
- operation on Escape and return to its main polling loop, with very
- little special code needed, and no risk of files being left open, etc.
-
- The OSLib SWI veneers provide both X and non-X forms of each SWI
- function. When writing an application, or a library to be used by
- others, there is a big decision to make: do its functions return
- |os_errorĀ *|, and call X-form SWI's, or do they return a useful result
- and call non-X-form SWI's (with error-handlers in the right places)? In
- other words, do the library functions mimic x-set SWI's, or x-clear
- SWI's?
-
- The libraries and modules described enable us to write better
- applications in less time, that run faster and require less memory to
- run in.
-