Ein paar Worte vorabHome   Letzte MeldungenNews   Index der Kapitel und der besprochenen FunktionenIndex   Wer ich bin, warum ich diese Seiten mache, KontaktImpressum   Ich freue mich über jeden Eintrag im Gästebuch!Gästebuch   Einige Links zu anderen AutoLisp-SeitenLinks   Copyrights und DisclaimerRechts
Hier können die kompletten Seiten als ZIP-File heruntergeladen werden!

Funktionen für komfortables Arbeiten mit Zeichenketten String-Tango
Noch mehr Funktionen für komfortableres Arbeiten mit Zeichenketten Kettenhunde
strtok zerlegt Zeichenketten anhand eines Trennzeichens Tock-Tock
Arbeiten mit Datum und Zeit in AutoLisp Zeitlos...
Dotted pairs - wie man den Programmabbruch verhindert Gepunktet?
Neue Funktionen für die Listenbearbeitung Strukturtapete
Weitere neue Funktionen für die Listenbearbeitung Listen to me!
Lambda expressions - dasSalz in der Suppe Lambada
Lambda expressions anhand eines Praxisbeispiels Unter der Erde
Where und whereever erleichtern den Umgang mit Listen Quo vadis?
Rekursion - Funktionen, die sich selbst aufrufen Katzenschwanz
Ein äusserst wichtiger Prototyp für Funktionen Nix passiert
Wo das lineare mapcar am Ende ist Tiefer rein!
Über Effekte und Neben(Seiten-)Effekte von Funktionen Seitensprünge
Die Namensräume (Sichtbarkeit) von Variablen Raumwunder
Let dient zur Schaffung kleinerer Namensräume Lass mal...
Sukzessive Verarbeitung von Listenresten mit mapcdr Der Bruder
Was in AutoLisp einfach nicht machbar ist (Teil I) Beschränkt
Was in AutoLisp einfach nicht machbar ist (Teil II) Limited Edition
Nicht mit Effekten arbeiten, sondern direkter Daten-Änderung Destruktiv
Sequenzielles vs. paralleles Abarbeiten von Argumenten Parallelwelten
Über den Umgang mit Funktionsschablonen Erwachet!
Ein Praxiskapitel über Auswahlsätze, Attribute, wcmatch und mehr Durch die Brust
Die Farben des AutoCAD Color Index und ihre RGB-Werte Alles so schön
Hier laufen die Fäden zusammen: Viele Konzepte vereint Lapsus Lispuli
Ein Spiel als Beispiel für lernfähige Funktionen Zug um Zug
Errorhandling in AutoLisp - Teil 1 Alles valsch!
Errorhandling in AutoLisp - Teil 2 Und foll Feler!


Zum Einsteiger-Tutorial

Zu den ActiveX-Seiten

Meine Private HP mit Fotos, Gedichten, Musik und Postkartenversand

Mein Online-Lexikon der Fotografie

Mein völlig abgedrehtes Reisebüro










Errorhandling in AutoLisp ist ein vieldiskutiertes Thema, und kaum ein User hat ein klares Konzept, wie die Sache sinnvoll anzugehen ist. Ich möchte nicht wissen, wieviele unnütze Arbeitsstunden weltweit in den letzten Jahren damit verbracht wurden, immer und immer wieder ein Errorhandling in kleine Programme einzubauen. Hier liegt das Dilemma: 99% der AutoLisp-Programmierer erfinden das Rad täglich neu und schreiben sich für jede noch so kleine Applikation jeweils einen neuen Handler.

Was soll ein Errorhandler in AutoLisp leisten? Er hat vor allem zwei Dinge zu erledigen: Zum Einen sollen alle AutoCAD-Systemvariablen, die innerhalb des Programms modifiziert wurden, wieder auf die Ausgangswerte zurücksetzen. Die andere Aufgabe ist das Behandeln der Undo-Funktionalität: Mit einem einzigen Aufruf des 'Z'-Befehls muss alles, was das Programm in der Zeichnung verändert hat, wieder rückgängig zu machen sein. Man stelle sich ein Programm vor, das z.B. Tausende von Kreisen zeichnet, aber kurz vor Ende durch den Benutzer abgebrochen wird oder wegen eines Fehlers aus dem digitalen Leben scheidet: Ist kein Undo-Handling eingebaut, muss der Benutzer jetzt Tausende Male den Z-Befehl aufrufen!

