Delphi异常处理总结
以前写delphi程序一直不注意异常处理,对其异常处理机制总是一知半解,昨天程序中的一个bug,让我对异常有了更深入的认识,必须要对可能产生异常的地方进行异常处理,否则可能给程序造成灾难,就像昨天,因为写的filecopy函数没有做异常捕获处理,导致复制文件出错时整个程序崩溃,用户只能通过杀进程的方式重启程序再进行其它操作(汗~)。后来对程序进行异常处理,遇到意外只是提示下用户,然后可以继续运行下去,表现很完美,才意识到异常处理的重要性,故要总结下Delphi异常处理相关的知识。
Delphi异常处理机制建立在保护块(Protected Blocks)的概念上。所谓保护块是用保留字try和end封装的一段代码。保护块的作用是当应用程序发生错误时自动创建一个相应的异常类(Exception)。程序可以捕获并处理这个异常类,以确保程序的正常结束以及资源的释放和数据不受破坏。如果程序不进行处理,则系统会自动提供一个消息框。每一段程序都有可能产生错误!这是软件业的一个不容置疑的现象和规律。事实上,传统的if…else…结构完全可以解决所有的错误,使用Exception机制也没能够回避在最原始的层次,通过遍历可能的情况来产生异常的做法,但异常提供了一种更加灵活和开放的方式,使得后来的编程者可以来根据实际的情况处理这种错误,而不是使用预先设定好的处理结果。
一、异常的来源
在Delphi的应用程序中,下列的情况都比较有可能产生异常。
(1)文件处理
(2)内存分配
(3)Windows资源
(4)运行时创建对象和窗体
(5)硬件和操作系统冲突
on <异常对象类型1> do <语句1> //捕获指定类型的异常对象,进行处理
on <异常对象类型n> do <语句n> //捕获指定类型的异常对象,进行处理
这种异常处理结构一般用于保护Windows的资源分配等方面,它确保了无论try体内的代码是否发生异常,都需要由系统进行最后的统一处理的一些Windows对象的正确处理。
和try…except…end不同,该结构的finally部分总被执行。在try-finally语句中,当try部分产生异常后,应用程序直接执行Finally部分的资源释放语句。
(4) raise:知道一些情况不合理,直接手工弹异常对话框。如:raise 异常类.Create('异常的缺省说明');
try...finally结构与try...except结构在用法上主要有以下区别:
(2) 在try...except结构中,当异常被处理后异常对象就被释放,除非重新触发异常。而在try...finally结构中,即使finally部分对异常作了处理,异常对象仍然存在。
(3) finally部分不能处理特定的异常,因为它没有try...except结构中的异常句柄,无法知道确切的异常类型。因此,在finally部分只能对异常作笼统的处理。
(4) 在try…finally结构中,如果在try部分调用标准命令Exit、Break或Continue,将导致程序的执行流程提前跳到finally部分。finally部分不允许调用上述三个命令。
运行库异常可以分为七类,它们都定义在SysUtils库单元中。
当设置编译指示{$I-}时,不产生I/0异常类而是把错误代码返回到预定义变量IOResult中。
堆异常是在动态内存分配中产生的,包括两个类EOutOfMemory和EInvalidPointer,如表3-1所示。
EOutOfMemory 没有足够的空间用于满足所要求的内存分配
EInvalidPointer 非法指针。一般是由于程序试图去释放一个已释放的指针而引起的
整数异常都是从一个EIntError类派生的,但程序运行中引发的总是它的子类:EDivByZero,ERangeError,EIntOverFlow,如表3-2所示。
ERangeError当一个整数表达式的值超过为一个特定整数类型分配的范围时引发。比如下面一段代码将引发一个ERangeError异常。
特定整数类型包括ShortInt、Byte以及与整数兼容的枚举类型、布尔类型等。例如:
THazard=(Safety,Marginal,Critical,Catastrophic);
由于枚举类型越界而引发一个ERangeError异常。数组下标越界也会引发一个ERangeError异常,如:
Values:array[1..10] of Integer;
EIntOverFlow异常类在Integer、Word、Longint三种整数类型越界时引发。如下面的代码将引发一个EIntOverFlow异常:
EIntOverFlow异常类只有在编译选择框Option|Project|Over_Flow_Check Option选中时才产生。当关闭溢出检查,则溢出后变量的值是丢弃溢出部分后的剩余值。
各种类型的浮点数(Real、Single、Double、Extended)越界引起同样的溢出异常。
类型匹配异常EInvalidCast当试图用As操作符把一个对象与另一类对象匹配失败后引发。
类型转换异常EConvertError当试图用转换函数把数据从一种形式转换为另一种形式时引发,特别是当把一个字符串转换为数值时引发。下面程序中的两条执行语句都将引发一个EConvertError异常。
要注意,并不是所有的类型转换函数都会引发EConvertError异常。比如函数Val当它无法完成字符串到数值的转换时只把错误代码返回。利用这一点我们实现了输入的类型和范围检查。
硬件异常发生的情况有两种:或者是处理器检测到一个它不能处理的错误,或者是程序产生一个中断试图中止程序的执行。硬件异常不能编译进动态链接库(DLLs)中,而只能在标准的应用中使用(表3-4)。
硬件异常都是EProcessor异常类的子类。但运行时并不会引发一个EProcessor异常。
EGPFault 一般保护错,通常由一个未初始化的指针或对象引起
EPageFault Windows内存管理器不能正确使用交换文件
EInvalidOpCode 处理器碰到一个未定义的指令,这通常意味着处理器试图去操作非法数据或未初始化的内存
所谓对象异常是指非组件的对象引发的异常。Delphi定义的对象异常包括流异常、打印异常、图形异常、字符串链表异常等。
流异常类包括EStreamError、EFCreateError、EFOpenError、EFilerError、EReadError、EWriteError、EClassNotFound。
EStreamError 利用LoadFromStream方法读一个流发生错误
EReadError ReadBuffer方法不能读取特定数目的字节
EWriteError WriteBuffer方法不能写特定数目的字节
EClassNotFound 窗口上的组件被从窗口的类型定义中删除
打印异常类EPrinter当打印发生错误时引发。它在printers库单元中定义。例如应用程序试图向一个不存在的打印机打印或由于某种原因打印工作无法送到打印机时,就会产生一个打印异常。
图形异常类定义在Graphic库单元中,包括EInvalidGraphic和EInvalidGraphicOperation两类。
EInvalidGraphic当应用程序试图从一个并不包含合法的位图、图标、元文件或用户自定义图形类型的文件中装入图形时引发。例如下面的代码:
Image1.Picture.LoadFromFile('Readme.txt');
由于Readme.txt并不包含一个合法的图形,因而将引发一个EInvalidGraphic异常。
EInvalidGraphicOperation当试图对一个图形进行非法操作时引发。例如试图改变一个图标的大小。
AnIcon.LoadFromFile('C:/WINDOWS/DIRECTRY.ICO');
字符串链表异常EStringListError、EListError在用户对字符串链表进行非法操作时引发。
由于许多组件(如TListBox,TMemo,TTabSet,…)都有一个TStrings类的重要属性,因而字符串链表异常在组件操作编程中非常有用。
EStringListError异常一般在字符串链表越界时产生。例如对如下初始化的列表框:
ListBox1.Items.Add('Firstitem');
ListBox1.Items.Add('Seconditem');
ListBox1.Items.Add('Thirditem');
(1)当字符串链表的Duplicates属性设置为dupError时,应用程序试图加入一个重复的字符串。
a. 应用程序试图对一个Parent属性为nil的组件进行一些需要Windows句柄的操作
例如,ScrollBar、Gauge等组件要求Max属性大于等于Min属性,因而下面的语句:
ScrollBar1.Max:=ScrollBar1.Min-1;
a. 在Register过程之外试图登录一个组件(常用于自定义组件开发中)
b. 应用程序在运行中改变了一个组件的名称并使该组件与另一个组件重名
c. 一个组件的名称改变为一个Object Pascal非法的标识符
当应用程序试图创建一个Windows句柄而Windows却没有多余的句柄分配时引发该异常。
许多组件都定义了相应的组件异常类。但并不是有关组件的任何错误都会引发相应的异常类。许多情况下它们将引发一个运行时异常或对象异常。
非法的菜单操作,例如试图删除一个不存在的菜单项。这一异常类在Menus库单元中定义。
非法的网格操作,比如试图引用一个不存在的网格单元。这一异常类在Grids库单元中定义。
DDE异常。比如应用程序找不到特定的服务器或会话,或者一个连接意外中止。这一异常类在DDEMan库单元中定义。