【Gu_xl】Lisp程序设计错误处理的技巧
本帖最后由 Gu_xl 于 2013-6-11 10:38 编辑这篇文章原本是为 “明经VLISP开发宝典”准备的,但由于种种原因,现在出书没有进展,若一直尘封在电脑里,觉得甚为可惜,所以先拿出来和大家一块分享,内容有不妥之处,请大家拍砖!
Lisp程序设计错误处理的技巧——作者: Gu_xl (源自我的博客:http://guxl.mjtd.com/?p=14)任何程序在运行中都可能不完全会按照程序设计者预想的方式去运行,程序运行中会出现各种意想不到的情况,AutoLisp提供的标准错误处理函数*error*,仅能告诉你程序运行有了错误,但不能对错误进行善后处理。这对于一个优秀的程序来说,是不能忍受的,为此,我们应当用自定义的错误处理函数来改善这种情况,使程序在出现任何错误情况下都能正确处理!一、 在程序内设置局部自定义错误处理函数AutoLisp提供内部提供了一个标准的自定义错误处理函数:1. (*error* string) 当*error*函数值不为空,AutoLisp程序运行出错时,AutoCAD会传递给*error*函数的参数string一个错误信息字符串值,并自动执行*error*函数。根据这个错误处理运行机制,我们可以根据自己的需要,对错误处理函数*error*进行自定义。例1:自定义如下错误处理函数:1. (defun *error*(msg)2. (princ "error:")3. (princ msg) ;_ 打印错误信息4. (princ) 5. )当Lisp程序运行出错时,系统会自动调用*error*函数,打印出错误处理信息。但是这个错误处理函数几乎没有什么用,该错误处理函数仅仅是对错误信息进行了输出,没有对可能出现的错误进行任何处理,这可不是我们想要的程序,我们需要对*error*函数内容根据程序处理的需要进行定制!例2:下面是一个绘制三角形的例子,程序目的是在图上选取三点,自动绘制三角形,。1. (defun c:tt1 (/ p1p2 osmode cmdecho)2. (setq cmdecho(getvar “cmdecho”)) ;_ 保存系统变量cmdecho值3. (setvar “cmdecho”0) ;_ 关闭命令行的回显提示4. ;保存系统变量osmode值5. (setq osmode(getvar “osmode”))6. (setvar “osmode” 0);_ 关闭捕捉模式7. (setq p1 (getpoint “\n输入第一点: “)8. P2 (getpoint “\n输入第二点: “)9. P3 (getpoint “\n输入第三点: “)10. )11. (vl-cmdf “_.pline”p1 p2 p3 “c”)12. (setvar “cmdecho”cmdecho) ;_ 恢复cmdecho系统变量13. (setvar “osmode”osmode) ;_ 恢复osmode系统变量14. (princ)15. )在程序正常运行的情况下,程序会在绘制完三角形后自动恢复系统变量”osmode”、“ cmdecho”,但是在图面选取点时,按下ESC键,或某个点输入为空,会导致程序出错,后面的恢复系统变量值的代码则不会执行,程序关闭了捕捉模式,我们不得不手动来重新设置捕捉方式。为此,我们在程序的开始自定义一个错误处理函数*error*,在*error*函数内添加恢复系统变量”osmode”、“ cmdecho”的代码,在程序运行出错时自动调用*error*函数,*error*函数内恢复系统变量”osmode”、“ cmdecho”的代码自动运行,这便达到了我们对程序错误处理的要求!示例代码如下(c:tt2):1. (defun c:tt2 (/ p1p2 osmode cmdecho *error*)2. (defun *error* (msg)3. (setvar "cmdecho" cmdecho) ;_ 恢复cmdecho系统变量4. (setvar "osmode" osmode) ;_ 恢复osmode系统变量5. (princ "error: ")6. (princ msg) ;_ 打印错误信息7. (princ) 8. )9. ;;;以下为主程序内容10. (setq cmdecho (getvar "cmdecho"));_ 保存系统变量cmdecho值11. (setvar "cmdecho" 0) ;_ 关闭命令行的回显提示12. ;保存系统变量osmode值13. (setq osmode (getvar "osmode"))14. (setvar "osmode" 0) ;_ 关闭捕捉模式15. (setq p1 (getpoint "\n输入第一点: ")16. P2 (getpoint "\n输入第二点: ")17. P3 (getpoint "\n输入第三点: ")18. )19. (vl-cmdf "_.pline" p1 p2 p3"c")20. (setvar "cmdecho" cmdecho) ;_ 恢复cmdecho系统变量21. (setvar "osmode" osmode) ;_ 恢复osmode系统变量22. (princ)23. )二、 通用错误处理函数的处理技巧请注意,在上面例2的程序中,我们是将*error*函数声明为局部变量,则该错误处理仅在test函数范围内有效!这样我们就需要在每个程序里都要添加类似的错误处理代码,其实大部分程序的错误处理代码都类似,如果在每个程序里都添加相同的代码,是一件很讨厌的事儿,会导致代码越来越长,为此,我们可以做一个通用错误处理函数,以后仅需要调用该函数即可,无需再写出长长的一串代码!1、例3:自定义通用错误处理初始化函数1. ;;(gxl-error-init syslst) 初始化*error*2. file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image001.gif;;;功能:保存syslst给定的系统变量值,并按表给定的系统变量值设置系统变量3. file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image001.gif;;; 参数 syslst 系统变量及其设置值列表 例如: '("osmode" 0 "cmdecho" 0"cecolor" “1”)4. (defun gxl-error-init (syslst / sysnamesysvar)5. (setq *olderror* *error*) ;_ 储存*error*函数6. ;;自定义出错函数 *error*7. (defun *error* (msg / sysname sysvar)8. (if *sysvarInit*;_ 储存的系统变量值列表9. (while (and (setq sysname (car*sysvarInit*))10. (setq sysvar (cadr *sysvarInit*))11. )12. (setq *sysvarInit* (cddr *sysvarInit*))13. (setvar sysname sysvar)14. )15. )16. (setq *error* *olderror*)17. (or (wcmatch(strcase msg) "*BREAK,*CANCEL*,*EXIT*")18. (princ (strcat "\n** Error: " msg" **"))19. )20. )21. ;;如果有活动编组,先结束之22. (if (= 8 (logand (getvar "undoctl")8))23. (command "_undo" "_e")24. )25. (command "_undo" "_be")26. (if syslst27. (while (and (setqsysname (car syslst))28. (setq sysvar (cadr syslst))29. )30. (setq *sysvarInit*31. (append *sysvarInit*32. (list sysname (getvar sysname))33. )34. ) ;_ 储存系统变量35. (setq syslst (cddr syslst))36. (setvar sysname sysvar) ;_ 设置系统变量37. )38. )39. (princ)40. )2、例4:自定义通用错误处理恢复函数1. file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image001.gif;;(gxl-error-end) 恢复*error*2. file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image001.gif (defun gxl-error-end()3. (if(= 8 (logand (getvar "undoctl") 8))4. (command "_undo" "_e")5. )6. (if*olderror*7. (setq *error* *olderror*8. *olderror* nil9. ) ;_ 恢复*error*函数10. )11. (if *sysvarInit* ;_ 恢复储存的系统变量12. (while (and (setq sysname (car*sysvarInit*))13. (setq sysvar (cadr *sysvarInit*))14. )15. (setq *sysvarInit* (cddr *sysvarInit*))16. (setvar sysname sysvar)17. )18. )19. (setq *Function* nil20. *UndoMode* nil21. )22. (princ)23. )实际运用示例:1. (defun c:tt3()2. (gxl-error-init '("osmode" 0 "cmdecho" 0"cecolor" "1"))3. ;;;主程序内容4. .5. .6. .7. (gxl-error-end) ;_恢复*error*8. (princ)9. )3、gxl-error-init syslst错误处理函数,当程序运行出错时只可以对保存的系统变量进行恢复,但这在复杂的程序面前,并不能满足我们的需要,因为各种各样的程序在运行出错后可能还要进行其他各种各样不同的处理,为此,我们要对gxl-error-init函数进行进一步改进,使得gxl-error-init函数可以应对各种可能出现的各种问题。例5、改进后的自定义通用错误处理初始化函数1. ;;;( gxl-error-init1syslst fun UndoMode)2. ;;;功能: 多功能错误处理函数3. ;;;参数4. ;;; syslst = 系统变量表 5. ;;; fun = 要执行的函数 ,无动作则为nil6. ;;; UndoMode = Undo处理模式 0 = 不编组 1 = 仅仅编组 2 = 回到出错前7. (defungxl-error-init1 (syslst fun UndoMode / sysname sysvar)8. (setq *olderror**error*9. *Function* fun10. *UndoMode* UndoMode11. )12. (defun *error* (msg / sysname sysvar)13. (if (=2 *UndoMode*)14. (progn15. (if (= 8 (logand (getvar"undoctl") 8))16. (command "_undo" "_e")17. )18. (command "_U")19. )20. (progn21. (if *sysvarInit*22. (while (and (setq sysname (car *sysvarInit*))23. (setq sysvar (cadr *sysvarInit*))24. )25. (setq *sysvarInit* (cddr *sysvarInit*))26. (setvar sysname sysvar)27. )28. )29. (if *Function* (VL-CATCH-ALL-APPLY*Function*)) ;_执行函数30. (if (= *UndoMode* 1)31. (if(= 8 (logand (getvar "undoctl") 8))32. (command "_undo" "_e")33. )34. )35. )36. )37. (setq *error* *olderror*38. *olderror* nil39. *Function* nil40. *UndoMode* nil41. )42. (or (wcmatch(strcase msg) "*BREAK,*CANCEL*,*EXIT*")43. (princ (strcat "\n** Error: " msg" **"))44. )45. )46. 47. (if (or (= *UndoMode* 1) (= *UndoMode* 2))48. (progn49. ;;如果有活动编组,先结束编组50. (if (= 8 (logand (getvar"undoctl") 8))51. (command "_undo" "_e")52. )53. (command "_undo""_BE") ;_ 编组开始54. )55. )56. (if syslst57. (while (and (setqsysname (car syslst))58. (setq sysvar (cadr syslst))59. )60. (setq *sysvarInit*61. (append *sysvarInit*62. (list sysname (getvar sysname))63. )64. )65. (setq syslst (cddr syslst))66. (setvar sysname sysvar)67. )68. )69. (princ)70. )错误处理应用范例:1. (defun c:tt4(/tt1 p1 p2 p3 end)2. ;;初始化*error* 参数UndoMode根据需要可设置为 0 1 23. (gxl-error-init1 (list 'blipmode 0 'cmdecho 0'osmode 0) 'tt1 2) ;_ 出错只编组4. (defun tt1 () ;_ 出错后*error*要执行的动作5. (alert "出错啦!")6. )7. (setq p1 (getpoint "\n第一点: "))8. (setq p2 (getpoint "\n第二点: "))9. (setq p3 (getpoint "\n第三点: "))10. (vl-cmdf "line" p1 p2 p3"c")11. ;;将UndoMode分别设置为 0 1 2,运行到以下任意位置按下ESC试试效果12. (setq p1 (getpoint "\n第一点: "))13. (setq p2 (getpoint "\n第二点: "))14. (setq p3 (getpoint "\n第三点: "))15. (vl-cmdf "line" p1 p2 p3"c")16. (setq p1 (getpoint "\n第一点: "))17. (setq p2 (getpoint "\n第二点: "))18. (setq p3 (getpoint "\n第三点: "))19. (vl-cmdf "line" p1 p2 p3"c")20. (setq p1 (getpoint "\n第一点: "))21. (setq p2 (getpoint "\n第二点: "))22. (setq p3 (getpoint "\n第三点: "))23. (vl-cmdf "line" p1 p2 p3"c")24. (GXL-ERROR-END)25. (princ)26. )可能有些读者觉得对于这样的出错处理函数中,对于系统变量表参数,要写一长串变量,也很麻烦,应为在程序中,我们经常要设置和使用的一些系统变量都数量有限,为此,我们提供另外一种错误处理的方法和思路,在初始化错误函数中保存一些常用的系统变量初始值,储存在全局变量中,然后初始化一些常用系统变量的值,程序中可以对这些已经保存的系统变量任意修改,在程序结束时用错误恢复函数自动恢复系统变量值,程序出错时由错误处理函数来恢复系统变量值。我们将一些有关经常使用的系统变量名储存表,设为常量*SysVarNL*:1. ;;;*SysVarNL* 常用系统变量表2. (setq *SysVarNL*3. (list 'AUNITS 'AUPREC 'ATTDIA 'ATTREQ4. 'BLIPMODE 'DIMZIN 'CECOLOR 'CELTYPE5. 'CLAYER 'CMDECHO 'TRIMMODE 'EXPERT6. 'HIGHLIGHT'LUNITS 'LUPREC 'EDGEMODE7. 'OSMODE 'ORTHOMODE 'TEXTSTYLE 'PLINEWID 'PLINEGEN8. 'FILEDIA 'PICKBOX 'QAFLAGS 'UCSAXISANG9. 'CELTSCALE 'NOMUTT 'PEDITACCEPT 'Mirrtext'limcheck10. )11. ) 例6、自定义通用错误处理函数gxl-error-init2:1. ;;;自定义错误处理函数 gxl-error-init22. ;;;参数3. ;;; fun = 要执行的函数,无动作则为nil4. ;;; UndoMode = Undo处理模式 0 = 不编组 1 = 仅仅编组 2 = 回到出错前5. (DEFUNgxl-error-init2 (fun UndoMode / sv 0lay os)6. ;;定义出错函数7. (defun *error* (msg / sysname sysvar)8. (if (=2 *UndoMode*)9. (progn10. (if (= 8 (logand (getvar"undoctl") 8))11. (command "_undo" "_e")12. )13. (command "_U")14. )15. (progn16. (if *Function* (VL-CATCH-ALL-APPLY*Function*)) ;_ 执行函数17. (if (= *UndoMode* 1)18. (if(= 8 (logand (getvar "undoctl") 8))19. (command "_undo" "_e")20. )21. )22. )23. )24. (if (and *SVARL* *SysVarNL*)25. (MAPCAR '(lambda (a b) (VL-CATCH-ALL-APPLY'setvar (list a b)))26. *SYSVARNL*27. *SVARL*28. )29. )30. ;;回收变量值31. (setq *error* *olderror*32. *olderror* nil33. *Function* nil34. *UndoMode* nil35. *SVARL* nil36. )37. (or (wcmatch(strcase msg) "*BREAK,*CANCEL*,*EXIT*")38. (princ (strcat "\n** Error: " msg" **"))39. )40. )41. ;;程序开始42. (SETQ *SVARL* nil) ;_ *SVARL*用于储存系统变量表*SysVarNL*中系统变量对应的值43. (setq *SVARL* (mapcar '(lambda (x) (GETVARx)) *SYSVARNL*))44. ;;对一些程序中必须要初始化为0的系统变量初始化为045. (FOREACH SV '("ATTDIA" "ATTREQ" "BLIPMODE" "CMDECHO"46. "DIMZIN" "OSMODE" "ORTHOMODE""MIRRTEXT"47. )48. (SETVAR SV 0)49. )50. (setq *olderror* *error*51. *Function* fun52. *UndoMode* UndoMode53. )54. ;;是否开始编组55. (if (or (= *UndoMode* 1) (= *UndoMode* 2))56. (progn57. ;;如果有活动编组,先结束编组58. (if (= 8 (logand (getvar"undoctl") 8))59. (command "_undo" "_e")60. )61. (command "_undo""_BE")62. )63. )64. ;| EXPERT = 5禁止显示提示“块已经存在。重新定义?”,使用DIMSTYLE 命令的“保存”选项时,如果输入65. 的标注样式名已经存在,系统将显示该提示。\n\n如果 EXPERT 禁止显示一个提示,相应操作会认66. 为用户已输入 y 确认提示。EXPERT 的设置会影响脚本、菜单宏、AutoLISP 及其命令函数。67. |;68. (SETVAR "EXPERT" 5)69. (SETVAR "CECOLOR""BYLAYER") ;_ 设置颜色随层70. (SETVAR "celtype""BYLAYER") ;_ 设置线型随层71. (SETVAR "LWDISPLAY" 1) ;_ 设置显示线宽72. (SETVAR "CELTSCALE" 1) ;_ 设置当前线形比例为173. (SETVAR "PLINEGEN" 1) ;_ 设置围绕多段线顶点生成连续线型74. )例7:gxl-error-init2对应的自定义通用错误处理恢复函数1. (defungxl-error-end2 ()2. (if (or (= *UndoMode* 1) (= *UndoMode* 2))3. (if (= 8 (logand (getvar"undoctl") 8))4. (command "_undo""_e")5. )6. )7. (if *olderror*8. (setq *error* *olderror*9. *olderror* nil10. ) ;_ 恢复*error*函数11. )12. (if (and *SVARL* *SysVarNL*)13. (MAPCAR '(lambda (a b) (VL-CATCH-ALL-APPLY'setvar (list a b)))14. *SYSVARNL*15. *SVARL*16. )17. )18. (setq *Function*19. nil20. *UndoMode*21. nil22. *SVARL* nil23. )24. (princ)错误处理应用示例:1. (defun c:tt5(/tt1 p1 p2 p3 end)2. ;;;初始化*error* 参数UndoMode根据需要可设置为 0 1 23. (gxl-error-init2 'tt1 2) ;_ 出错只编组,这里省去了系统变量设置4. (defun tt1 () ;_ 出错后*error*要执行的动作5. (alert "出错啦!")6. )7. (setq p1 (getpoint "\n第一点: "))8. (setq p2 (getpoint "\n第二点: "))9. (setq p3 (getpoint "\n第三点: "))10. (vl-cmdf "line" p1 p2 p3"c")11. ;;;将 UndoMode分别设置为 0 1 2,运行到以下任意位置按下ESC试试效果12. (setq p1 (getpoint "\n第一点: "))13. (setq p2 (getpoint "\n第二点: "))14. (setq p3 (getpoint "\n第三点: "))15. (vl-cmdf "line" p1 p2 p3"c")16. (setq p1 (getpoint "\n第一点: "))17. (setq p2 (getpoint "\n第二点: "))18. (setq p3 (getpoint "\n第三点: "))19. (vl-cmdf "line" p1 p2 p3"c")20. (setq p1 (getpoint "\n第一点: "))21. (setq p2 (getpoint "\n第二点: "))22. (setq p3 (getpoint "\n第三点: "))23. (vl-cmdf "line" p1 p2 p3"c")24. (GXL-ERROR-END2)25. (princ)26. )三、 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函数返回结果是否异常。例如:下面这个函数是根据图上选取直角三角形的一条直角边,然后输入斜边长度,根据勾股定理计算另一直角边,并绘制三角形的例子:1. (defun c:tt6 ()2. ;; 初始化错误处理,出错后回到初始状态3. (gxl-error-init1 (list "cmdecho" 0"osmode" 0) nil 2) 4. (setq p1 (getpoint "\n输入直角三角形直角边第一点: "))5. (setq p2 (getpoint p1 "\n输入直角三角形直角边第二点: "))6. (setq d1 (distance p1 p2))7. (vl-cmdf "_line" p1 p2"")8. (setq dd (getdist p1 "\n请输入斜边长度: "))9. ;;;根据勾股定理计算另一条直角边长度10. (setq d211. (VL-CATCH-ALL-APPLY ;_ 捕捉sqrt函数的错误12. 'sqrt13. (list14. (- (* dd dd) (* d1 d1))15. )16. )17. )18. (if(VL-CATCH-ALL-ERROR-P d2) ;_ 当输入的斜边长度小于直角边长度时sqrt函数会出错19. (prong ;_ sqrt 函数结果异常20. (alert (VL-CATCH-ALL-ERROR-MESSAGE d2)) ;_ 提示错误信息21. (abcdefg) ;_ 制造一个错误退出程序22. )23. ;;;绘制三角形24. (progn25. (setq ang (+ (angle p1 p2) (* 0.5 pi)))26. (command "_line" p2 (polar p2 angd2) p1 "")27. )28. )29. (gxl-error-end) ;_ 恢复错误处理30. (princ)31. )这样,自定义函数test就可以返回正确的结果或出错信息这两种结果,这是一种新的函数定义模式,其使用方法简而言之就是使用Vl-Catch-All-Apply对函数进行包装,然后使用Vl-Catch-All-Error-P判断包装函数返回值是否异常,若有异常,可使用Vl-Catch-All-Apply-Error-Message函数输出错误信息,并再此对错误进行处理。
本帖最后由 xxyyzzlg 于 2024-8-22 16:10 编辑
感谢楼主的精心梳理和热心分享。在使用中发现用下面的函数能正确处理错误,恢复系统变量。
(Gxl-ErrStaSys '("cmdecho" 0 "orthomode" 0 "cecolor" "256" "celtype" "bylayer" "clayer""构造线层"))
但若是将某个系统变量对应的值,改用赋值后的变量名,则失败。比如:
(setq envName (getenv "Ager1")) ;确定该环境变量的值为 "构造线层"
(Gxl-ErrStaSys '("cmdecho" 0 "orthomode" 0 "cecolor" "256" "celtype" "bylayer" "clayer" envName ))
** Error: 参数值错误: AutoCAD 变量值: ENVNAME **
而在命令行里用(setvar "clayer" envname) 是成功的。
搞不清是我用的方法不对,还是这个函数不支持这种用法了。
例子5和6中,
(vl-cmdf "_U") ;返回动作(CAD2017无法在error使用command)
可以改为
(Vlax-Invoke-Method *DOC* 'Sendcommand"_U ") 好高深的出错处理技术,现在看的似懂非懂的,待后面慢慢研究,感谢G版分享,谢谢! 米西米西,学习学习,谢谢古版 好长的码,要好好学习了,正在弄出错返回,一直弄不明白. 想G版学习啊! 学习学习,谢谢G版 很好的帖子,仔细学习,慢慢琢磨。 学习下了 嘻嘻 向无私以及务实的gu版致敬! 来学习GU版的好东西
写的很好,谢谢G版!