AutoLISP程序设计错误处理的技巧

AutoLISP程序设计错误处理的技巧

——作者:Gu_xl

任何程序在运行中都可能不完全会按照程序设计者预想的方式去运行,程序运行中会出现各种意想不到的情况,AutoLisp提供的标准错误处理函数*error*,仅能告诉你程序运行有了错误,但不能对错误进行善后处理。这对于一个优秀的程序来说,是不能忍受的,为此,我们应当用自定义的错误处理函数来改善这种情况,使程序在出现任何错误情况下都能正确处理!

一、在程序内设置局部自定义错误处理函数

AutoLisp提供内部提供了一个标准的自定义错误处理函数:

(*error* string)

*error*函数值不为空,AutoLisp程序运行出错时,AutoCAD会传递给*error*函数的参数string一个错误信息字符串值,并自动执行*error*函数。

根据这个错误处理运行机制,我们可以根据自己的需要,对错误处理函数*error*进行自定义。

例1:自定义如下错误处理函数:

(defun *error* (msg) 
  (princ "error:")
  (princ msg) ;_打印错误信息
)

当Lisp程序运行出错时,系统会自动调用*error*函数,打印出错误处理信息。但是这个错误处理函数几乎没有什么用,该错误处理函数仅仅是对错误信息进行了输出,没有对可能出现的错误进行任何处理,这可不是我们想要的程序,我们需要对*error*函数内容根据程序处理的需要进行定制!

例2:下面是一个绘制三角形的例子,程序目的是在图上选取三点,自动绘制三角形。

(defun c:tt1 (/ p1 p2 osmode cmdecho) 
  (setq cmdecho (getvar "cmdecho")) ;_保存系统变量cmdecho值
  (setvar "cmdecho" 0) ;_关闭命令行的回显提示
  ;保存系统变量osmode值
  (setq osmode (getvar "osmode"))
  (setvar "osmode" 0) ;_关闭捕捉模式
  (setq p1 (getpoint "\n输入第一点:")
        p2 (getpoint "\n输入第二点:")
        p3 (getpoint "\n输入第三点:")
  )
  (vl-cmdf "_.pline" p1 p2 p3 "c")
  (setvar "cmdecho" cmdecho) ;_恢复cmdecho系统变量
  (setvar "osmode" osmode) ;_恢复osmode系统变量
  (princ)
)

在程序正常运行的情况下,程序会在绘制完三角形后自动恢复系统变量"osmode""cmdecho",但是在图面选取点时,按下ESC键,或某个点输入为空,会导致程序出错,后面的恢复系统变量值的代码则不会执行,程序关闭了捕捉模式,我们不得不手动来重新设置捕捉方式。

为此,我们在程序的开始自定义一个错误处理函数*error*,在*error*函数内添加恢复系统变量"osmode""cmdecho"的代码,在程序运行出错时自动调用*error*函数,*error*函数内恢复系统变量"osmode""cmdecho"的代码自动运行,这便达到了我们对程序错误处理的要求!示例代码如下(c:tt2):

(defun c:tt2 (/ p1 p2 osmode cmdecho *error*) 
  (defun *error* (msg) 
    (setvar "cmdecho" cmdecho) ;_恢复cmdecho系统变量
    (setvar "osmode" osmode) ;_恢复osmode系统变量
    (princ "error:")
    (princ msg) ;_打印错误信息
    (princ)
  )
  ;以下为主程序内容
  (setq cmdecho (getvar "cmdecho")) ;_保存系统变量cmdecho值
  (setvar "cmdecho" 0) ;_关闭命令行的回显提示
  ;保存系统变量osmode值
  (setq osmode (getvar "osmode"))
  (setvar "osmode" 0) ;_关闭捕捉模式
  (setq p1 (getpoint "\n输入第一点:")
        p2 (getpoint "\n输入第二点:")
        p3 (getpoint "\n输入第三点:")
  )
  (vl-cmdf "_.pline" p1 p2 p3 "c")
  (setvar "cmdecho" cmdecho) ;_恢复cmdecho系统变量
  (setvar "osmode" osmode) ;_恢复osmode系统变量
  (princ)
)

二、通用错误处理函数的处理技巧

