Seit der Einführung von VisualLisp und dem damit verbundenen
neuen Lisp-Interpreter gibt es eine ganz neue Möglichkeit in
Lisp, die dem Programmierer bis dahin verschlossen war: Auftretende
Fehler können aufgefangen werden. Bisher musste man damit leben,
dass das Auftreten eines Fehlers das laufende Programm auf jeden
Fall beendete - es ging dann nur noch um's Aufräumen und das
Zurückversetzen von AutoCAD in den Zustand, in dem es zu Beginn
des Programms war.
Die neue Funktion, die das Abfangen eines Fehlers ermöglicht,
heisst
(vl-catch-all-apply). Sie bekommt immer genau
zwei Argumente - das erste Argument ist eine Funktion oder
ein Lambda-Ausdruck, und das zweite Argument ist eine Liste mit
den Argumenten, die dieser Funktion zugeführt werden sollen.
Die Funktion arbeitet also genau wie das altbekannte
(apply),
ein Unterschied besteht jedoch in der Rückgabe: Wenn die
Evaluation des Funktionsausdrucks einen Fehler verursacht,
wird nicht abgebrochen, sondern es wird ein Fehler-Objekt
zurückgegeben.
Ein solches Fehler-Objekt stellt einen neuen, gesonderten Datentyp
in Lisp dar. Dieses Fehler-Objekt kann dann aber (bei weiterlaufendem
Programm) verarbeitet bzw. verglichen werden. Zunächst aber
erstmal ein bisschen Code, um die Dinge zu verdeutlichen:
(defun test1( / )
(vl-catch-all-apply 'vla-item
(list
(vla-get-Layers
(vla-get-ActiveDocument
(vlax-get-acad-object)
)
)
"NichtVorhanden"
)
)
)
(test1)
=> #<%catch-all-apply-error%>
(type(test1))
=> VL-CATCH-ALL-APPLY-ERROR
Wenn wir den Code nun noch etwas modifizieren (wir speichern
das Ergebnis des
Item-Aufrufs ab und vergleichen den
erhaltenen Datentyp), haben wir schon eine Methode, wie wir
absturzfrei auf die Layers-Collection zugreifen können:
(defun test2(layer-name / result)
(if
(/=
(type
(setq result
(vl-catch-all-apply 'vla-item
(list
(vla-get-Layers
(vla-get-ActiveDocument
(vlax-get-acad-object)
)
)
layer-name
)
)
)
)
'VL-CATCH-ALL-APPLY-ERROR
)
result
)
)
(test2 "0")
=>
(test2 "NichtVorhanden")
=> nil
Das klappt doch wunderbar! Allerdings modifizieren wir noch
ein wenig weiter, denn VisualLisp bietet zum Umgang mit
abgefangenen Fehlern noch zwei weitere neue Funktionen:
(vl-catch-all-error-p) ist eine Prädikatfunktion, mit
der wir ein wenig Schreiberei sparen können: Sie macht genau
das, was wir mit unserem
(type)-Vergleich erledigt
haben - sie testet, ob ihr Argument ein
#<%catch-all-apply-error%> ist.
(defun test3(layer-name / result)
(if
(not
(vl-catch-all-error-p
(setq result
(vl-catch-all-apply 'vla-item
(list
(vla-get-Layers
(vla-get-ActiveDocument
(vlax-get-acad-object)
)
)
layer-name
)
)
)
)
)
result
)
)
(test2 "0")
=>
(test2 "NichtVorhanden")
=> nil
Eine kleine kosmetische Änderung, sonst nichts - der Code
verhält sich genauso wie vorher. Die zweite Funktion zum
Behandeln von abgefangenen Fehlern heisst
(vl-catch-all-error-message). Sie ist nur dann von
Interesse, wenn man die Ursache des aufgefangenen Fehlers
nachträglich ermitteln möchte. Der Fehlertext, der bei einem
nicht aufgefangenen Fehler auf der Kommandozeile ausgegeben
würde, wird hier als Zeichenkette zurückgegeben. In unserem
Fall können wir allerdings auf diese Analyse verzichten, da
wir genau wissen, dass der Fehler durch die
Item-Methode
verursacht wird.
Wir können nun unsere im vorigen Kapitel definierte Funktion
(collection-member-p) noch einmal auf andere Weise
definieren:
(defun collection-member-p(name collection / )
(not
(vl-catch-all-error-p
(vl-catch-all-apply 'vla-item
(list collection name)
)
)
)
)
(collection-member-p "0"
(vla-get-Layers
(vla-get-ActiveDocument
(vlax-get-acad-object)
)
)
)
=> T
(collection-member-p "NichtVorhanden"
(vla-get-Layers
(vla-get-ActiveDocument
(vlax-get-acad-object)
)
)
)
=> nil
Die beiden Versionen sind im Verhalten völlig gleich - bei
grossen Collections dürfte allerdings diese Version etwas
schneller sein. Für die praktische Arbeit sollte man aber
vielleicht doch eine Funktion vorziehen, die entweder (falls
enthalten) das Collection-Item zurückgibt, oder (falls nicht
vorhanden)
nil. Diese Funktion könnte so aussehen:
(defun collection-item(name collection / result)
(if
(not
(vl-catch-all-error-p
(setq result
(vl-catch-all-apply 'vla-item
(list collection name)
)
)
)
)
result
)
)
(collection-item "0"
(vla-get-Layers
(vla-get-ActiveDocument
(vlax-get-acad-object)
)
)
)
=> #<VLA-OBJECT IAcadLayer 0fc04474>
(collection-item "NichtVorhanden"
(vla-get-Layers
(vla-get-ActiveDocument
(vlax-get-acad-object)
)
)
)
=> nil