Ever used a program only to discover some time later that your Object Snaps have been mysteriously cleared? Or maybe AutoCAD is suddenly behaving differently? These are tell-tale signs that you have aborted a program that is not equipped with an appropriate error handler.
This tutorials aims to teach you how to create an error handler for your programs to deal with the clean-up operation when something in the code goes wrong.
What can go Wrong?
Errors can arise in many forms: from a null user input, to attempting a division by zero; although, what most users don't realise is that thumping the Esc key during program execution is also considered an error and will hence abort the program instantaneously. This usually induces the situation in which users find themselves manually resetting their osnap settings. This is where a suitable error handler comes into its own.
The *error* Function
The *error* function is a user-definable function which will be evaluated when AutoCAD encounters an error when executing an AutoLISP expression.
The *error* function takes a single string argument, which is supplied by AutoCAD when an error is encountered during program evaluation. This string describes the error which has caused the *error* function to evaluate.
The standard error handler used by AutoCAD will simply print this argument to the command line; you can see this behaviour first hand by typing at the command line something like:
This will in turn cause the standard error handler to evaluate and print the following message at the command line:
; error: bad argument type: fixnump: nil
Indicating that we have passed a value of nil to the itoa function, which was expecting an integer (indicated by the fixnump message).
Creating an Error Handler
The main purpose of an error handler is to restore a user's AutoCAD environment back to its original settings should something in a program goes wrong. To accomplish this, we can redefine the *error* function.
(defun c:test ( / *error* ) (defun *error* ( msg ) (princ "error: ") (princ msg) (princ) ) (getstring "\nPress Esc to force an error...") (princ) )
When running the above program using the command test at the command line, upon pressing Esc at the prompt, the program will be aborted and the error handler will be evaluated.
Notice that the above *error* function takes a single argument: msg which, when the *error* function is evaluated, is passed a string describing the error which has occurred; the above function then proceeds to print this message to the command line. This particular function emulates the standard AutoCAD error handler.
Note that the *error* symbol is declared as a local function (localised) - this ensures that the default AutoCAD error handler is restored following completion of the program. For more information on localising symbols, see my tutorial on Localising Variables.
A More Professional Error Handler...
At this point you might have noticed that upon pressing Esc at the prompt, above the error handler will print a message similar to:
error: Function cancelled
Of course, many users will regularly press Esc to exit a program, so a developer would want to suppress this error message in order to better emulate the behaviour of standard AutoCAD commands.
However, at the same time, we would also want to print any other critical error messages to the command line when an error has occurred outside of the user's control.
To achieve this, we can define the *error* function in the following way:
(defun c:test ( / *error* ) (defun *error* ( msg ) (if (not (member msg '("Function cancelled" "quit / exit abort"))) (princ (strcat "\nError: " msg)) ) (princ) ) (rtos (getreal "\nPress Esc to exit, press Enter to force an error ...")) (princ) )
The above if statement simply says:
"If the error message is neither "Function cancelled" nor "quit / exit abort", then print the message."
Now, upon testing the above code, should the user hit Esc at the prompt, the error handler will not print an error message; however, if the user hits Enter at the prompt, the rtos function is supplied with a nil argument and the error handler will print the relevant error message.
Resetting the User Environment
Sometimes programs will change system variables during execution to achieve certain results; however, if the user presses Esc at some point after a system variable has been altered and not yet reset, without the use of an appropriate error handler the system variable will not be reset to its original value.
We can reset any settings changed by the program through the use of a user-defined error handler, as the following example demonstrates:
(defun c:test ( / *error* osm ) (defun *error* ( msg ) (if osm (setvar 'osmode osm)) (if (not (member msg '("Function cancelled" "quit / exit abort"))) (princ (strcat "\nError: " msg)) ) (princ) ) (setq osm (getvar 'osmode)) (setvar 'osmode 0) (rtos (getreal "\nPress Esc to exit, press Enter to force an error ...")) (setvar 'osmode osm) (princ) )
The above code will first assign the current setting of the OSMODE system variable to a local variable (osm) before setting the system variable to 0. The program will then restore the original setting following the prompt.
Should the user hit Esc (or Enter) at the prompt, the locally defined *error* function is evaluated.
The error handler tests the local variable osm for a non-nil value, and, should the original setting of the OSMODE system variable be available, the system variable is reset to this value.
Restoring the Previous Error Handler
In all the examples featured in this tutorial, I have localised the *error* symbol. This ensures that, following completion of the program, the *error* symbol reverts to the value it held before the program was evaluated (of course this value may be [and usually is] nil).
We can witness this behaviour by considering the following code:
(defun *error* ( msg ) (princ "\n*error* outside of test program.") (princ) ) (defun c:test ( / *error* ) (defun *error* ( msg ) (princ "\n*error* inside of test program.") (princ) ) (getstring "\nPress Esc to force an error...") (princ) )
Upon calling the program with test at the command line and pressing Esc at the prompt, the following message will be displayed:
*error* inside of test program.
Now, due to the localisation of the *error* symbol in the program, the *error* symbol will now revert to its previous value: that which is defined outside of the program's function definition.
So, if we now type at the command line:
The previously defined error handler will evaluate, to display the message:
*error* outside of test program.
To reset to the default error handler, we can clear the value of the *error* symbol:
(setq *error* nil)
If AutoCAD encounters an error during program execution and the *error* symbol is nil, the default, in-built error handler will be evaluated to print the error message as mentioned earlier in this tutorial.
An Alternative to Function Localisation
Here I will demonstrate another method to reset the previous error handler following program completion, without localising the *error* function symbol. Some error handling tutorials that I have witnessed utilise this method, but note that it will perform in exactly the same way as the localisation of the *error* symbol, moreover, in my opinion the localisation is a cleaner and more readable solution.
Consider the following code:
(defun c:test ( / myerr olderr ) (defun myerr ( msg ) (setq *error* olderr) (princ "\n*error* inside of test program.") (princ) ) (setq olderr *error* *error* myerr ) (getstring "\nPress Esc to force an error...") (setq *error* olderr) (princ) )
I'm sure that you would concur that the above alternative method is far less succinct than a simple symbol localisation; however, let us dissect the code to reveal the process that is occurring.
First, we define a function myerr taking a single string argument: msg. This function will act as our redefined error handler.
Next, we have these lines:
(setq olderr *error* *error* myerr )
Here, we are assigning the current function defintion held by the *error* symbol to a local variable: olderr, and pointing the *error* symbol to our new function definition: myerr.
This means that if the program encounters an error and the *error* symbol is evaluated, our locally defined function myerr will be evaluated.
Now, since we are not localising the *error* symbol, the previous definition of *error* must be restored 'manually'. This needs to occur not only at the end of the program but also within the myerr function so that, should the program be aborted via the error handler, the previous error handler defined is still restored.
This restoration is accomplished by the line:
(setq *error* olderr)
Hence assigning the previously stored function definition to the *error* symbol.
The Visual LISP IDE Help Documentation offers further information regarding error handling in AutoLISP - be sure to read the following topics:
AutoLISP Reference » AutoLISP Functions » E Functions » *error*
AutoLISP Developer's Guide » Using the AutoLISP Language » AutoLISP Basics » Error Handling in AutoLISP
For an introduction into using the Visual LISP IDE, see my tutorial: An Introduction to the Visual LISP IDE.