请注意,在上面例2的程序中,我们是将*error*函数声明为局部变量,则该错误处理仅在test函数范围内有效!这样我们就需要在每个程序里都要添加类似的错误处理代码,其实大部分程序的错误处理代码都类似,如果在每个程序里都添加相同的代码,是一件很讨厌的事儿,会导致代码越来越长,为此,我们可以做一个通用错误处理函数,以后仅需要调用该函数即可,无需再写出长长的一串代码!

1、例3:自定义通用错误处理初始化函数

;;(gxl-error-init syslst) 初始化*error*
;;功能:保存syslst给定的系统变量值,并按表给定的系统变量值设置系统变量
;;参数 syslst 系统变量及其设置值列表 例如: '("osmode" 0 "cmdecho" 0  "cecolor" “1”)
(defun gxl-error-init (syslst / sysname sysvar) 
  (setq *olderror* *error*) ;储存*error*函数
  ;;自定义出错函数
  (defun *error* (msg / sysname sysvar) 
    (if *sysvarInit*  ;_储存的系统变量值列表
      (while 
        (and (setq sysname (car *sysvarInit*)) 
             (setq sysvar (cadr *sysvarInit*))
        )
        (setq *sysvarInit* (cddr *sysvarInit*))
        (setvar sysname sysvar)
      )
    )
    (setq *error* *olderror*)
    (or (wcmatch (strcase msg) "*BREAK*, *CANCEL*,*EXIT*") 
        (princ (strcat "\n** Error:" msg "**"))
    )
  )
  ;;如果有活动编组,先结束之
  (if (= 8 (logand (getvar "undoctl") 8)) 
    (command "_undo" "_e")
  )
  (command "_undo" "_be")
  (if syslst 
    (while 
      (and (setq sysname (car syslst)) 
           (setq sysvar (cadr syslst))
      )
      (setq *sysvarInit* (append *sysvarInit* 
                                 (list sysname (getvar sysname))
                         )
      ) ;_储存系统变量
      (setq syslst (cddr syslst))
      (setvar sysname sysvar) ;_设置系统变量
    )
  )
  (princ)
)

2、例4:自定义通用错误处理恢复函数

