马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本帖最后由 sachindkini 于 2013-5-13 15:11 编辑
Error HandlingEver 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 resetting their OSnaps. This is where a suitable Error Handler comes into its own.
The *error* FunctionThe *error* function is a user-definable function which will be evaluated when AutoCAD encounters an error when executing a LISP expression. The *error* function takes a single string argument passed to it by AutoCAD when an error is encountered. 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: [pcode=lisp,true](itoa nil)[/pcode]
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 nil value to the itoa function, which was expecting an integer (indicated by the 'fixnump' message).
Creating an Error HandlerThe 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. [pcode=lisp,true](defun c:test (/ *error*) (defun *error* (msg)
(princ "error: ")
(princ msg)
(princ)
)
(getstring "\nPress Esc to force an Error...")
(princ)
)[/pcode]
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.
Notice that the *error* symbol is 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: [pcode=lisp,true]error: Function cancelled[/pcode]
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: [pcode=lisp,true](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)
)
[/pcode]
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 EnvironmentSometimes programs will change System Variables during execution to achieve certain results, however, if the user hits Esc at some point when running the program, without the use of an appropriate error handler, these System Variables will not be reset to their original settings. We can reset any settings changed by the program through the use of a user-defined error handler, as the following example demonstrates: [pcode=lisp,true](defun c:test (/ *error* osmode) (defun *error* (msg)
(if osmode
(setvar 'osmode osmode)
)
(if (not (member msg '("Function cancelled" "quit / exit abort")))
(princ (strcat "\nError: " msg))
)
(princ)
)
(setq osmode (getvar 'osmode))
(setvar 'osmode 0)
(rtos (getreal "\nPress Esc to exit, press Enter to force an error ..."))
(setvar 'osmode osmode)
(princ)
)
[/pcode]
The above code will first store the current setting of the OSMODE System Variable before setting it to 0, and will then restores 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 variable osmode 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 HandlerIn 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: [pcode=lisp,true](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)
)
[/pcode]
Upon calling the program with test at the command line and pressing Esc at the prompt, the following message will be displayed: [pcode=lisp,true]*error* inside of Test Program.[/pcode]
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: [pcode=lisp,true](itoa nil)[/pcode]
The previously defined error handler will evaluate, to display the message: [pcode=lisp,true]*error* outside of Test Program.[/pcode]
To reset to the default error handler, we can clear the value of the *error* symbol: [pcode=lisp,true](setq *error* nil)[/pcode]
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 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: [pcode=lisp,true](defun c:test (/ myerror olderror)
(defun myerror (msg)
(setq *error* olderror)
(princ "\n*error* inside of Test Program.")
(princ)
)
(setq olderror *error*
*error* myerror
)
(getstring "\nPress Esc to force an error...")
(setq *error* olderror)
(princ)
)
[/pcode]
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 myerror taking a single string argument: msg. This function will act as our redefined error handler. Next, we have these lines: [pcode=lisp,true] (setq olderror *error* *error* myerror )[/pcode]
Here, we are storing the function defintion of *error* in a local variable: olderror, and pointing the *error* symbol to our new definition myerror. This means that if the program encounters an error and the *error* symbol is evaluated, our locally defined function myerror 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 myerror 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: [pcode=lisp,true](setq *error* olderror)[/pcode]
Hence setting the *error* symbol to its previously stored value.
Further Information
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,
|