Error Handling
When an AutoLISP program encounters an error — division by zero, file not found, or simply the user pressing Escape — execution stops abruptly. If your program had modified system variables like CMDECHO or OSMODE, they remain in their modified state, which can disrupt the user's workflow. Error handling prevents this problem.
The Problem: An Interruption Leaves an Inconsistent State
Let's revisit the example from the chapter on interacting with drawings:
(defun c:my-drawing (/ old-echo old-osmode)
;; Save and modify variables
(setq old-echo (getvar "CMDECHO"))
(setq old-osmode (getvar "OSMODE"))
(setvar "CMDECHO" 0)
(setvar "OSMODE" 0)
;; Draw...
(command "_LINE" '(0 0) '(100 0) "")
(command "_CIRCLE" '(50 50) 25)
;; Restore variables
(setvar "CMDECHO" old-echo)
(setvar "OSMODE" old-osmode)
(princ)
)
If the user presses Escape during execution, or if an error occurs in one of the commands, the program stops before reaching the restoration lines. The result: CMDECHO stays at 0 and OSMODE stays at 0.
The *error* Function
AutoLISP automatically calls the *error* function whenever an error occurs. By default, this function simply displays the error message. But you can redefine it to run your own cleanup code.
Basic Principle
(defun *error* (message)
;; Cleanup code here
(princ (strcat "\nError: " message))
(princ)
)
The *error* function receives a single argument: the error message as a string. This message can be:
"Function cancelled"— the user pressed Escape"divide by zero"— division by zero"bad argument type"— incorrect argument type"too few arguments"— not enough arguments- etc.
The Standard Pattern
The best practice is to:
- Save the original
*error*function at the beginning of your program - Redefine
*error*with your cleanup code - Restore the original
*error*function at the end (on success) and in your error handler (on failure)
(defun c:my-drawing (/ old-error old-echo old-osmode)
;; 1. Save the original error handler
(setq old-error *error*)
;; 2. Save the system variables
(setq old-echo (getvar "CMDECHO"))
(setq old-osmode (getvar "OSMODE"))
;; 3. Define our custom error handler
(defun *error* (message)
;; Restore system variables
(setvar "CMDECHO" old-echo)
(setvar "OSMODE" old-osmode)
;; Restore the original error handler
(setq *error* old-error)
;; Display the error message (unless it's an Escape)
(if (/= message "Function cancelled")
(princ (strcat "\nError: " message))
)
(princ)
)
;; 4. Modify system variables
(setvar "CMDECHO" 0)
(setvar "OSMODE" 0)
;; 5. Main code
(command "_LINE" '(0 0) '(100 0) "")
(command "_CIRCLE" '(50 50) 25)
;; 6. Restore (normal case, no error)
(setvar "CMDECHO" old-echo)
(setvar "OSMODE" old-osmode)
(setq *error* old-error)
(princ)
)
Now, whether the program finishes normally or is interrupted, the system variables will always be restored.
Factoring Out the Restoration Code
You may notice that the restoration code is duplicated: once in *error* and once at the end of the program. To avoid this duplication, you can extract the restoration into a separate function:
(defun c:my-drawing (/ old-error old-echo old-osmode)
;; Cleanup function
(defun restore ()
(setvar "CMDECHO" old-echo)
(setvar "OSMODE" old-osmode)
(setq *error* old-error)
)
;; Error handler
(setq old-error *error*)
(defun *error* (message)
(restore)
(if (/= message "Function cancelled")
(princ (strcat "\nError: " message))
)
(princ)
)
;; Save and modify
(setq old-echo (getvar "CMDECHO"))
(setq old-osmode (getvar "OSMODE"))
(setvar "CMDECHO" 0)
(setvar "OSMODE" 0)
;; Main code
(command "_LINE" '(0 0) '(100 0) "")
(command "_CIRCLE" '(50 50) 25)
;; Restore (normal case)
(restore)
(princ)
)
Handling Open Files
Error handling is also essential when working with files. If the program crashes while a file is open, it will remain locked:
(defun c:export-data (/ old-error file)
(setq old-error *error*)
(defun *error* (message)
;; Close the file if it is open
(if file
(progn
(close file)
(setq file nil)
)
)
(setq *error* old-error)
(if (/= message "Function cancelled")
(princ (strcat "\nError: " message))
)
(princ)
)
;; Open and write
(setq file (open "C:/temp/export.txt" "w"))
(write-line "Exported data" file)
;; ... code that might fail ...
;; Close properly
(close file)
(setq file nil)
(setq *error* old-error)
(princ)
)
Cancelling Commands on Error
If your program uses command and an error occurs in the middle of an active command, it is wise to cancel the active command in the error handler:
(defun *error* (message)
;; Cancel any active command
(while (> (getvar "CMDACTIVE") 0)
(command "")
)
;; Restore variables...
(restore)
(princ)
)
The CMDACTIVE system variable indicates whether a command is currently running. The loop sends empty strings (simulating Enter) until all commands have finished.
The Undo Block
To allow the user to undo all of your program's operations with a single Ctrl+Z, wrap your code in an undo block:
(defun c:my-drawing (/ old-error old-echo old-osmode)
(defun restore ()
(setvar "CMDECHO" old-echo)
(setvar "OSMODE" old-osmode)
(setq *error* old-error)
)
(setq old-error *error*)
(defun *error* (message)
(while (> (getvar "CMDACTIVE") 0)
(command "")
)
;; Undo everything that was done
(command "_.UNDO" "_End")
(command "_.UNDO" "")
(restore)
(if (/= message "Function cancelled")
(princ (strcat "\nError: " message))
)
(princ)
)
(setq old-echo (getvar "CMDECHO"))
(setq old-osmode (getvar "OSMODE"))
(setvar "CMDECHO" 0)
(setvar "OSMODE" 0)
;; Begin the undo block
(command "_.UNDO" "_Begin")
;; Main code
(command "_LINE" '(0 0) '(100 0) "")
(command "_CIRCLE" '(50 50) 25)
;; End the undo block
(command "_.UNDO" "_End")
(restore)
(princ)
)
With this pattern, if the program succeeds, the user can undo everything with a single Ctrl+Z. If the program fails, the error handler automatically undoes all partial modifications.
Summary
| Concept | Description |
|---|---|
*error* |
Function called automatically when an error occurs |
Save *error* |
(setq old-error *error*) |
Redefine *error* |
(defun *error* (message) ...) |
Restore *error* |
(setq *error* old-error) |
| Cancel an active command | (while (> (getvar "CMDACTIVE") 0) (command "")) |
| Undo block | (command "_.UNDO" "_Begin") ... (command "_.UNDO" "_End") |
| Pattern | When to use |
|---|---|
| Restore system variables | Always when modifying CMDECHO, OSMODE, etc. |
| Close files | Always when opening a file with open |
| Cancel active commands | When using command |
| Undo block | When the program creates or modifies multiple entities |
In the next chapter, we will cover advanced concepts: symbols and lambda functions.
Need an AutoCAD (AutoLISP, ObjectARX, .NET, VBA) development? Contact me for a free quote.