;;(gxl-error-end) 恢复*error*
(defun gxl-error-end () 
  (if (= 8 (logand (getvar "undoctl") 8)) 
    (command "_undo" "_e")
  )
  (if *olderror* 
    (setq *error*    *olderror*
          *olderror* nil
    ) ;恢复*error*函数
  )
  (if *sysvarInit*  ;_恢复储存的系统变量
    (while 
      (and (setq sysname (car *sysvarInit*)) 
           (setq sysvar (cadr *sysvarInit*))
      )
      (setq *sysvarInit* (cddr *sysvarInit*))
      (setvar sysname sysvar)
    )
  )
  (setq *Function* nil
        *UndoMode* nil
  )
  (princ)

实际运用示例:

(defun c:tt3 () 
  (gxl-error-init '("osmode" 0 "cmdecho" 0 "cecolor" "1"))
  ;;;主程序内容
  .
  .
  .
  (gxl-error-end) ;_恢复*error*
  (princ)
)

3、gxl-error-init syslst错误处理函数

当程序运行出错时只可以对保存的系统变量进行恢复,但这在复杂的程序面前,并不能满足我们的需要,因为各种各样的程序在运行出错后可能还要进行其他各种各样不同的处理,为此,我们要对gxl-error-init函数进行进一步改进,使得gxl-error-init函数可以应对各种可能出现的各种问题。

例5、改进后的自定义通用错误处理初始化函数

;;;( gxl-error-init1syslst fun UndoMode)
;;;功能: 多功能错误处理函数
;;;参数
;;; syslst = 系统变量表
;;; fun = 要执行的函数 ,无动作则为nil
;;; UndoMode = Undo处理模式 0 = 不编组 1 = 仅仅编组 2 = 回到出错前
(defun gxl-error-init1 (syslst fun UndoMode / sysname sysvar) 
  (setq *olderror* *error*
        *Function* fun
        *UndoMode* UndoMode
  )
  (defun *error* (msg / sysname sysvar) 
    (if (= 2 *UndoMode*) 
      (progn 
        (if (= 8 (logand (getvar "undoctl") 8)) 
          (command "_undo" "_e")
        )
        (command "_U")
      )
      (progn 
        (if *sysvarInit* 
          (while 
            (and (setq sysname (car *sysvarInit*)) 
                 (setq sysvar (cadr *sysvarInit*))
            )
            (setq *sysvarInit* (cddr *sysvarInit*))
            (setvar sysname sysvar)
          )
        )
        (if *Function* (VL-CATCH-ALL-APPLY*Function*)) ;_执行函数
        (if (= *UndoMode* 1) 
          (if (= 8 (logand (getvar "undoctl") 8)) 
            (command "_undo" "_e")
          )
        )
      )
    )
    (setq *error*    *olderror*
          *olderror* nil
          *Function* nil
          *UndoMode* nil
    )
    (or (wcmatch (strcase msg) "*BREAK,*CANCEL*,*EXIT*") 
        (princ (strcat "\n** Error: " msg " **"))
    )
  )

  (if (or (= *UndoMode* 1) (= *UndoMode* 2)) 
    (progn 
      ;;如果有活动编组,先结束编组
      (if (= 8 (logand (getvar "undoctl") 8)) 
        (command "_undo" "_e")
      )
      (command "_undo" "_BE") ;_ 编组开始
    )
  )
  (if syslst 
    (while 
      (and (setqsysname (car syslst)) 
           (setq sysvar (cadr syslst))
      )
      (setq *sysvarInit* (append *sysvarInit* 
                                 (list sysname (getvar sysname))
                         )
      )
      (setq syslst (cddr syslst))
      (setvar sysname sysvar)
    )
  )
  (princ)
)

错误处理应用范例:

(defun c:tt4 (/ tt1 p1 p2 p3 end) 
  ;;初始化*error* 参数UndoMode根据需要可设置为 0 1 2

  (gxl-error-init1 (list 'blipmode 0 'cmdecho 0 'osmode 0) 'tt1 2) ;_ 出错只编组
  (defun tt1 ()  ;_ 出错后*error*要执行的动作
    (alert "出错啦!")
  )

  (setq p1 (getpoint "\n第一点: "))
  (setq p2 (getpoint "\n第二点: "))
  (setq p3 (getpoint "\n第三点: "))
  (vl-cmdf "line" p1 p2 p3 "c")

  ;;将UndoMode分别设置为 0 1 2,运行到以下任意位置按下ESC试试效果
  (setq p1 (getpoint "\n第一点: "))
  (setq p2 (getpoint "\n第二点: "))
  (setq p3 (getpoint "\n第三点: "))
  (vl-cmdf "line" p1 p2 p3 "c")

  (setq p1 (getpoint "\n第一点: "))
  (setq p2 (getpoint "\n第二点: "))
  (setq p3 (getpoint "\n第三点: "))
  (vl-cmdf "line" p1 p2 p3 "c")

  (setq p1 (getpoint "\n第一点: "))
  (setq p2 (getpoint "\n第二点: "))
  (setq p3 (getpoint "\n第三点: "))
  (vl-cmdf "line" p1 p2 p3 "c")

  (GXL-ERROR-END)
  (princ)
)

可能有些读者觉得对于这样的出错处理函数中,对于系统变量表参数,要写一长串变量,也很麻烦,应为在程序中,我们经常要设置和使用的一些系统变量都数量有限,为此,我们提供另外一种错误处理的方法和思路,在初始化错误函数中保存一些常用的系统变量初始值,储存在全局变量中,然后初始化一些常用系统变量的值,程序中可以对这些已经保存的系统变量任意修改,在程序结束时用错误恢复函数自动恢复系统变量值,程序出错时由错误处理函数来恢复系统变量值。

我们将一些有关经常使用的系统变量名储存表,设为常量*SysVarNL*

;;;*SysVarNL* 常用系统变量表
(setq *SysVarNL* (list 'AUNITS 'AUPREC 'ATTDIA 'ATTREQ 'BLIPMODE 'DIMZIN 'CECOLOR 
                       'CELTYPE 'CLAYER 'CMDECHO 'TRIMMODE 'EXPERT 'HIGHLIGHT 'LUNITS 
                       'LUPREC 'EDGEMODE 'OSMODE 'ORTHOMODE 'TEXTSTYLE 'PLINEWID 'PLINEGEN 
                       'FILEDIA 'PICKBOX 'QAFLAGS 'UCSAXISANG 'CELTSCALE 'NOMUTT 
                       'PEDITACCEPT 'Mirrtext 'limcheck
                 )
)

例6、自定义通用错误处理函数gxl-error-init2:

;;;自定义错误处理函数 gxl-error-init2
;;;参数
;;; fun = 要执行的函数,无动作则为nil
;;; UndoMode = Undo处理模式 0 = 不编组 1 = 仅仅编组 2 = 回到出错前
(defun gxl-error-init2 (fun UndoMode / sv 0lay os) 
  ;;定义出错函数
  (defun *error* (msg / sysname sysvar) 
    (if (=2 *UndoMode*) 
      (progn 
        (if (= 8 (logand (getvar "undoctl") 8)) 
          (command "_undo" "_e")
        )
        (command "_U")
      )

      (progn 
        (if *Function* (VL-CATCH-ALL-APPLY*Function*)) ;_ 执行函数
        (if (= *UndoMode* 1) 
          (if (= 8 (logand (getvar "undoctl") 8)) 
            (command "_undo" "_e")
          )
        )
      )
    )

    (if (and *SVARL* *SysVarNL*) 
      (MAPCAR '(lambda (a b) (VL-CATCH-ALL-APPLY 'setvar (list a b))) 
              *SYSVARNL*
              *SVARL*
      )
    )

    ;;回收变量值
    (setq *error*    *olderror*
          *olderror* nil
          *Function* nil
          *UndoMode* nil
          *SVARL*    nil
    )

    (or (wcmatch (strcase msg) "*BREAK,*CANCEL*,*EXIT*") 
        (princ (strcat "\n** Error: " msg " **"))
    )
  )

  ;;程序开始
  (SETQ *SVARL* nil) ;_ *SVARL*用于储存系统变量表*SysVarNL*中系统变量对应的值
  (setq *SVARL* (mapcar '(lambda (x) (GETVARx)) *SYSVARNL*))

  ;;对一些程序中必须要初始化为0的系统变量初始化为0

  (FOREACH SV 
    '("ATTDIA" "ATTREQ" "BLIPMODE" "CMDECHO" "DIMZIN" "OSMODE" "ORTHOMODE" "MIRRTEXT")
    (SETVAR SV 0)
  )

  (setq *olderror* *error*
        *Function* fun
        *UndoMode* UndoMode
  )

  ;;是否开始编组
  (if (or (= *UndoMode* 1) (= *UndoMode* 2)) 
    (progn 
      ;;如果有活动编组,先结束编组
      (if (= 8 (logand (getvar "undoctl") 8)) 
        (command "_undo" "_e")
      )
      (command "_undo" "_BE")
    )
  )

;|EXPERT = 5禁止显示提示“块已经存在。重新定义?”,
;使用DIMSTYLE 命令的“保存”选项时,如果输入的标注样式名已经存在,
;系统将显示该提示。\n\n如果 EXPERT 禁止显示一个提示,相应操作会认为用户已输入 y 确认提示。
;EXPERT 的设置会影响脚本、菜单宏、AutoLISP 及其命令函数。|;
  (SETVAR "EXPERT" 5)
  (SETVAR "CECOLOR" "BYLAYER") ;_ 设置颜色随层
  (SETVAR "celtype" "BYLAYER") ;_ 设置线型随层
  (SETVAR "LWDISPLAY" 1) ;_ 设置显示线宽
  (SETVAR "CELTSCALE" 1) ;_ 设置当前线形比例为1
  (SETVAR "PLINEGEN" 1) ;_ 设置围绕多段线顶点生成连续线型
)

例7:gxl-error-init2对应的自定义通用错误处理恢复函数

(defun gxl-error-end2 () 
  (if (or (= *UndoMode* 1) (= *UndoMode* 2)) 
    (if (= 8 (logand (getvar "undoctl") 8)) 
      (command "_undo" "_e")
    )
  )

  (if *olderror* 
    (setq *error*    *olderror*
          *olderror* nil
    ) ;_ 恢复*error*函数
  )

  (if (and *SVARL* *SysVarNL*) 
    (MAPCAR '(lambda (a b) (VL-CATCH-ALL-APPLY 'setvar (list a b))) 
            *SYSVARNL*
            *SVARL*
    )
  )

  (setq *Function* nil
        *UndoMode* nil
        *SVARL*    nil
  )
  (princ)
)

错误处理应用示例:

(defun c:tt5 (/ tt1 p1 p2 p3 end) 
  ;;;初始化*error* 参数UndoMode根据需要可设置为 0 1 2
  (gxl-error-init2 'tt1 2) ;_ 出错只编组,这里省去了系统变量设置

  (defun tt1 ()  ;_ 出错后*error*要执行的动作
    (alert "出错啦!")
  )

  (setq p1 (getpoint "\n第一点: "))
  (setq p2 (getpoint "\n第二点: "))
  (setq p3 (getpoint "\n第三点: "))
  (vl-cmdf "line" p1 p2 p3 "c")

  ;;;将 UndoMode分别设置为 0 1 2,运行到以下任意位置按下ESC试试效果
  (setq p1 (getpoint "\n第一点: "))
  (setq p2 (getpoint "\n第二点: "))
  (setq p3 (getpoint "\n第三点: "))
  (vl-cmdf "line" p1 p2 p3 "c")

  (setq p1 (getpoint "\n第一点: "))
  (setq p2 (getpoint "\n第二点: "))
  (setq p3 (getpoint "\n第三点: "))
  (vl-cmdf "line" p1 p2 p3 "c")

  (setq p1 (getpoint "\n第一点: "))
  (setq p2 (getpoint "\n第二点: "))
  (setq p3 (getpoint "\n第三点: "))
  (vl-cmdf "line" p1 p2 p3 "c")

  (GXL-ERROR-END2)
  (princ)
)

三、Vl-Catch-All-Apply函数捕捉错误的应用技巧

上面部分讨论的是传统的*error*方法,这种方法是对整个程序设置的统一的处理功能,无论程序中什么地方出错,都集中在这里处理!这种情况在程序比较简单,要处理的情况不多时,比较容易应付,因为这时程序需要处理的情况比较少!当程序的规模比较大,要处理的情况比较复杂时,就很难满足我们对程序错误处理的要求,特别是当出现同样的错误代码需要在程序的不同部分做出不同处理的时候。幸运的是,AutoLisp另外还提供了三个错误处理函数。Vl-Catch-All-Apply函数能够捕捉指定函数的错误,Vl-Catch-All-Apply-Error-Message函数可以读取相关错误信息,Vl-Catch-All-Error-P函数可以测试Vl-Catch-All-Apply函数返回结果是否异常。

例如:下面这个函数是根据图上选取直角三角形的一条直角边,然后输入斜边长度,根据勾股定理计算另一直角边,并绘制三角形的例子:

(defun c:tt6 () 
  ;; 初始化错误处理,出错后回到初始状态
  (gxl-error-init1 (list "cmdecho" 0 "osmode" 0) nil 2)
  (setq p1 (getpoint "\n输入直角三角形直角边第一点: "))
  (setq p2 (getpoint p1 "\n输入直角三角形直角边第二点: "))
  (setq d1 (distance p1 p2))
  (vl-cmdf "_line" p1 p2 "")
  (setq dd (getdist p1 "\n请输入斜边长度: "))

  ;;;根据勾股定理计算另一条直角边长度
  ;_ 捕捉sqrt函数的错误
  (setq d2 (VL-CATCH-ALL-APPLY 
             'sqrt
             (list 
               (- (* dd dd) (* d1 d1))
             )
           )
  )

  (if (VL-CATCH-ALL-ERROR-P d2)  ;_ 当输入的斜边长度小于直角边长度时sqrt函数会出错
    (prong  ;_ sqrt 函数结果异常
      (alert (VL-CATCH-ALL-ERROR-MESSAGE d2)) ;_ 提示错误信息
      (abcdefg) ;_ 制造一个错误退出程序
    )

    ;;;绘制三角形
    (progn 
      (setq ang (+ (angle p1 p2) (* 0.5 pi)))
      (command "_line" p2 (polar p2 angd2) p1 "")
    )
  )

  (gxl-error-end) ;_ 恢复错误处理
  (princ)
)

这样,自定义函数test就可以返回正确的结果或出错信息这两种结果,这是一种新的函数定义模式,其使用方法简而言之就是使用Vl-Catch-All-Apply对函数进行包装,然后使用Vl-Catch-All-Error-P判断包装函数返回值是否异常,若有异常,可使用Vl-Catch-All-Apply-Error-Message函数输出错误信息,并再此对错误进行处理。

posted @ 2022-06-14 16:39  Slow-Step  阅读(1439)  评论(0编辑  收藏  举报