|
在应用程序开发中如何检测、处理程序的运行错误是一个很重要的问题。在Delphi 的集成开发环境( IDE )中提供了一个完善的内置调试器,可以帮助你发现大部分程序错误。但并不是所有的错误都可以被发现,而且当程序涉及到与外设的数据交换或操作外设,如要求用户输入、读写磁盘等时,错误的发生是程序无法控制的,如输入非法字符、磁盘不能读写等。这些情况不仅会导致应用程序异常中止而且可能引起系统的崩溃。针对这些问题,Delphi同时提供了一套强大的异常处理机制。巧妙地利用它,可以使你的程序更为强健,使用更为友好。 虽然Delphi为应用程序提供了一套缺省的自动异常处理机制,即当前模块发生错误后退出当前模块并给出错误信息,而并不立即引起应用程序的中止。但当应用程序执行的过程性很强时,仅仅利用这种方法是不够的,而且很容易导致程序执行的不可预测性。 12.1 Delphi异常处理机制与异常类 Delphi异常处理机制建立在保护块(Protected Blocks)的概念上。所谓保护块是用保留字try和end封装的一段代码。保护块的作用是当应用程序发生错误时自动创建一个相应的异常类(Exception)。程序可以捕获并处理这个异常类,以确保程序的正常结束以及资源的释放和数据不受破坏。如果程序不进行处理,则系统会自动提供一个消息框。 异常类是Delphi异常处理机制的核心,也是Delphi异常处理的主要特色。下面我们对异常类的概念和体系进行详细的介绍。 Delphi提供的所有异常类都是类Exception的子类。用户也可以从Exception派生一个自定义的异常类。 Exception类的定义如下,对于不常用的成员没有列出。 {SysUtils 单元中} Exception = class(TObject) private FMessage: PString; FHelpContext: Longint; function GetMessage: String; procedure SetMessage(const Value: String); public constructor Create(const Msg: String); constructor CreateFmt(const Msg: String; const Args: array of const);. . . destructor Destroy; override; property HelpContext: Longint property Message: String; property MessagePtr: PString; end; Exception的一系列构造函数中最重要的参数是显示的错误信息。而数据成员中最重要的也是可被引用的消息字符串(message,messagePtr)。这些信息分别对自定义一个异常类和处理一个异常类有重要作用。 Delphi提供了一个很庞大的异常类体系,这些异常类几乎涉及到编程的各个方面。从大的方面我们可以把异常类分为运行时间库异常、对象异常、部件异常三类。下面我们分别进行介绍。 12.1.1 运行时间库异常类(RTLException) 运行时间库异常可以分为七类,它们都定义在SysUtils库单元中。 12.1.1.1 I/O异常 I/O异常类EInOutError是在程序运行中试图对文件或外设进行操作失败后产生的,它从Exception派生后增加了一个公有数据成员ErrorCode,用于保存所发生错误的代码。这一成员可用于在发生I/O异常后针对不同情况采取不同的对策。 当设置编译指示{$I- } 时,不产生I/O异常类而是把错误代码返回到预定义变量IOResult中。 12.1.1.2 堆异常 堆异常是在动态内存分配中产生的,包括两个类EOutOfMemory和EInvalidPointer。 表12.1 堆异常类及其产生原因 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 异常类 引发原因 ───────────────────────────────── EOutOfMemory 没有足够的空间用于满足所要求的内存分配 EInvalidPointer 非法指针。一般是由于程序试图去释放一个业已释放的指针而引起的 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.1.1.3 整数异常 整数异常都是从一个EIntError类派生的,但程序运行中引发的总是它的子类:EDivByZero,ERangeError,EIntOverFlow。 表12.2 整数异常及其产生原因 ━━━━━━━━━━━━━━━━━━━━━ 异常类 引发原因 ───────────────────── EDivByZero 试图被零除 ERangeError 整数表达式越界 EIntOverFlow 整数操作溢出 ━━━━━━━━━━━━━━━━━━━━━━ ERangeError当一个整数表达式的值超过为一个特定整数类型分配的范围时引发。比如下面一段代码将引发一个ERangeError异常。 var SmallNumber: ShortInt; X , Y: Integer; begin X := 100; Y := 75; SmallNumber := X * Y; end; 特定整数类型包括ShortInt、Byte以及与整数兼容的枚举类型、布尔类型等。例如: type THazard = ( Safety , Marginal , Critical , Catastrophic ); var Haz: THazard; Item: Integer; begin Item:= 4; Haz:= THazard ( Item ); 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选择框。 EIntOverFlow异常类在Integer、Word、Longint三种整数类型越界时引发。如: var I : Integer; a,b,c : Word; begin a := 10; b := 20; c := 1; for I := 0 to 100 do begin c := a*b*c; end; end; 引发一个EIntOverFlow异常。 EIntOverFlow异常类只有在编译选择框Option|Project|Over_Flow_CheckOption选中时才产生。当关闭溢出检查,则溢出后变量保留该类整数的最大范围值。 整数类型的范围如下表。 表12.3 整数类型的范围 ━━━━━━━━━━━━━━━━━━━━━━━━━━━ 类型 范围 格式 ─────────────────────────── Shortint -128 .. 127 有符号8位 Integer -32768 .. 32767 有符号16位 Longint -2147483648 .. 2147483647 有符号32位 Byte 0 .. 255 无符号8位 Word 0 .. 65535 无符号16位 ━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.1.1.4 浮点异常 浮点异常是在进行实数操作时产生的,它们都从一个EMathError类派生,但与整数异常相同,程序运行中引发的总是它的子类EInvalidOp、EZeroDivide、EOverFlow、EUnderFlow。 表12.4 浮点异常类及其引发原因 ━━━━━━━━━━━━━━━━━━━━━━━━ 异常类 引发原因 ──────────────────────── EInvalidOp 处理器碰到一个未定义的指令 EZeroDivide 试图被零除 EOverFlow 浮点上溢 EUnderFlow 浮点下溢 ━━━━━━━━━━━━━━━━━━━━━━━━ EInvalidOp最常见的引发原因是没有协处理器的机器遇到一个协处理器指令。由于在缺省情况下Delphi总是把浮点运算编译为协处理器指令,因而在386以下微机上常常会碰到这个错误。此时只需要在单元的接口部分设置全局编译指示{$N-},选择利用运行时间库进行浮点运算,问题就可以解决了。 各种类型的浮点数(Real、Single、Double、Extended)越界引起同样的溢出异常。这同整数异常类是不同的。 12.1.1.5 类型匹配异常 类型匹配异常EInvalidCast当试图用As操作符把一个对象与另一类对象匹配失败后引发。 12.1.1.6 类型转换异常 类型转换异常EConvertError当试图用转换函数把数据从一种形式转换为另一种形式时引发,特别是当把一个字符串转换为数值时引发。下面程序中的两条执行语句都将引发一个EConvertError异常。 var rl : Real; int: Integer; begin rl := StrToFloat(' $140.48'); int := StrToInt(' 1,402 '); end; 要注意并不是所有的类型转换函数都会引发EConvertError异常。比如函数Val当它无法完成字符串到数值的转换时只把错误代码返回。利用这一点我们在(6.2)节中实现了输入的类型和范围检查。 12.1.1.7 硬件异常 硬件异常发生的情况有两种:或者是处理器检测到一个它不能处理的错误,或者是程序产生一个中断试图中止程序的执行。硬件异常不能编译进动态链接库(DLLs)中,而只能在标准的应用中使用。 硬件异常都是EProcessor异常类的子类。但运行时间并不会引发一个EProcessor异常。 表12.5 硬件异常类及其产生原因 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 异常类 引发原因 ───────────────────────────────── Efault 基本异常类。是其它异常类的父类 EGPFault 一般保护错。通常由一个未初始化的指针或对象引起 EStackFault 非法访问处理器的栈段 EPageFault Windows内存管理器不能正确使用交换文件 EInvalidOpCode 处理器碰到一个未定义的指令。这通常意味着处理器 试图去操作非法数据或未初始化的内存 EBreakPoint 应用程序产生一个断点中断 ESingleStep 应用程序产生一个单步中断 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ EFault、EGPFault 往往意味着致命的错误。而EBreakPoint、ESingleStep被DelphiIDE的内置调试器处理。事实上前边的五种硬件异常的响应和处理对开发者来说都是十分棘手的问题。
|