Error Trapping with Visual LISP
In standard AutoLisp, if your program encounters an error of any sort, it passes to the *error* function only one thing, a description of the error, and your program then ends (refer to "Error Trapping" for more details).
Let's have a quick look at this in action. Load and run this little routine :
(defun error-test1 () (setq int (getint "\nEnter Number : ")) (setq result (apply 'sqrt (list int))) (alert (strcat "Result = " (rtos result))) (princ) );defun
Enter "4" at the command prompt. This should result in an alert dialog displaying the answer "Result = 2".
Now, run it again and enter "- 4". Your program will come up with an error :
_$ (error-test1)
; error: function undefined for argument: -4
The reason being of course, is that you cannot determine the square root of a negative number. Now let's add our error trap :
(defun error-test1 () (setq temperr *error*) (setq *error* trap) (setq int (getint "\nEnter Number : ")) (setq result (apply 'sqrt (list int))) (alert (strcat "Result = " (rtos result))) (princ) );defun (defun trap (errmsg) (setq *error* temperr) (alert errmsg) (alert "There was an error!!") (princ) );defun
Load and run the program again. The error will be encountered and control will pass to the error trap which will display two alert dialogs, one displaying the error message, and one displaying a user defined error message :
Your program will now stop. In a properly designed program, the error trap function would reset the system to the state it was in before your program started. (eg. snaps would be reset, system variables would be reset, etc).
Now this fine if you're using standard AutoLisp, but if you are using Visual Lisp function, this is another matter. Many of the Visual Lisp functions are designed to be used in the "programming by exception" style. This means they either return useful values if they succeed, or raise an exception if they fail (instead of returning an error value). If your program uses Visual Lisp functions, you must prepare it to catch exceptions, otherwise the program halts, leaving the user at the command prompt.
The advantage of this though, is that your program can intercept and attempt to process errors instead of allowing control to pass to the *error* function.
For this we would use the "vl-catch-all-apply" function
which is designed to invoke any function, return the value from the
function, and trap any error that may occur. You could call it an "inline
local error function."
The function requires two arguments :
- a symbol identifying a function or "lambda" expression
- a list or arguments to be passed to the calling function
Here's how we would use it in our program :
(setq result (vl-catch-all-apply 'sqrt (list int)))
Almost exactly the same as the "apply" function hey!
Now load and run the following, entering "- 4" at the command
prompt to force and error :
(defun error-test2 ()
(setq int (getint "\nEnter Number : "))
(setq result (vl-catch-all-apply 'sqrt (list int)))
(alert (strcat "Result = " (rtos result)))
(princ)
);defun
An error message should appear at the Console prompt :
_$ (error-test2) ; error: bad argument type: numberp: #<%catch-all-apply-error%>
Have a look at the variable "result" in the Watch window :
The variable contains an "error object"!
If the program runs correctly, "vl-catch-all-apply"
stores the return value in "result". If the call is
unsuccessful, "vl-catch-all-apply" stores an error object
in "result".
Using the "vl-catch-all-error-p" function, we can test
for this "error object" :
(defun error-test2 () (setq int (getint "\nEnter Number : ")) (setq result (vl-catch-all-apply 'sqrt (list int))) (if (vl-catch-all-error-p result) (progn (setq int (abs int)) (setq result (vl-catch-all-apply 'sqrt (list int))) );progn );if (alert (strcat "Result = " (rtos result))) (princ) );defun
Load and run this routine entering "- 4" at the command
prompt. This should have caused an error, but because we used the "vl-catch-all-apply"
function, the error was trapped and by testing for the error using "vl-catch-all-error-p",
we were able to rectify the problem by using the "abs" function.
Let's add our normal error function :
(defun error-test2 () (setq temperr *error*) (setq *error* trap) (setq int (getint "\nEnter Number : ")) (setq result (vl-catch-all-apply 'sqrt (list int))) (if (vl-catch-all-error-p result) (progn (setq int (abs int)) (setq result (vl-catch-all-apply 'sqrt (list int))) );progn );if (alert (strcat "Result = " (rtos result))) (princ) );defun (defun trap (errmsg) (setq *error* temperr) (alert errmsg) (alert "There was an error!!") (princ) );defun
Load and run the program, again entering "- 4" at the
command prompt.
No error, everything runs smoothly. Now run the program again, but this
time hitting "Esc" at the command prompt. This error is not an
ActiveX error, therefore the error is passed to our conventional error
trap to be processed.
Hey, we've got two "error" functions to play with now!
Another good example of when to us the "vl-catch-all-apply"
function, is when dealing with "Selection
Sets" using Visual Lisp.
When you first create a selection set in Visual Lisp, all is well and
good. But, if you try to create a selection set that already exists, you
will raise an error. Here's a way around that problem :
(defun selset_test1 () (vl-load-com) (setq acadDocument (vla-get-activedocument (vlax-get-acad-object))) (setq ssets (vla-get-selectionsets acadDocument)) (if (vl-catch-all-error-p (vl-catch-all-apply 'vla-item (list ssets "$Set"))) (setq newSet (vla-add ssets "$Set")) (progn (vla-delete (vla-item ssets "$Set")) (setq newSet (vla-add ssets "$Set")) );progn );if );defun
If the selection sets does not exists, the selection set is created. If it does exist, it is first deleted and then created.