![]() |
出错处理函数的浅析
出错处理函数的浅析
www.dimcax.com 出错处理函数的浅析 出错处理函数的浅析 ■■■■ 1 浅析定义出错处理函数的常犯的错误 ■■ 1.1 概念及其编程应用的意义 我们知道 *error* 函数是用来执行程序出错时进行相关错误处理 的特殊函数操作。就调用方式而言,大家都很熟悉。所以我们要讨论的 不是技术问题,而是它对我们编程应用的意义。 一个完整的程序应该具备一个周全的出错处理。因此,出错处理函 数我们就会频繁的使用。怎样让我们自定义的出错处理能够应对各种各 样的出错情况呢? 通常我们的出错处理函数会分为三部分来定义: a. 定义初始化设置,如保存 *error* 的当前值,将自定义的出错函数 赋名为 *error*,放置 undo 的开始标记,保存系统变量信息并设置新 值等等。★这部分通常被放在程序的开始。 b. 定义自己的 *error*,在出错时自动激活该函数。执行必要的 undo 恢复,系统变量恢复,初始化设置中的全局变量恢复以及其他的图形操 作等。 c. 定义正常退出的恢复设置。★这部分通常被放在程序的结束。 基本的格式如下: ;;------------------------------------------------------------ ;;例1 这是大多数人的写法 (defun xxx-begin (varlst / cmd) (setq cmd (getvar "cmdecho")) (setvar "cmdecho" 0) (command "_.undo" "_begin");放置undo开始标记 (setvar "cmdecho" cmd) (setq $savvarlst (mapcar '(lambda (x) (list x (getvar x)) ) varlst ) ;系统变量保存及设置 $olderr *error* ;保存 *error* 的当前值 *error* xxx-error ;自定义的出错函数赋名为 *error* ) ;... ) (defun xxx-error (msg) (princ msg) ;打印出错信息 (xxx-end) ;调用自定义函数 xxx-end ;... ;其他出错处理 ) (defun xxx-end (/ cmd) (if $savvarlst (mapcar '(lambda (x) (apply 'setvar x) ) $savvarlst ) ;系统变量恢复到初始设置 ) (setq *error* $olderr ;*error* 恢复到初始设置 $savvarlst nil ;全局变量设置为 nil $olderr nil ) (setq cmd (setvar "cmdecho")) (setvar "cmdecho" 0) (command "_undo" "_end") ;放置 undo 的结束标志 (setvar "cmdecho" cmd) (princ) ) ;;------------------------------------------------------------ 看上去似乎可以了,但在一些特定的情况下还会出错,它仍然不具备通 用性。 我们在下面具体分析。 ■■ 1.2 通用性及出错可能性分析 我们在出错处理函数要完成的基本任务,通常包括以下几个要素, 大家可以检验一下自己写的出错函数能否胜任以下要求。 a. 系统变量设置 ◇当程序被用户取消或出现错误而中止能否恢复。 ◇中途设置的(非在初始时设置的)的系统变量,当程序被用户取消 或出现错误而中止能否恢复。 用上面(例1)的函数举例如下: ;例2--------------------------------------------------- (defun c:test (/ os) (xxx-begin '("cmdecho" "skpoly" "cecolor")) (setvar "cmdecho" 0) (setvar "skpoly" 1) (setvar "cecolor" "1") ;... (setq os (getvar "osmode")) (setvar "osmode" 3333) ;... ;;出错点,在此按 esc ;... (setvar "osmode" os) (xxx-end) ) ;------------------------------------------------------- 在运行以上程序时用户按下 "esc" ,相信 "cmdecho" "skpoly" "cecolor" 能够恢复原值,但 "osmode" 就不行了。 -------------------------------------------------------- 问:为什么不在初试时的函数中设置? 答:假如在复杂程序中可能会套嵌调用初始化函数,然 *error* 设置只能有一个。 (defun c:test (/ ttt) (xxx-begin '("cmdecho" "skpoly" "cecolor")) ... (defun ttt () (xxx-begin '("osmode")) ... ) ... ) 问:为什么不在 xxx-error 中补充定义? 答:补充定义的 xxx-error 只能适用当前的程序。我们要求的是 能够在任意的程序中调用,这就是我讲的“通用性”。 --------------------------------------------------------- 其实我们只要把例2中的 (setq os (getvar "osmode")) (setvar "osmode" 3333) ... ***出错点 ... (setvar "osmode" os) 改成: (setq $savvarlst (cons (list "osmode" (getvar "osmode")) $savvarlst ) ) (setvar "osmode" 3333) ;... ;;出错点,在此按 esc ;... 就行了,这我们在后面讨论。 b. undo 设置 谈论 undo 设置之前我们可能有必要来了解一下系统变量 undoctl 的相关设置。下面是 autocad 的帮助: --------------------------------------------------------------- (只读) 类型 整数 保存位置 尚未保存 初始值 21 指示 undo 命令的“自动”、“控制”和“编组”选项的状态。 系统将 使用下列位码值之和将该设置存储为一个位码: 0 undo 关闭 1 undo 打开 2 只能放弃一条命令 4 打开“自动” 8 一个编组处于当前活动状态 16 将缩放和平移操作编组为单个操作 --------------------------------------------------------------- 看完上面的帮助,我们来用(例1)中的函数来作测试: ;例3----------------------------------------------------------- ;我们先作如下操作: (command "_.undo" "_control" "_none") ;然后我们来测试 (xxx-begin '()) ;结果返回命令行操作错误信息:"无效的选项关键字。" ;-------------------------------------------------------------- 为什么呢?其实错误在于 xxx-begin 函数中的 (command "_.undo" "_begin") 因为我们执行了 (command "_.undo" "_control" "_none") 操作之后 undo 命令的关键字发生了变化,如下: “输入 undo 控制选项 [全部(a)/无(n)/一个(o)/合并(c)] <全部>:” 所以我们需要根据系统变量 undoctl 的值,而对症下药。然而由于该变量 为只读属性,setvar 对它无效。所以我们必须通过命令行操作来完成相关 设置。 c. 程序出错后的操作 同样以上面的例1的函数为例: ;例4------------------------------------------------------------- (defun c:test (/ p1 p2 e p3) (xxx-begin '("osmode")) (initget 1) (setq p1 (getpoint "\n指定点1: ")) (initget 1) (setq p2 (getpoint p1 "\n指定点2: ")) (command "_.line" p1 p2 "") (setq e (entlast)) (redraw e 3) (initget 1) (setq p3 (getpoint p2 "\n指定点3 (或在此按 esc): ")) ;;出错点 (command "_.line" p2 p3 "") (redraw e 4) (xxx-end) ) ;--------------------------------------------------------------- 正常运行例4的程序第一个对象被高亮显示后被恢复,而中途出错的则无法 恢复。 d. 先选后执行的功能 我们知道当 (= 1 (logand 1 (getvar "pickfirst"))) 返回 t 的情 况下我们可以先选择对象,而后执行命令操作。如命令 move、copy 等。 但是例1的函数在此则无法实现。为什么呢? 原来我们先选后,然后执行 xxx-begin 中的 (command "_.undo" "_begin") 后先选的对象被取消了。 activex 的方法不失为一种好方法: ;--------------------------------------------------------------- ;初始时 (setq $acaddoc (vla-get-activedocument (vlax-get-acad-object))) (vla-startundomark $acaddoc) ;结束时 (if $acaddoc (progn (vla-endundomark $acaddoc) (setq $acaddoc nil)) ) ;--------------------------------------------------------------- 上面的方法优劣参半: 优点--是既能够实现先选后执行的功能,又避免了系统变量 cmdecho 的设置。 缺点--它在理论上等价于 (command "_undo" "_begin") 和 (command "_undo" "_end") 也存在我们在例 3 中的问题。 于是我们只能在调用 xxx-begin 开始加上以下代码: (if (= 1 (logand 1 (getvar "pickfirst"))) (setq ss (ssgetfirst)) ) 而在调用 xxx-begin 结束加上以下代码: (if (apply 'or ss) (apply 'sssetfirst ss) ) 确实是高手,很难懂,还得好好学习 |
| 所有的时间均为北京时间。 现在的时间是 05:40 AM. |