Wir müssen uns darüber im Klaren sein, dass AutoLisp keine großartigen Möglichkeiten zum Error- und Undo-Handling bietet - wir müssen mit wenig auskommen. Dreh- und Angelpunkt des Ganzen ist die Funktion (*error*). Dieser Name ist fest verdrahtet, also unveränderbar, und diese Funktion wird immer dann aufgerufen, wenn der Interpreter sich an einem Fehler verschluckt oder das Programm durch den Benutzer abgebrochen wird.

Die Funkton ist AutoLisp-intern vordefiniert und tut nichts weiter als das Ausgeben einer Fehermeldung - jedenfalls in den neuen AutoLisp-Versionen. In den älteren Versionen war sie auch für das 'Ausschütten' des Codes in immer höheren Verschachtelungsebenen verantwortlich, was zwar einerseits optisch etwas verwirrend, andererseits aber durchaus von praktischem Nutzen war. Jedenfalls haben wir die Möglichkeit, diese Funktion zu überschreiben und etwas mehr daraus zu machen.

Zwei Überlegungen, um uns das Leben leichter zu machen: Statt für jede Systemvariable Codezeilen zu schreiben, sollten wir uns darauf besinnen, was die Stärke von Lisp ist und hier Funktionen zur Listenbearbeitung einsetzen. Und dann sollte kein Code doppelt vorhanden sein: Wir ziehen uns aus der Affäre, indem wir das Zurücksetzen ausschliesslich in der (*error*)-Funktion durchführen. Damit auch bei einem normalen (fehler- und abbruchslosen) Verlauf alles sauber zurückgesetzt wird, rufen wir einfach am Ende des Programms die (*error*)-Funktion selbst auf. Damit diese weiss, dass gar kein Fehler vorliegt, geben wir aber als msg-Argument einfach nil mit.

Für die gesicherten Systemvariablen legen wir nun keine einzelnen Lispvariablen mehr an, sondern nur eine einzige Liste. Den notwendigen Code zur Fehlerbehandlung lagern wir in zwei wiederverwendbare Bibliotheksfunktionen aus:
(defun startErrorHandler(varsToSave / )
  (command "_undo" "_begin")
  (foreach pair varsToSave
    (setq *saveList*
      (cons
        (cons(car pair)(getvar(car pair)))
        saveList
      )
    )
    (setvar(car pair)(cdr pair))
  )
  (setq *oldError* *error*)
  (setq *error* myErrorFunction)
)

(defun endErrorHandling( / )
  (*error* nil)
)

(defun myErrorFunction(msg / )
  (if msg(print msg))
  (command"_undo""_end")
  (foreach pair *savedList*
    (setvar(car pair)(cadr pair))
  )
  (setq *savedVars* nil)
  (setq *error* *oldError*)
  (setq *oldError* nil)
)

; so wird's verwendet:
(defun C:irgendwas( / )
  (startErrorHandling
   '( ("osmode" . 0)
      ("cmddia" . 0)
      ("filedia" . 0)
    )
  )
  ...
  ...
  ...
  (endErrorHandling)
)
                  
Es ist nicht zu übersehen, dass der Code in der Programmfunktion selbst nun absolut minimal und übersichtlich geworden ist - das ist schon mal ein voller Erfolg! Die Start- und Ende-Funktionen sowie den Code von (myErrorFunction) können wir abspeichern und von nun an immer wieder verwenden. Allerdings haben wir die Erleichterungen für den Preis zweier globaler Variablen erreicht: *savedVars* und *oldError* schwirren im Speicher herum - das muss beim gegenwärtigen Stand der Dinge so sein, damit die Funktion (*error*) auf die darin enthaltenen Daten zugreifen kann.

