AutoLISP : le guide complet — Chapitre 12 / 16

La gestion des erreurs

Quand un programme AutoLISP rencontre une erreur — division par zéro, fichier introuvable, ou simplement l'utilisateur qui appuie sur Échap — l'exécution s'interrompt brutalement. Si votre programme avait modifié des variables système comme CMDECHO ou OSMODE, elles restent dans leur état modifié, ce qui peut perturber le travail de l'utilisateur. La gestion des erreurs permet d'éviter ce problème.

Le problème : une interruption laisse un état incohérent

Reprenons l'exemple du chapitre sur l'interaction avec le dessin :

(defun c:mon-dessin (/ ancien-echo ancien-osmode)
  ;; Sauvegarder et modifier les variables
  (setq ancien-echo (getvar "CMDECHO"))
  (setq ancien-osmode (getvar "OSMODE"))
  (setvar "CMDECHO" 0)
  (setvar "OSMODE" 0)

  ;; Dessiner...
  (command "_LINE" '(0 0) '(100 0) "")
  (command "_CIRCLE" '(50 50) 25)

  ;; Restaurer les variables
  (setvar "CMDECHO" ancien-echo)
  (setvar "OSMODE" ancien-osmode)
  (princ)
)

Si l'utilisateur appuie sur Échap pendant l'exécution, ou si une erreur survient dans une des commandes, le programme s'arrête avant d'atteindre les lignes de restauration. Résultat : CMDECHO reste à 0 et OSMODE reste à 0.

La fonction *error*

AutoLISP appelle automatiquement la fonction *error* chaque fois qu'une erreur se produit. Par défaut, cette fonction affiche simplement le message d'erreur. Mais vous pouvez la redéfinir pour exécuter votre propre code de nettoyage.

Principe de base

(defun *error* (message)
  ;; Code de nettoyage ici
  (princ (strcat "\nErreur : " message))
  (princ)
)

La fonction *error* reçoit un seul argument : le message d'erreur sous forme de chaîne de caractères. Ce message peut être :

  • "Function cancelled" — l'utilisateur a appuyé sur Échap
  • "divide by zero" — division par zéro
  • "bad argument type" — type d'argument incorrect
  • "too few arguments" — pas assez d'arguments
  • etc.

Le pattern standard

La bonne pratique est de :

  1. Sauvegarder la fonction *error* d'origine au début de votre programme
  2. Redéfinir *error* avec votre code de nettoyage
  3. Restaurer la fonction *error* d'origine à la fin (en cas de succès) et dans votre gestionnaire d'erreur (en cas d'échec)
(defun c:mon-dessin (/ ancien-error ancien-echo ancien-osmode)
  ;; 1. Sauvegarder le gestionnaire d'erreur d'origine
  (setq ancien-error *error*)

  ;; 2. Sauvegarder les variables système
  (setq ancien-echo (getvar "CMDECHO"))
  (setq ancien-osmode (getvar "OSMODE"))

  ;; 3. Définir notre gestionnaire d'erreur personnalisé
  (defun *error* (message)
    ;; Restaurer les variables système
    (setvar "CMDECHO" ancien-echo)
    (setvar "OSMODE" ancien-osmode)

    ;; Restaurer le gestionnaire d'erreur d'origine
    (setq *error* ancien-error)

    ;; Afficher le message d'erreur (sauf si c'est un Échap)
    (if (/= message "Function cancelled")
      (princ (strcat "\nErreur : " message))
    )
    (princ)
  )

  ;; 4. Modifier les variables système
  (setvar "CMDECHO" 0)
  (setvar "OSMODE" 0)

  ;; 5. Le code principal
  (command "_LINE" '(0 0) '(100 0) "")
  (command "_CIRCLE" '(50 50) 25)

  ;; 6. Restaurer (cas normal, sans erreur)
  (setvar "CMDECHO" ancien-echo)
  (setvar "OSMODE" ancien-osmode)
  (setq *error* ancien-error)
  (princ)
)

Désormais, que le programme se termine normalement ou qu'il soit interrompu, les variables système seront toujours restaurées.

Factoriser le code de restauration

Vous remarquez que le code de restauration est dupliqué : une fois dans *error* et une fois à la fin du programme. Pour éviter cette duplication, vous pouvez extraire la restauration dans une fonction séparée :

(defun c:mon-dessin (/ ancien-error ancien-echo ancien-osmode)
  ;; Fonction de nettoyage
  (defun restaurer ()
    (setvar "CMDECHO" ancien-echo)
    (setvar "OSMODE" ancien-osmode)
    (setq *error* ancien-error)
  )

  ;; Gestionnaire d'erreur
  (setq ancien-error *error*)
  (defun *error* (message)
    (restaurer)
    (if (/= message "Function cancelled")
      (princ (strcat "\nErreur : " message))
    )
    (princ)
  )

  ;; Sauvegarder et modifier
  (setq ancien-echo (getvar "CMDECHO"))
  (setq ancien-osmode (getvar "OSMODE"))
  (setvar "CMDECHO" 0)
  (setvar "OSMODE" 0)

  ;; Code principal
  (command "_LINE" '(0 0) '(100 0) "")
  (command "_CIRCLE" '(50 50) 25)

  ;; Restaurer (cas normal)
  (restaurer)
  (princ)
)

Gérer les fichiers ouverts

La gestion d'erreur est aussi indispensable quand on travaille avec des fichiers. Si le programme plante alors qu'un fichier est ouvert, il restera verrouillé :

(defun c:exporter (/ ancien-error fichier)
  (setq ancien-error *error*)

  (defun *error* (message)
    ;; Fermer le fichier s'il est ouvert
    (if fichier
      (progn
        (close fichier)
        (setq fichier nil)
      )
    )
    (setq *error* ancien-error)
    (if (/= message "Function cancelled")
      (princ (strcat "\nErreur : " message))
    )
    (princ)
  )

  ;; Ouvrir et écrire
  (setq fichier (open "C:/temp/export.txt" "w"))
  (write-line "Données exportées" fichier)

  ;; ... code qui pourrait échouer ...

  ;; Fermer proprement
  (close fichier)
  (setq fichier nil)
  (setq *error* ancien-error)
  (princ)
)

Annuler les commandes en cas d'erreur

Si votre programme utilise command et qu'une erreur survient au milieu d'une commande en cours, il est prudent d'annuler la commande active dans le gestionnaire d'erreur :

(defun *error* (message)
  ;; Annuler toute commande en cours
  (while (> (getvar "CMDACTIVE") 0)
    (command "")
  )

  ;; Restaurer les variables...
  (restaurer)
  (princ)
)

La variable système CMDACTIVE indique si une commande est en cours d'exécution. La boucle envoie des chaînes vides (simule Entrée) jusqu'à ce que toutes les commandes soient terminées.

Le bloc d'annulation

Pour que l'utilisateur puisse annuler toutes les opérations de votre programme en un seul Ctrl+Z, encadrez votre code avec un bloc d'annulation :

(defun c:mon-dessin (/ ancien-error ancien-echo ancien-osmode)
  (defun restaurer ()
    (setvar "CMDECHO" ancien-echo)
    (setvar "OSMODE" ancien-osmode)
    (setq *error* ancien-error)
  )

  (setq ancien-error *error*)
  (defun *error* (message)
    (while (> (getvar "CMDACTIVE") 0)
      (command "")
    )
    ;; Annuler tout ce qui a été fait
    (command "_.UNDO" "_End")
    (command "_.UNDO" "")
    (restaurer)
    (if (/= message "Function cancelled")
      (princ (strcat "\nErreur : " message))
    )
    (princ)
  )

  (setq ancien-echo (getvar "CMDECHO"))
  (setq ancien-osmode (getvar "OSMODE"))
  (setvar "CMDECHO" 0)
  (setvar "OSMODE" 0)

  ;; Début du bloc d'annulation
  (command "_.UNDO" "_Begin")

  ;; Code principal
  (command "_LINE" '(0 0) '(100 0) "")
  (command "_CIRCLE" '(50 50) 25)

  ;; Fin du bloc d'annulation
  (command "_.UNDO" "_End")

  (restaurer)
  (princ)
)

Avec ce pattern, si le programme réussit, l'utilisateur peut tout annuler en un seul Ctrl+Z. Si le programme échoue, le gestionnaire d'erreur annule automatiquement toutes les modifications partielles.

Résumé

Concept Description
*error* Fonction appelée automatiquement en cas d'erreur
Sauvegarder *error* (setq ancien-error *error*)
Redéfinir *error* (defun *error* (message) ...)
Restaurer *error* (setq *error* ancien-error)
Annuler une commande active (while (> (getvar "CMDACTIVE") 0) (command ""))
Bloc d'annulation (command "_.UNDO" "_Begin") ... (command "_.UNDO" "_End")
Pattern Quand l'utiliser
Restaurer les variables système Toujours quand on modifie CMDECHO, OSMODE, etc.
Fermer les fichiers Toujours quand on ouvre un fichier avec open
Annuler les commandes actives Quand on utilise command
Bloc d'annulation Quand le programme crée ou modifie plusieurs entités

Dans le prochain chapitre, nous aborderons des concepts avancés : les symboles et les fonctions lambda.


Coup de pouce Besoin d'un développement AutoCAD (AutoLISP, ObjectARX, .NET, VBA) ? Contactez-moi pour un devis gratuit.