BCB讲座第十三讲异常处理
} 为MP3Collect程序添加异常处理功能 了解了有关CBuilder中异常处理的基础知识后,下面我们就来为 Mp3Collect添加异常处理代码,以提高程序的容错性和强壮性。 实际上,由CBuilder自动生成的程序框架中已经包含了异常处理代 码,在应用程序入口单元文件MP3Collect.cpp中,入口函数WinMain()便 包含有try和catch代码段。不过,仅仅依靠这些缺省的异常处理还不 够,我们还需要针对不同的错误情况分别进行处理,才能保证程序运行 的可靠性。 在Mp3Collect中,可能出现的异常情况主要是由媒体播放器控件产生 的EMCIDeviceError和进行文件操作时产生的各种文件操作异常,具体 地说,可能产生异常的函数主要有btnShowAllClick()、btnFindClick()、 SaveFile()、btnFileNameClick()、mnuAddClick()、 ListView1SelectItem()、MediaPlayer1Click()、Timer1Timer()、 PlayTheSong()等。不过,为了简化程序代码,心铃准备统一采用了 Exception类来捕捉所有的VCL异常对象,并利用对象的类名称属性 ClassName来显示该异常的类别。 BtnFileNameClick()的主要功能是运行打开文件对话框OpenDialog1, 并将获取的文件名赋给文件名称编辑框和媒体播放器,其中,从 OpenDialog1->Execute()开始就可能产生异常,下面是添加了异常处理后 的btnFileNameClick(): void __fastcall TMainForm::btnFileNameClick(TObject *Sender) { try{//标志可能产生异常的代码段 if(OpenDialog1->Execute()) { MediaPlayer1->FileName=OpenDialog1->FileName; edtFileName->Text=OpenDialog1->FileName; edtSongName>Text=ChangeFileExt(ExtractFileName(OpenDialog1>FileName),""); edtSingerName->Text=""; MediaPlayer1->Open(); } } catch(Exception &e){//异常信息由两部分组成:异常对象的类名称、 异常对象的错误消息 ShowMessage(AnsiString(e.ClassName())+ ": " + e.Message);
{ DoDefault=false;//取消缺省操作 m_bIsห้องสมุดไป่ตู้laying=false;//清除播放标志 Timer1->Enabled=false;//关闭时钟 EnableButtons(true);//使能命令按钮 ShowMessage(AnsiString(e.ClassName())+" Error IntToStr(MediaPlayer1->Error) + ": " + e.Message); } } Timer1Timer()与MediaPlayer1Click()的异常处理方式基本一致,也是 将函数体内原有的全部代码标志为try代码段,并在catch代码段中进行 清除播放标志、关闭Timer1等操作。另外,在时钟处理函数中应该尽量 缩短函数的执行时间,避免函数重入引起的问题,因此Timer1Timer()函 数的catch代码段最好不要显示异常信息,如果确实需要显示,必须在关 闭Timer1之后再显示。 由于PlayTheSong()被MediaPlayer1Click()和Timer1Timer()调用,而后 两个函数中的异常处理代码完全可以处理PlayTheSong()可能产生的异 常,因此就不需要再单独添加异常处理代码了。 最后,btnShowAllClick()、btnFindClick()、SaveFile()三个函数已经有 了基于传统的条件判断式的异常检测和异常处理代码,能够处理打开文 件不正常的情况,读者朋友可以自行练习一下,把这一部分的异常处理 改用try和catch代码段来实现。 本讲介绍了在CBuilder中进行异常处理的内容,不少初学 CBuilder(或其它编程语言)的朋友因为觉得异常处理很复杂或是用处 不大,就忽略了这部分内容,其实,异常处理的原理并不复杂,无非是 中断产生异常的程序代码,捕捉可能产生的异常并进行相应处理,掌握 了这一基本原理之后,就容易理解为什么要引入异常处理机制,以及如 何使用异常处理机制了。
常处理机制,微软公司提供的Win32结构化异常处理机制,以及基于 VCL的异常处理机制,后者是Borland公司建议在CBuilder编程中采用的 异常处理方式。 基于VCL的典型异常处理结构的形式如下所示: try{ //可能引起异常的代码段 } catch(Exception &e){ //对异常进行处理的代码 } 其中try和catch为C++关键字。try用于标志可能产生异常的代码段 (Block),该代码段用try后紧跟的一对大括号{}包括在内。如果这段 程序在运行时产生了异常,系统会中止try代码段中的代码执行,并查找 相应的catch代码段,如果找到了合适的catch代码段,即表示错误被捕 捉到,这时相应的catch代码段被执行,如果没有找到合适的catch代码 段,即错误始终没有被捕捉到,则系统会调用VCL库按照缺省的方法来 处理异常。当然,如果try代码段在运行时一切正常,则catch代码段是 不会被调用的。 在上面的异常处理结构中,我们看到,catch语句带有一个参数 Exception &e,该参数是一个异常对象的引用。其中Exception是VCL库 提供的异常处理类,该类代表了VCL库对异常事件的一个封装。也许有 的朋友要问,catch语句中的Exception &e对象是哪里来的呢?整个程序 代码中没有该对象的声明或定义呀?这正是VCL异常处理机制的特点, 当异常产生时,VCL库会自动生成该异常对象,并将其作为参数调用合 适的catch代码段。 Exception类也是其它VCL异常处理类的基类。为了处理不同的异常 原因,CBuilder提供了多种异常处理类,例如,代表申请内存失败的 EOutOfMemory异常,代表除数为0的EDivByZero异常,代表文件打开 错误的EFOpenError,代表数据库操作错误的EDatabaseError,以及代表 多媒体操作错误的EMCIDeviceError等。 事实上,一个代码块可能产生不止一种类型的错误,这样,对一个 try代码段可以采用多个catch代码段。例如,一个try代码段内部可能产 生申请内存失败异常EOutOfMemory和打开文件错误EFOpenError,那么 我们可以使用两个catch语句来分别监视两种异常情况。采用多个catch 语句的优点在于,可以对不同类型的异常分别进行捕捉和处理,但有时 即使使用了多个catch语句后仍无法保证能够捕捉到所有的异常,这时可 以使用参数为省略号(...)的通用catch语句,它可以捕捉尚未捕捉的所有
任意类型的异常。 下面是使用多个catch及通用catch语句的典型例子,try代码段中进行 了打开文件操作、分配内存操作、文件读操作和整数除法操作,这些操 作都有可能引起异常,程序中对打开文件异常和分配内存异常分别进行 了处理,对剩下的异常则统一由catch(...)语句进行处理。 try{ FILE * fp=fopen("test.dat","rb");//可能出现EFOpenError类异常 BYTE * buf=new BYTE[1024];//可能出现EOutOfMemory类异常 int k,i=100,j=fread(buf,1024,1,fp);// 可能出现EReadError类异常 k=i/j; //可能出现EDivByZero类异常 fclose(fp); delete buf; } catch(EFOpenError &e){ ShowMessage("test.dat:打开文件错误"); } catch(EOutOfMemory &e){ ShowMessage("内存不足错误"); } catch(...){ ShowMessage("应用程序出现异常错误"); } CBuilder为VCL库提供了非常灵活的异常唤醒和异常处理机制,除了 由VCL库检测和产生异常之外,还可以由程序通过throw语句来强制产 生一个异常,例如下面的代码在检测到异常情况后,强制产生一个异 常,并初始化异常消息,交由catch代码段进行处理,后者将显示出现异 常的信息。在这段代码中,异常检测仍然采用的是传统的条件语句检测 方法,但异常处理则采用的是面向对象的异常处理方式。 try{ FILE * fp=fopen("test.dat","rb"); if(fp==NULL) //检测异常条件 throw Exception("test.dat:打开文件错误"); //创建异常对象, 并抛出异常对象 ...//正常的执行代码 } catch(Exception &e){ ShowMessage(e.Message);//显示异常信息
} } mnuAddClick()与btnFileNameClick()相似,此处不再重复。 ListView1SelectItem()在操作MediaPlayer1控件时容易产生异常,因此 try代码段中应包括对MediaPlayer1的操作,添加异常处理后的该函数如 下: void __fastcall TMainForm::ListView1SelectItem(TObject *Sender, TListItem *Item, bool Selected) { if(Item && Selected) { ……//其它操作 try{//try代码段包括对MediaPlayer1的操作 MediaPlayer1->FileName=edtFileName->Text; MediaPlayer1->Open(); } catch(Exception &e){ //异常消息由三部分组成:异常的类名称、MediaPlayer1的错误代 码及其错误信息 ShowMessage(AnsiString(e.ClassName())+" Error " + IntToStr(MediaPlayer1->Error) + ": " + MediaPlayer1>ErrorMessage); } } } MediaPlayer1Click()函数体内原有的全部代码都应该被包括在try之 内,并且在catch代码中,除了显示异常信息外,还需要清除播放标志, 并关闭Timer1。添加了异常处理代码的该函数如下: void __fastcall TMainForm::MediaPlayer1Click(TObject *Sender,TMPBtnType Button,bool &DoDefault) { try{ switch(Button){ ……//根据Button参数判断用户点击了什么按钮,进行相应的 处理 } } catch(Exception &e)//捷捉可能产生的异常