Eine Überlegung noch zum Ende dieses Kapitels: Warum muss eigentlich die bisherige Fehlerfunktion abgesichert und wiederhergestellt werden? Nun ja, das muss eigentlich gar nicht sein. Tatsächlich reicht es aus, dem Symbol *error* die Bindung wegzunehmen, also ein (setq *error* nil) aufzurufen, damit wieder die in AutoCAD eingebaute (*error*)-Funktion verwendet wird. Allerdings kann es auch vorkommen, dass Lisp-Programme aus anderen Lisp-Programmen heraus aufgerufen werden. In diesem Fall müsste also die bisher vorhandene Error-Funktion auch noch ausgeführt werden - theoretisch wäre sogar ein Stapel von Error-Funktionen denkbar. Dies verträgt sich allerdings nicht mit den beiden globalen Variablen, die wir jetzt haben.

Zum Austesten schreiben wir uns eine kleine Funktion, die zwar ziemlich unsinnig ist, aber zum Testen ausreicht. Sie zeichnet zwei Linien und fordert den Benutzer dann auf, eine Zahl einzugeben. Wenn wir an dieser Stelle das Programm mit der ESC-Taste abbrechen, tritt unser Errorhandler auf den Plan. Er setzt die beiden Systemvariablen zurück, erzeugt das Ende der Undo-Gruppe und gibt die Fehlermeldung 'Funktion abgebrochen' aus. Wenn wir nach dem Abbruch den AutoCAD-Befel 'Z' aufrufen, verschwinden beide(!) Linien vom Bildschirm (wäre keine Undo-Gruppierung da, müsste man dazu zweimal 'Z' aufrufen).
(defun c:eh-test1( / )
  (startErrorHandler nil
   '(("osmode" . 0)("blipmode" . 1))
  )
  (command "_line" '(0 0)'(100 100)"")
  (command "_line" '(100 0)'(0 100)"")
  (getint "\nZahl eingeben: ")
  (endErrorHandler)
)
                  
Das Ganze testen wir aber nun noch einmal mit einer zweiten Testfunktion, die zwar ähnlich ist, aber noch ein weiteres Problem aufwirft:
(defun c:eh-test2( / p1)
  (startErrorHandler nil
   '(("osmode" . 0)("blipmode" . 1))
  )
  (command "_line"
    (setq p1(getpoint "\nErsten Punkt wählen: "))
    (getpoint p1 "\nZweiten Punkt wählen: ")
  )
  (endErrorHandler)
)
                  
Wenn wir hier während einer der beiden (getpoint)-Anfragen mit ESC abbrechen, wird zwar Lisp terminiert und der Error-Handler aufgerufen, allerdings erzeugt dieser sofort wieder einen weiteren Fehler, da der (command)-Aufruf nicht beendet wurde und noch läuft. Daher fügen wir eine weitere kleine Ergänzung in unseren Errorhandler ein, der zunächst einmal prüft, ob noch irgendwelche AutoCAD-Befehle offen sind und dieses gegebenenfalls abbricht. Damit man die Bestandteile nicht umständlich suchen muss, hier noch einmal der komplette Code inkl. der kleinen Änderung:
(defun startErrorHandler(varsToSave / )
  (command "_undo" "_begin")
  (foreach pair varsToSave
    (setq *saveList*
      (cons
        (cons(car pair)(getvar(car pair)))
        saveList
      )
    )
    (setvar(car pair)(cdr pair))
  )
  (setq *oldError* *error*)
  (setq *error* myErrorFunction)
)

(defun endErrorHandling( / )
  (*error* nil)
)

(defun myErrorFunction(msg / )
  ; hinzugefügt:
  (while(>(getvar "cmdactive")0)(command))
  (if msg(print msg))
  (command"_undo""_end")
  (foreach pair *savedList*
    (setvar(car pair)(cadr pair))
  )
  (setq *savedVars* nil)
  (setq *error* *oldError*)
  (setq *oldError* nil)
)
                  
Im nächsten Kapitel werden wir einen Weg kennenlernen, bei dem wir auf globale Variablen völlig verzichten, noch etwas Komfort einbauen und die Dinge sogar vollständig stapelbar machen. Sollte also Programm A das Programm B aufrufen und dieses wiederum Programm C, dann werden die (*error*)-Funktionen aller drei Programme nacheinander ausgeführt (natürlich in der Reihenfolge C-B-A), damit alle Rücksetzungen der Programme einwandfrei funktionieren.