Minimalist exceptions


Tweet blitz3d blitzbasic blitzplus code-archives userlibs
(Posted 1 week ago) RonTek

This code requires MikhailV's free FastPointer DLL.

Author: Yasha

This pair of rather unpleasant C functions lets you clunkily imitate exception handling in Blitz3D. For those who haven't used it before, exception handling lets you define control points with "handlers", and if you encounter an error deep inside many nested function calls, jump straight up the stack to the control point without having to mess about with returning error codes and have each function report that it failed to its individual call site and so on and so forth. Very good for the lazy coder.

In BlitzMax, you'd define the control point and handler with a Try/Catch block. We don't have those in Blitz3D, and have no way to define new syntax, so instead what we do is define the body and handler as two separate functions, and pass their function pointers to WithExceptionHandler (this is why you need the FastPointer DLL: this can't be done without function pointers), along with an argument for the body code.

Here's a demonstration:

Function Foo(a)
Print "Opening Foo with argument " + a
Bar(42)
Print "Ending Foo (never reached!)"
End Function

Function Bar(a)
Print "In Bar with " + a

;Oh no! An 'exceptional situation' requires special abort procedure!
ThrowException(42)

Print "We never get here!"
End Function

Function Hdl(e)
Print "Handled exception code: " + e
Return 64
End Function

Local fptr = FunctionPointer() : Goto skipF : Foo(0)
.skipF

Local hptr = FunctionPointer() : Goto skipH : Hdl(0)
.skipH

Print "WithResult: " + WithExceptionHandler(fptr, hptr, 47)

WaitKey
End

In the event you hit an "exceptional situation", calling ThrowException will jump right out of whatever you were doing, all the way back to the level of WithExceptionHandler, and call the handler function with whatever you passed as the exception code, which can then take any necessary steps to rectify the situation using this code (use this to signal a particular flag or perhaps pass a data object). WithExceptionHandler will then return the result of the handler function instead of whatever would have been returned had no exception been thrown.

So in the above example:
-- WithExceptionHandler is called with Foo, Hdl and 47
-- WithExceptionHandler calls Foo with 47
-- Foo does some stuff and calls Bar with 42
-- Bar panics, as its mathematics breaks down around the non-value that is 42
-- Bar calls ThrowException with 42
-- ThrowException nukes the call stack all the way down to WithExceptionHandler again
-- WithExceptionHandler is informed that something went wrong, and calls Hdl to deal with it
-- Hdl deals with the problem, and returns a final value

It's not that difficult once you get used to it: it's basically just a way to immediately return past more than one function call at a time. Note that this doesn't catch or otherwise interact with system or hardware exceptions (division by zero, MAV, etc.) - it only works with your own "soft" errors.

Be warned however, that bypassing everything that happens in the rest of the function will also bypass B3D's own internal cleanup instructions! These are important for dealing with object reference counts, string memory, and other things that it manages to keep things safe. If you jump out of the middle of a function (or there's one in the middle of the nuked section of stack) that would have needed to cleanup strings or type objects, you will likely get memory leaks as the hidden cleanup code is also bypassed! Not to mention that of course any Free calls of your own will be skipped over too.

So, I suggest for safety:

-- preferably only use this code with functions that deal in integers and floats (you should be safe with entities, images, banks and other "int handle"-style objects)

-- if you have to use type objects, only pass them in parameter slots or via global variables. DO NOT use anything that would "return" an object (this includes the Object command itself) if there's a risk that the cleanup code will be skipped over. Simple parameters appear to be OK as B3D optimises out their refcounts anyway. If in doubt, do not use.

-- Do not use unboxed strings anywhere near this code. Literals should be OK, but I expect using string variables is asking for disaster. Pass them in globals or type objects if necessary.

-- finally, this code is not safe. If in doubt, do not use it at all! This is for advanced users who want to "play about" only! "Using setjmp for exception handling" is one of those things that almost everyone on the internet will tell you never to do, and with good reason.

Stern warnings aside... enjoy! The entry below contains both .decls and DLL (that's all! really! I can't believe it's that simple either); alternatively, download a prebuilt version.

;/* Exceptions.decls:

.lib "Exceptions.dll"

WithExceptionHandler%(proc%, handler%, arg%) : "[email protected]"
ThrowException(code%) : "[email protected]"

; */

#include <stdlib.h>
#include <setjmp.h>

#define BBCALL __attribute((stdcall))

static jmp_buf * topBuffer;
static int code;

BBCALL int WithExceptionHandler(int (* proc)(int) BBCALL,
                         int (* handler)(int) BBCALL,
                         int arg) {
    jmp_buf env;
    jmp_buf * prev;
    prev = topBuffer;
    topBuffer = &env;
    int res;
    if (!setjmp(env)) {
        res = proc(arg);
        topBuffer = prev;
    } else {
        topBuffer = prev;   // Can't return here on further problems
        res = handler(code);
    }
    return res;
}

BBCALL void ThrowException(int c) {
    code = c;
    longjmp(*topBuffer, 1);
}

Reply To Topic (minimum 10 characters)

Please log in to reply