Delphi中的异常处理
转载:http://www.cnblogs.com/doit8791/archive/2012/05/08/2489471.html
以前写Delphi程序一直不注意异常处理,对其异常处理的机制总是一知半解,昨天程序中的一个bug ,让我对异常有了更深入的认识,必须要对可能产生异常的地方进行异常处理,否则可能给程序造成灾难。
就像昨天,因为写的 filecopy 函数没有做异常捕获处理,导致复制文件出错时整个程序崩溃,用户只能通过杀进程的方式重启程序再进行其他操作。后来对程序进行了异常处理,遇到意外时只是提示一下用户,然后可以继续运行下去,表现的很完美,才意识到异常处理的重要性,故要总结下Delphi 异常处理相关的知识
Delphi的异常处理的机制介绍
Delphi异常处理机制建立在保护块(protected blocks)的概念上。所谓保护块是用保留字try 和 end 封装的一段代码。保护块的作用是当应用程序发生错误的时候自动创建一个相应的异常类(Exception)。程序可以捕获并处理这个异常类,以确保程序的正常结束以及资源的释放和数据不受破坏。如果程序不进行处理,则系统会自动提供一个消息框。每一段程序都有可能产生错误!这是软件业的一个不容置疑的现象和规律。
传统的处理错误的方式和异常处理的联系和区别
事实上,传统的 if...else...结构完全可以解决所有的错误,使用Exception 机制也没能够回避在最原始的层次,通过遍历可能的情况来产生异常的做法,但是异常提供了一种更加灵活和开放的方式,使得后来的编程者可以来根据实际的情况处理这种错误,而不是使用预先设定好的处理结果
一、异常的来源
在Delphi的应用程序中,下列的情况比较有可能产生异常
1) 文件处理
2) 内存分配
3) Windows资源
4) 运行时创建对象和窗体
5) 硬件和操作系统冲突
二、异常处理
1. try... except... end;
在 try体内的代码发生异常时,系统将转向 except部分进行异常的处理。这是Delphi 处理异常的最基本的方式之一。try语句块指出来需要进行异常保护的代码。如果在这部分有不正常的事情发生,则引发一个异常对象。 except 是异常处理部分,被保护部分引发的异常对象将执行 <异常处理语句> 或由这部分代码捕获并进行处理
try ..except ..end 语句的一般格式如下
try //try保护代码块 被保护语句块 except //异常处理块 异常处理语句(如果异常不发生,就不执行) end;
或者
try //try保护代码块 被保护语句 except //异常处理块 on <异常处理对象类型1> do <语句1> //捕获指定类型的异常对象,进行处理 on <异常处理对象类型2> do <语句2> //捕获指定类型的异常对象,进行处理 else <语句 n+1> //缺省的异常处理代码 end;
2. try... finally... end;
这种异常处理结构一般用于保护 Windows的资源分配等方面,它确保了无论 try 体内的代码是否发生异常,都需要由系统进行最后的统一处理的一些 Windows对象的正确处理
和 try... except... end不同,该结构的finally部分总被执行。在 try-finally语句中,当 try部分产生异常后,应用程序直接执行 finally部分的资源释放语句
try finally语句的一般格式如下
try //try保护代码块 被保护语句 finally //异常处理块 异常处理语句 //无论异常是否发生,都必须处理 end;
若用作创建一个资源保护块时,它的格式可以写成:
(分配系统资源) try (使用系统资源的语句) finally (释放系统资源) end;
3. 不存在 try... except ... finally... end 结构来既处理异常,又保护资源分配的结构
但是,try... except... end 结构允许嵌套到 try... finally... end结构中,从而实现既处理异常,又保护资源的分配
4. raise:知道一些情况不合理,直接手工弹异常对话框。
比如
raise 异常类.Create('异常的缺省说明');
5. try... finally结构和 try... except 结构在用法上主要有以下区别
1) 对于try...finally结构来说,不管try部分的代码是否触发异常,finally部分总是执行的。如果发生异常,就提前跳到finally部分。而对于try...except结构来说,只有当触发了异常后,才会执行except部分的代码。
2) 在 try... except 结构中,当异常被处理后异常对象就被释放,除非重新触发异常。而在 try... finally 结构中,及时 finally部分对异常做了处理,异常对象依然存在
3) finally 部分不能处理特定的异常,因为它没有 try... except 结构中的异常句柄,无法知道确切的异常类型。因此,在 finally 部分只能对异常作笼统的处理
4) 在 try... finally 结构中,如果在 try 部分调用标准命令 exit、break或 continue,将导致程序的执行提前跳到 finally部分。finally部分不允许调用上述三个命令
三、Delphi中的异常类结构
Delphi 提供的所有异常类都是 Exception的子类。用户也可以从 Exception派生一个自定义的异常类。即 Exception是所有异常类的基类,它并不是以 'T' 开头的,而是以 'E' 开头,它的派生类也是以 'E' 开头的。
Delphi提供了一个很庞大的异常类体系,从大的方面可以把异常类分为运行库异常、对象异常、组件异常三类
1.运行库异常类(RTL Exception)
运行库异常类可以分为七类,它们都定义在SysUtils 库单元中
1.1. I/O异常
I/O异常类 EIOOutError 是在程序运行中试图对文件或外设进行操作失败之后产生的,它从 Exception 派生后增加了一个公有数据成员 ErrorCode,用于保存所发生错误的代码。这一成员可用于在发生 I/O 异常后针对不同的情况采取不同的策略
当设置编译指令 {$I-} 时,不产生 I/O异常类而是把错误代码返回到预定义变量 IOResult中
1.2堆异常
堆异常是在动态内存分配中产生的,包括两个雷 EOutOfMemory 和EInvalidPointer,如下表所示
堆异常类 | 引发条件、产生原因 |
EOutOfMemory | 没有足够的控件用于满足所要求的内存分配 |
EInvalidPointer | 非法指针。一般是由于程序试图去释放一个已经释放的指针而引起的 |
1.3整数异常
整数异常都是从一个 EIntError 类派生的,但是程序运行中引发的总是它的子类:EFivByZero,ERangeError,EIntOverFlow,如下表
异常类 | 引发条件 |
EDivByZero | 试图被零除 |
ERangeError | 整数表达式越界 |
EIntOverFlow | 整数操作溢出 |
ERangeError 当一个整数表达式的值超过一个特定整数类型分配的范围时引发。比如下面一段代码将引发一个ERangeError异常
var Smallnumber: ShortInt; X, Y: Integer; begin X:= 100; Y:= 75; SmallNumber:= X*Y; // X*Y的值超出了 SmallNumber(ShortInt)的表示范围 end
特定整数类型包括 ShortInt、Byte以及与整数兼容的枚举类型、布尔类型等。例如
type THazard = (Safety, Marginal, Critical, Catastrophic); var Haz: THazard; Item: Integer; begin Item:= 5; Haz:= THazard(Item); // Haz是THazard类型,范围如果用整数表示是0~3,但这里将5 赋值给它,所以出现 ERangeError异常 end;
由于枚举类型越界而引发一个 ERangeError异常。数组下标越界也会引发一个 ERangeError异常,如
var Values: array[1..10] of Integer; I: Integer; begin for I:=1 to 11 do Values[I]:= I; end;
ERangeError 异常只有当范围检查打开时才会引发。这可以在代码中包含 {$R+} 编译指令或设置 IDE的 Option|Project 的Range_Checking Option 选择框。注意 Delphi不对长字符串做范围检查
EIntOverFlow异常类在 Integer、Word、LongItn三种证书类型越界时引发。如下面的代码将引发一个 EIntOverFlow 异常
var I: Integer; a, b, c: Word; begin a:= 10; b:= 20; c:= 1; for I:=0 to 100 do c:= a*b*c; end;
EIntOverFlow异常类只有在编译选择框 Option|Projetc|Over_Flow_Check Option选中时才产生。当关闭异常检查,则溢出后变量的值是丢弃溢出部分后的剩余值
1.4浮点异常
浮点异常是在进行实数操作时产生的,它们都从一个 EMathError类派生,但与整数异常相同,程序运行中引发的总是它的子类 EInvalidOP、EZeroDivide、EOverFlow、EUnderFolw
异常类 | 引发条件 |
EInvalidOp | 处理器碰到一个未定义的指令 |
EZeroDivide | 试图被零除 |
EOverFlow | 浮点上溢 |
EUnderFlow | 浮点下溢 |
EInvalidOp最常见的引发条件是没有协处理器的机器遇到一乐协处理器指令。由于在缺省情况下 Delphi总是把浮点运算编译为协处理器指令,因而在 386以下微机上常会遇到这种错误。此时只需要在单元的接口部分设置全局编译指令 {$N-} ,选择利用运行库进行浮点运算,问题就可以解决了
各种类型的浮点数(Real、Single、Double、Extended)越界引发同样的溢出异常
1.5类型匹配异常
类型匹配异常 EInvalidCast 当试图用 as 操作符把一个对象与另一类对象匹配失败后引发
1.6类型转换异常
类型转换异常 EConvertError 当试图用转换函数把数据从一种类型转换为另一种类型时引发,特别是当把一个字符串转换为数值时引发。下面程序中的两条执行语句都将引发一个 EConvertError异常
var r1: Real; int: Integer; begin r1:= StrToFloat('$140.48'); int:= StrToInt('1,402'); end;
要注意,并不是所有的类型转换函数都会引发 EConvertError异常。比如函数 Val当它无法完成字符串到数值的转换时只把错误代码返回。利用这一点我们实现了输入的类型和范围检查
1.7硬件异常
硬件异常发生的情况有两种:或者是处理器检测到一个它不能处理的错误,或者是程序产生一个中断试图终止程序的执行。硬件异常不能编译进动态链接库(DLL)中,而只能在标准的应用中使用,如下表
硬件异常都是 EProcessor异常类的子类。但是运行时并不会引发一个 EProcessor异常
异常类 | 引发条件 |
EFault | 基本异常类,是其他异常类的父类 |
EGPFault | 一般保护错,通常由一个未初始化的指针或对象引起 |
EStackFault | 非法访问处理器的栈段 |
EPageFault | Windows内存管理器不能正确使用交换文件 |
EInvalidOpCode | 处理器碰到一个未定义的指令,这通常意味着处理器试图去操作非法数据或未初始化的内存 |
EBreakPoint | 应用程序产生一个断点中断 |
ESingleStep | 应用程序产生一个单步中断 |
EFault、EGPFault 往往意味着致命的错误。
而EBreakPoint、ESingleStep被 Delphi IDE的内置调试器处理。
事实上,上表中前面的五种硬件异常的响应和处理对开发者来说都是十分棘手的问题
2.对象异常类
所谓对象异常是指非组件的对象引发的异常。Delphi定义的对象异常包括流异常、打印异常、图形异常、字符串链表异常等
2.1流异常类
流异常类包括 EStreamError、EFCreateError、EFOpenError、EFileError、EReadError、EWriteError、EClassNotFound。
流异常在 Classes库单元中定义
异常类 | 引发条件 |
EStreamError | 利用LoadFromStream方法读一个流发生错误 |
EFCreateError | 创建文件时发生错误 |
EFOpenError | 打开文件时发生错误 |
EFileError | 试图再次登录一个已经存在的对象 |
EReadError | ReadBuffer方法不能读取特定数目的字节 |
EWriteError | WriteBuffer方法不能写特定数目的字节 |
EClassNotFound | 窗口上的组件被从窗口的类型定义中删除 |
2.2打印异常类
打印异常类 EPrintet 当打印发生错误时引发。它在 Printers库单元中定义。例如应用程序试图向一个不存在的打印机或由于某种原因打印工作无法送到打印机时,就会产生一个打印异常
2.3图形异常类
图形异常类定义在 Graphic 库单元中,包括 EInvalidGraphic 和 EInvalidGraphicOperation两类
EInvalidGraphic 当应用程序试图从一个并不包含合法的位图、图标、元文件或用户自定义图形类型的文件中装入图形时引发的。例如下面的代码
Image1.Picture.LoadFromFile('Readme.txt')';
由于 Readme.txt 并不包含一个合法的图形,因而将引发一个 EInvalidGraphic异常
EInvalidGraphicOperation 当试图对一个图形进行非法操作时引发,例如试图改变一个图标的大小
var AnIcon: TIcon; begin AnIcon:= TIcon.Create; AnIcon.LoadFromFile('C:/WINDOWS?DIRECTRY.ICO'); AnIcon.Width:= 100; {引发一个图形异常} end;
2.4字符串链表异常
字符串链表异常 EStringListError、EListError 在用户对字符串链表进行非法操作时引发
由于许多组件(如 TListBox、TMemo、TTabSet、……)都有一个 TStrings 类的重要属性,因而字符串链表异常在组件操作编程中非常有用
EStringListError 异常一般在字符串链表越界时产生。例如对如下初始化的列表框
ListBox1.Itema.Add('Firstitem'); ListBox1.Items.Add('Seconditem'); ListBox1.Items.Add('Thiritem);
则以下操作都会引起 EStringListError 异常
1) 当字符串链表的 Duplicates属性设置为 dupError时,应用程序试图加入一个重复的字符串
2) 试图往一个排序的字符串链表中插入一个字符串
3.组件异常类
3.1通用组件异常类
通用组件异常类常用的有三个:EInvalidOperation、EComponentError、EOutOfResource。其中 EInvalidOperation、EOutOfResource在 Controls单元中定义;EComponentError在 Classes 单元中定义
(1)非法操作异常 EInvalidOperation
EInvalidOperation引发的原因可能有:
a. 应用程序试图对一个Parent 属性为 nil 的组件进行一些需要 Windows句柄的操作
b. 试图对一个窗口进行拖放操作
c. 操作违反了组件属性间内置的相互关系等
例如,ScrollBar、Gauge等组件要求 Max属性大于等于 Min属性,因而下面的语句
ScrollBar1.Max:= ScrollBar1.Min-1;
将引发一个 EInvalidOperation异常
(2)组件异常EComponentError
引发该异常的原因可能有:
a. 在 Register 过程之外试图登录一个组件(常用于自定义组件开发中)
b. 应用程序在运行中改变了一个组件的名称并使该组件与另一个组件重名
c. 一个组件的名称改变为一个 Object Pascal 非法的标识符
d. 动态生成一个组件与已存在的另一组件重名
(3)资源耗尽异常 EOutOfResource
当应用程序试图创建一个 Windows句柄而 Windows却没有多余的句柄分配时引发该异常
3.2专用组件异常类
许多组件都定义了相应的组件异常类。但并不是有关组件的任何错误都会引发相应的异常类。许多情况下它们将引发一个运行时异常或对象异常
下面列出几个典型的组件异常类
(1)EMenuError
非法的菜单操作,例如试图删除一个不存在的菜单项。这一异常类在Menus 库单元中定义
(2)EInvalidGridOperation
非法的网格操作,比如试图引用一个不存在的网格单元。这一异常类在Grids 库单元中定义
(3)EDDEError
DDE异常。比如英语程序找不到特定的服务器或会话,或者一个连接意外中止。这一异常类在 DDFMan库单元中定义
(4)EDatabaseError、EReportError
数据库异常(EDatabaseError)和报表异常(EReportError)在进行数据库和报表操作出现错误时引发