An error a day....

Note : A word of thanks for the macros used in this tutorial should go to David Ruck and Stuart Brodie, who pointed them out on comp.sys.acorn.programmer in April 2001.

Up until now, I have been rather naughty. While I have been using the _swix form of call, I have not been checking to see if the call has actually worked and if it hasn't, trap the error, inform me and then do something else (like a simple quit). This can be performed very simply by making use of a macro, a new function and the RISC OS error block.

The error block is defined in kernel.h as

typedef struct {
                int errnum;           /* error number */
                char errmess[252];    /* error message*/
               } _kernel_oserror;

(a similar definition is given in both EasyC, GCC and LCC).

How do we use it?

Firstly, we define a macro in a header file (called error.h). This will look something like

/* error.h */

extern void wimp_error(_kernel_oserror *);

#define E(a) if ((a)!=NULL) {wimp_error(a);}

wimp_error is our wimp handling function and E(a) is our error macro. Simple enough.

Next we require a function to handle this. Currently, we are using our own one called report_error. The one we need to use is almost the same. report_error now becomes redundant and can be removed from the source.

void wimp_error (_kernel_oserror *error)
{
 _kernel_oserror *er;
 er = _swix(Wimp_ReportError,_INR(0,2),error,1,TASK_NAME);
 E(er);
}

You will notice though that this version is a lot smaller.

While for the moment, this will suffice, it will need to be changed later if we want to check the result. This will also require changing the header file as well. The change is very small, but may confuse matters at this point.

What does this do though?

To explain this, I will look at the Wimp_ReportError swi. According to the PRM, the first parameter is the pointer to the error block. The error block contains the error number and the error message. The error number is a number from the module responsible for the swi call with an associated text message. The error box is comprised like this

Error box

(you will have seen plenty of these!)

flags (Wimp_ReportError, r1)

Depending on the bit set for the flags, depends on which action buttons are displayed in the error box. For instance, if you just want an OK box, you would place in r1 a one.

Other values are

Bit Meaning when set
0 OK box
1 Cancel box
2 Highlight cancel (or OK if no cancel box)
3 Dont do 'Press space or click mouse to continue)
4 Don't prefix title with 'Error from'
5 Return immediately with r1 = 0 and box open
6 Simulate iconclick in box according to bits 0 and 1
7 Don't beep
8 Use categories
9 Catagories
12-23 Reserved. Must be zero
24-27 Number to return for "Continue" of "may have gone
wrong" (OS 4.00+)
28-31 Number to return for "Quit" of "may have gone wrong"
(OS 4.00+)

For the present, I will not deal with bits 8 and 9.

As with the poll mask, you can calculate the bits to be set in much the same way i.e. if you wanted an OK box which was highlighted and not to beep, you would do something like

mask = 1 + (1<<2) + (1<<7)

and place mask in r1 of the swi.

MessageTrans

The MessageTrans swis are very useful. They mean that all the error messages are kept externally with only a reference code (known as a token) held within the program. This cuts down on the size of the compiled file and also allows for other languages to be used with your programs. The messages are (normally) kept in a file called Messages. This does necessitate that your compiled file is kept within an application - this is no bad thing, it makes things look tidier.

Tokens can be anything you like, though it makes sense to give them some meaning!

On the cover disc application, there is a Messages file within the application. This contains tokens such as Comp1, Comp2, Sinc3, Sinc4 and Lang5. All of the tokens are then separated by a colon (:).

These are referenced by the program. While this example is not that much use for converting the messages to another language, the principle is the same. If the token "foo" has the message "Machine is about to close down.", this will read totally differently in French, German or Welsh.

However, as the token has not changed, the program does not need to be concerned with this.

How to code it up

A couple of variables have to be set up prior to using the MessageTrans series of swis. A small array and a couple of pointers are required for MessageTrans_OpenFile. I will come to these presently.

The first function required is one which tells the application how large the Messages file is and if it can be held in memory (memory here is within the wimpslot allocation) or if it has to be reference from the disc. Also returned by the call is the size of buffer required to hold the messages file. The swi used for this is MessageTrans_FileInfo.

The second function will physcially open the file and the third is counted as the function which looks up and returns the either the token or the message.

As code, it may look like this. E is the error macro as used before. EXIT_FAILURE is defined in stdlib.h. While we could have used exit (1), the use of EXIT_FAILURE shows at a glance that the reason for the program crashing is that it is due to a failure. The analogy of EXIT_FAILURE is EXIT_SUCCESS which is also defined. Numerically, they correspond to 1 and 0 respectively.

int mess_data [4]; /* four word data structure */
int *mess, *mem;
void message_fileinfo(void)
{
 int buffer,flags;
 _kernel_oserror *er
 er = _swix(MessageTrans_FileInfo,_IN(1)|_OUT(0)|_OUT(2),
            "<app$dir>.Messages",&flags,&buffer);
 E(er);
 if (er != NULL) exit (EXIT_FAILURE)
 if (flags & 1 == 1) buffer = 0;
 open_message_file(buffer);
}

void open_message_file(int buffer)
{
 _kernel_oserror *er;
 if (buffer != 0)
 {
  mem = malloc(buffer);
  if (mem == NULL)
  {
   strcpy (er->err_mess,lookup("MemFail"));
   wimp_error(er);
   exit (EXIT_FAILURE);
  }
 }
 mess = mess_data;
 er = _swix(MessageTrans_OpenFile,_INR(0,2),mess,
            "<app$dir>.Messages",mem);
 E(er);
 if (er != NULL) exit (EXIT_FAILURE);
}

char *lookup(char *token)
{
 char *out,*buff;
 int size;
 _kernel_oserror *er;
 buff = malloc(256);
 if (buff == NULL)
 {
  strcpy (er->errmess,"Lookup malloc failed");
  er->errnum = 0;
  wimp_error (er);
  exit (EXIT_FAILURE);
 }
 er = _swix(MessageTrans_Lookup,_INR(0,7)|_OUT(2),
                      mess,token,buff,size,0,0,0,0,&out);
 E(er);
 if (er != NULL)
 {
  strcpy (er->errmess,lookup("LookFail"));
  er->errnum=0;
  wimp_error (er);
 }
 return out;
}

By using this, we do have to include in the messages file LookFail: and MemFail:. If they are not in the message file (or are, but have been misnamed lookfail: or Memfail:), then only the token is returned. This is very handy for debugging - you can see that your messages file is correct!

Next time, I will look further at the MessageTrans call and show you how to make your messages even more flexible.

Until then, on the cover disc is a small source file (funstuff.c). Have a go at converting this to an application which uses a messages file. Also on the disc is an application shell called !FunStuff. If you open this and save the compiled image as !Runimage into there (as well as placing your messages file in there!) then this should work like a normal application.