Error Catching in Visual LISP and VBA31 Aug, 2001 By: Bill Kramer
Programming applications will often involve the detection of and recovery from errors caused by a variety of sources. Bad input, ill-conditioned data, faulty calculations, or system failures can cause errors in the computer. How your program reacts when an error condition is encountered will sometimes make an important difference when users are involved. If your program just dies and leaves no trace as to why it stopped, then the user is left without any recourse but to try the application again. Although it is not always possible to help the user, any attempt at recovery and reporting will be appreciated.
This situation is especially true if the application is robust in nature and has many different possible avenues of execution driven by the user. As a result, it is important that the program has good error trapping and recovery techniques available to it. Both Visual LISP and VBA provide a variety of tools to assist in error trapping and recovery. These tools include everything from testing functions for checking the data type or range to error event handling objects that help the program recover from more severe situations.
There are several levels where error trapping is employed in programming. Input functions offer the most logical place for the user to supply data for performing the task. Users will sometimes supply data that is completely wrong for any number of reasons. For instance, they could be just testing the program to see what it does under that circumstance; they may not understand the prompt and are guessing, or they might just be bending the rules to see if a special case can be computed for them. In any case, it is a good idea to verify input from the user.Powerful Dialog Boxes
Dialog boxes provide a nice way to isolate the input system. From the users point of view, a dialog box is power. He or she can navigate to any point in the box and supply input. From the programmer's point of view, a dialog box is a series of isolated controls or tiles that have basic objects such as features. They are isolated in that you control what functions are to be run as the user changes the contents of dialog box items. Callback functions and event handlers are called into action when the item is manipulated, which is also when your programming is back in charge. You can get the current contents of the item and check it to see if the user is doing something incorrect. For example, suppose a text entry field is for the input of a whole number, such as how many of something, and the user enters a decimal point. The input event function could reject the decimal point and remove it from the contents of the field. It would appear to the user that the decimal key has been turned off for the input field. They would not see the dot appear and go away in the entry area under most circumstances.
You can also check the validity of the data relative to the application. Suppose you needed a whole number between one and 10. Your input validation function could check the data input to make sure it conforms to the desired range or post a message to the user if it is out of range. The value can then be reset to the maximum or minimum acceptable. Both Visual LISP and VBA have a utility to enable you to send a message to the user while a dialog box is running. In Visual LISP the subr ALERT will create a small dialog box with your text message. VBA has the MSGBOX function for the same purpose. In both cases you can create a text message dialog box on the fly to tell users about the problem with their input. The strings provided can be quite long and may include carriage-control characters. For example, an ALERT string in Visual LISP to say "Input too large" on one line of the dialog box and "Enter a number between one and ten" on the second line would be presented as "Input too large\nEnter a number between one and ten." The "\n" control character sequence will force a new line in the dialog box text output. In VBA, use the constant vbCRLF character for a carriage return and line feed when building the output string.
Most error potentials can be caught at the input stage; however, there are many more problems that could be waiting for your program to wander into. Suppose the data all looks good individually, but is poorly conditioned for the calculations to be performed with it. This can happen in more advanced applications involving many equations or repeated applications of equations to arrive at a solution. Ill-conditioned data can cause errors such as division by zero or an attempt to get a square root of a negative number. In many cases you should be able to detect bad data during the input phase. However, you may want to add additional tests in your code to make sure everything is proceeding okay when there is even the slightest probability of a problem.Error Catches
Of course it is impossible to anticipate all the possible errors a complex application may encounter, and that is why there are additional tools for the programmer called error catches. An error catch is an object that will catch errors and allow you to make a decision based on the error and your program. Visual LISP and VBA both provide such a mechanism; however, they work differently.
VBA contains a special object named ERR. The error object can be activated in your code for a function or subroutine. It is only active inside the subroutine in which you turn it on. Should that subroutine call another function or subroutine, the error object is suspended until the invoked function has completed. If you want to provide error handling in the called function, you will need to enable the error object within it as well.
The ON ERROR statement is used in VBA to activate the error object for a function. Following the key words, ON ERROR will be what you want to have happen when an error situation is encountered. You can force execution to continue, force the program to jump to another place in the code, or cause another function to be run.
For example the statement ON ERROR RESUME NEXT will enable the function in which it appears to continue processing right on through errors. This may be a desirable thing if your function is reading a data file. If the end of file is encountered or a bad record is returned, your program can react by just testing the error object value to see if there was a problem. The NUMBER property of the ERR object will be zero if there was no problem; it will be non-zero if there was a problem. Thus a test to see if ERR.NUMBER is equal to zero will let you know that no errors have been encountered.
The error object also contains a property with the description text that can be used in messages or for your own knowledge when devising a scheme to handle the anticipated errors in the data that may trigger unwanted problems. Do note that the ON ERROR feature of VBA is used in the normal course of programming as well. Creating selection sets and file handling are two areas where the ON ERROR statement is often found.
Visual LISP does not contain an error object as elegant in programming as the ON ERROR statement in VBA. However, it does contain more error-catching capabilities that can be used in your programming since the error-handling system can be set up to span any scope of functions you need. There are two primary error-handling mechanisms available in Visual LISP. The first is a global symbol that can be defined as a function. The function is evaluated as the last thing Visual LISP will do before returning control to the command line in AutoCAD. The symbol is *ERROR*, and a single value is supplied as an argument to the function. The value is a string describing the error that has caused the error handler to trigger. This string is the same error string you will see at the command line if the error function has not been activated.
The error symbol spans all the functions in your application. Should any error occur that Visual LISP deems as fatal in nature, the error function will be activated. Normally this is your chance to save critical variables or restore AutoCAD system variables to the condition they were in before your application started. There is one nuance though. When the user selects to abort the process using the escape key, an error is sent through the error handler. If a dialog box is not visible, the escape key is interpreted by the system as an immediate abort request by the user. This is so that your functions will behave similarly to AutoCADs internal command functions.
Of course there may be times when you don't want the escape key to abort your process, and, if you only use the *ERROR* function, you will be stuck for an answer. Visual LISP does offer an alternative via the VL-CATCH-ALL-APPLY subr. Using this technique requires you to get used to a different look in your programs. However, it does allow for a way to catch any errors yet continue processing without the use of the *ERROR* escape hatch.
VL-CATCH-ALL-APPLY is given two parameters: the name of the function to run and a list of the parameters. The syntax is just like the APPLY subr in Visual LISP. You can think of it as being the same as (EVAL (CONS <function name> <parameter list>)) when the function name is placed at the start of a list and handed to the evaluator. The difference is that, instead of aborting through the *ERROR* function when a problem is encountered, the VL-CATCH-ALL-APPLY will return an answer. The answer will be a special Visual LISP error object when an error occurs, or it will be the normal response from the function as expected during operation.
When used in a program, the VL-CATCH-ALL-APPLY is normally found in a SETQ so that the result can be saved and tested with a subsequent VL-CATCH-ALL-ERROR-P expression. VL-CATCH-ALL-ERROR-P will return true if the value presented as a parameter is an error object. If anything else is provided, the subr will return nil. To learn what error was trapped, the VL-CATCH-ALL-ERROR-MESSAGE subr will return a string with the error value given the error object.
You could use the VL-CATCH-ALL-APPLY subr to intercept impatient users. A WHILE loop can be set up to iterate so long as the response from the VL-CATCH-ALL-APPLY is an error object and the message contains the word cancel. I don't recommend this practice. However, there are times when you need to complete a transaction with another system before your application can exit with grace. Should this be the case for you, I suggest you explore this concept.
Using this tool you can allow a complex set of equations to complete before responding to an escape request by the user. When you start the evaluation of a function under the control of VL-CATCH-ALL-APPLY, the error handler is active throughout the function and all other functions called inside. You will want to keep a tight rein on the scope of that activity for a well-behaved function. If you run your functions with the catch active at all times, then your program may not run properly in the eyes of many AutoCAD users who don't want the ability to cancel programs that are taking too long to execute.
Error processing and how your program responds to errors is something that many users take for granted. In some cases this fact can be the most difficult aspect of your application since you must try to think of ways it will fail. One tactic I use is to allow users a chance to test the software in pieces and see what kinds of input they want to test it against. If there is something that goes wrong, the testing phase is the best time to catch it and then provide the required coding to allow the system to either exit with grace or guide the user towards providing acceptable input. The more users you have, the more time you will want to spend thinking about error recovery and processing. After all, you will spend that time helping the users out personally if you don't. Until next time, keep on programmin'!