A Delphi programozási nyelv

Kivételkezelés

Szintaxis

Általában véve a kivétel olyan hibás állapot vagy esemény, amely megszakítja az alkalmazás szabályszerű futás folyamatát. Amikor fellép egy kivétel, a vezérlés a futóprogram aktuális pontjáról a kivételkezelőhöz (exception handler) adódik át. Az Object Pascal a kivételkezelés támogatására olyan strukturális eszközöket tartalmaz, amelyek lehetővé teszi a normális programlogika és hibakezelési logika kettéválasztását. Ezzel nagyban növekszik az alkalmazások karbantarthatósága.

Az Object Pascal a kivételek megvalósítására objektumokat használ, melynek a legfontosabb előnyei így foglalhatóak össze:

A kivételkezelés használata

A kivételkezelés használatához az alkalmazásnak szüksége van a SysUtils unitra, amely az Object Pascal futási idejű könyvtárához (run-time library) definiálja kivételeket. Amikor egy alkalmazás használja a SysUtils unitot, a futási idejű hibák automatikusan kivételekké alakulnak. Ennek köszönhetően az olyan hibaállapotokat – mint például a memória túlcsordulása, nullával való osztás és az általános védelmi hiba -, amelynek máskülönben az alkalmazás leállításához vezetne, el lehet fogni és strukturált módon fel lehet dolgozni (kezelni).

Megjegyzés: a Delphi Visual Component Library teljes egészében támogatja a kivételkezelést. A VCL–en alapuló alkalmazások automatikusan használják a SysUtils unitot, engedélyezve ezzel a kivételkezelést. A TForm osztály az üzenet-feldolgozó ciklusában kezeli a kivételeket, így egy eseménykezelőben fellépő kezeletlen kivételtől nem áll le a program, csak egy hibaüzenet jelenik meg.

Kivételek definiálása

Object Pascalban a kivétel egyszerűen egy osztályt jelent, így a kivétel deklarációja semmivel sem különbözik egy közönséges osztálydeklarációtól. Kivétel objektumnak tetszőleges osztály példányát lehet használni, azonban ajánlatos minden kivételt a SysUtils unitban definiált Exception osztályból származtatni:

type EMathError = class (exception); EInvalidOp = class (EMathError); EZeroDivide = class (EMathError); EOwerflow = class (EMathError); EUnderflow = class (EMathError);

Az egymással összefüggő kivételeket - az öröklődés adta lehetőségeket kihasználva – családokba szokás csoportosítani. A példában szereplő deklarációk a matematikai kivételek családját definiálják. A kivételcsaládok révén lehetőség nyílik arra, hogy a kivételek csoportját egyetlen névvel azonosítjuk. Például, az EMathError kivételt feldolgozó kód kezelni fogja az összes EMathError osztályból közvetlenül vagy közvetett módon származtatott kivételt.

Vannak esetek, amikor a származtatott kivételosztályok újabb adatmezőket, metódusokat és jellemzőket vezetnek be a kivételekhez kapcsolódó kiegészítő információk továbbítására. Például, a SysUtils unitokban definiált EInOutError osztály az ErrorCode mezőt vezeti be, amely a kivételt kiváltó file-hiba kódját tartalmazza:

type EInOutError= class (Exception) ErrorCode: integer; end;

Kivételek kiváltása

Kivételeket a raise utasítás segítségével válthatunk ki:

raise [kivétel példány [at címkifejezés]]

A raise kulcsszó után osztálytípus objektumnak kell állnia. Amikor fellép egy kivétel, a kivételkezelő logika átveszi a kivétel objektum birtoklási jogát. Miután a kivételkezelés megtörtént, a kivétel-objektum automatikusan törlődik a Destroy destruktor hívása révén. Fontos, hogy a raise utasítás objektumot, nem pedig osztályt vár az argumentumában. Általában a kivétel-objektumot közvetlenül az argumentumban hozzuk létre a megfelelő kivételosztály Create konstruktorának meghívásával. Az argumentum nélküli raise utasítás újra kiváltja az aktuális kivételt.

A raise utasítás nem „tér vissza” a szokásos értelemben. Ehelyett a raise a vezérlést annak a legbelső kivételkezelőnek adja, amely alkalmas az adott osztályú kivételek kezelésére. A legbelső ebben az esetben azt a kezelőt jelenti, amelynek a try ... except blokkjába legkésőbb léptünk be, és még nem léptünk ki belőle.

Példa: a következő Str2IntR függvény stringet egésszé alakít. Ha az eredmény kilép a megadott intervallumból, egy ERangeError kivétel jön létre:

function Str2IntR(const s:string; max,min:longint):longint; begin result := StrToInt(s); if (result < min) or (result > max) then raise ERangeError.CreateFmt('%d nincs a megadott (%d..%d) intervallumban', [result, min, max]); end;

Fontos: egy unit initialization részében eldobott kivétel nem a várt hatást fogja kiváltani, mert a kivételkezelés a SysUtils unitban van bevezetve, és az ilyenkor még nem aktív.

Kivételek kezelése

A kivételek kezelése a try ... except utasítás segítségével történik. Az utasítás alakja a következő:

try utasítások listája except kivétel blokk [else kivétel blokk] end;

Az except blokk kivételkezelőit a következő alakban adhatjuk meg:

on [azonosító:] osztálytípus azonosító do utasítás

A try ... except utasítás egymás után végrehajtja az utasítások listájában szereplő utasításokat. Ha a végrehajtás során semmilyen hiba nem történt, akkor a kivétel blokk figyelmen kívül marad, és a vezérlés a try ... except utasítást záró end kulcsszót követő utasításra adódik.

Az except ... end rész kivételkezelőket tartalmaz. Valamely kivételkezelő meghívását csak a try ... except részben, illetve a try ... except részből hívott eljárásban vagy függvényben fellépő hiba, vagy raise utasítás végrehajtása eredményezi.

Kivételek terjedése

Amikor fellép egy kivétel, a vezérlés az ahhoz a legbelső kivételkezelőhöz kerül, amely alkalmas az adott osztályú kivételek kezelésére. A megfelelő kivételkezelő keresése az utolsóként végrehajtott try ... except utasításokban kezdődik. Ha ez nem tartalmaz megfelelő kivételkezelőt, akkor a keresés az egymásba ágyazott try ... except utasításokon keresztül kifelé haladva folytatódik. Ha egyetlen aktív try ... except utasítás sem tartalmazza a szükséges kezelőt, akkor a program futása hibaüzenettel megszakad.

A megfelelő kivételkezelő keresése a kivétel blokk on ... do kezelőinek vizsgálatával történik. Az on ... do kivételkezelőket a beírás sorrendjében ellenőrzi a rendszer, kiválasztva az első olyan kezelőt, amely típuskompatibilis az aktuális kivétellel. Ezért ajánlott, hogy mindig a származtatott kivételek kezelőit adjuk meg először a kezelőlistában.

Példa: A korábban említett kivételeket a megfelelő sorrendben adtuk meg:

try ... except on EZeroDivide do { //nullával való osztás kezelése }; on EOverflow do { //túlcsordulás kezelése}; on EMathError do { //matematikai kivétel kezelése}; end;

Ha a kivétel blokk tartalmaz else részt is, akkor az except részben folytatott sikertelen keresés esetén az else rész fog találtnak számítani.

Példa: az else ágban minden nem EMathError kivétel kezelése megtörténik:

try ... except on EZeroDivide do { //nullával való osztás kezelése }; on EOverflow do { //túlcsordulás kezelése}; on EMathError do { //matematikai kivétel kezelése}; else { //az összes többi kivétel kezelése}; end;

Ha a kivétel blokk csak utasításokat tartalmaz, akkor ez találtnak számít minden kivétel számára:

try ... except { //összes fellépő kivétel kezelése}; end;

Az on ... do kivételkezelőben nem kötelező azonosító: paramétert az on ... do utasítás végrehajtás során a kivételobjektum azonosítására használhatjuk:

try ... except on E: Exception do ErrorDialog(E.Message, E.HelpContext); end;

A kivételek ismételt előidézése

Olyan esetekben, mikor egy eljárás vagy egy függvény nem kezeli a benne fellépő kivételt, a kivételt tovább kell adni a függvényt hívó külső try ... except utasításnak, amelynek az except része kezelőket tartalmaz az adott jellegű kivételek feldolgozására. Ezt a mechanizmust az eredeti kivétel újraaktivizálásának nevezzük.

A kivételt ismételt előidézése a raise utasításnak a try ... except utasítás except részében történő megadásával végezhető el:

... try ... except ... raise; end;

Ha a kivételt újraaktivizáló függvény bármilyen memóriafoglalással kapcsolatos műveletet tartalmaz, akkor a kivétel ismételt előidézése előtt feltétlenül fel kell szabadítanunk a lefoglalt memóriaterületet vagy objektumokat.

Példa:

uses Classes, SysUtils; ... function FileLista(const path:string): TStringList; var sr:TSearchRec; begin result := TStringList.Create; try if FindFirst(path,0,sr) then repeat result.add(sr.name); until not FindNext(sr); except result.Free; raise; end; end;

Beágyazott kivételek

A kivételkezelőben végrehajtódó kód maga is kiválthat és kezelhet kivételeket. Ha a kivételkezelőben kiváltott kivételeket a kezelőn belül dolgozzuk fel, akkor ezek semmi hatással sincsenek az eredeti kivételre. Ha azonban a kivételkezelőben fellépő kivétel átterjed a kezelő határain kívülre, akkor az eredeti kivétel elveszik.

Példa:

A tangens függvényben az EMathError kivétel létrejötte esetén a kivételkezelő egy ETanError kivételt hoz létre. Mivel ennek a kivételnek a kezelését nem tartalmazza a Tan függvény, ezért ez a kivétel az eredeti kivételkezelőn kívülre kerül és a függvényt hívó számára az eredmény egyszerűen az lesz, hogy a Tan függvény egy ETanError kivételt váltott ki.

type ETanError = class (EMathError); function Tan(X:extended):extended; begin try result := sin(x) / cos(x); except on EMathError do raise ETanError.Create('Hibás argumentum!'); end; end;

Egyéb kapcsolódó dolgok

try ... finally

Amikor egy kódrészlet valamilyen erőforrást használ, szükséges lehet annak biztosítása, hogy mind a normális, mind pedig kivétellel megszakított programvégrehajtás esetében felszabadítsuk a lefoglalt erőforrást. Például, a file-nyitást és feldolgozást követően mindig biztosítani kell a file lezárását. Ilyen esetekben a try ... finally utasítást lehet használni:

try utasítások listája finally utasítások listája end;

A try ... finally utasítás egymás után végrehajtja a try és finally között álló utasításokat. Abban az esetben, ha a végrehajtás alatt nem történik semmilyen kivételt kiváltó hibás állapot, akkor egyszerűen végrehajtódnak a finally és az end között álló utasítások is. Ha azonban az első rész végrehajtása alatt bármelyik utasítás hibát váltott ki, a vezérlés átmegy a finally részhez, és az ott megadott utasítások végrehajtása után a kivétel automatikusan újra fellép, melynek feldolgozása általában a külső try ... except utasításban történik. Ezzel a megoldással a finally rész utasításai mindig végrehajtódnak, függetlenül a try rész feldolgozásának sikerességétől.

Példa: a try ... finally biztosítja, hogy a nyitott állomány mindig bezárjuk:

Reset(f); try //műveletek az állománnyal finally CloseFile(f); end;

A try ... finally alakú utasításokat általában gyakrabban használja egy alkalmazás, mint a try ... except alakúakat. Mivel a Delphi Visual Component Libraryt használó alkalmazások az esetek többségében a VCL alapértelmezés szerinti kivételkezelő mechanizmusait használják, ezért ritkán van szükség a try…except utasításokra, és inkább a try ... finally utasításokat alkalmazzák.

Olyan esetekben azonban, mint például a memóriafoglalás, szükség van a kivételek elleni védekezésre, így az ilyen alkalmazásokban gyakoribb a try ... except utasítások használata, amelyek természetesen tartalmazhatnak beágyazott try ... finally utasításokat is.

Megjegyzés: A beágyazásnál azonban vigyázni kell arra, hogy a try ... finally utasítás finally részében fellépő lokális kivételeket ugyanitt kezeljük, máskülönben az eredetileg kiváltott kivétel helyett ez a lokális kivétel kerül ki a külső kivételkezelőhöz, az eredeti pedig elvész.

A try ... finally utasítás try részéből bármikor átadhatjuk a vezérlést a finally résznek a szabályos Exit, Break vagy Continue eljárások hívásával. Ezen függvények használhatók a kivételkezelőből való kilépésre is, ekkor a kivétel objektum automatikusan megszűnik.

Előre definiált kivételek

Az előre definiált kivételosztályokat a SysUtils modulban találhatjuk az Exception osztállyal együtt, amely minden más kivételosztály alaposztálya. Az Exception osztály két tulajdonságát (property), a Message-et és a HelpContextet minden kivételosztály örökli. Ezért minden kivételobjektum rendelkezhet hibaállapotot leíró üzenettel és a megfelelő súgófejezetre hivatkozó azonosítóval. Az Exception osztálynak több konstruktora is van, amelyek különböző lehetőségeket kínálnak a Message és a HelpContext inicializálására. Ezek a konstruktorok:

Create, CreateFmt, CreateRes, CreateResFmt, CreateHelp, CreateFmtHelp, CreateResHelp, CreateResFmtHelp.

A következő táblázatban szerepelnek a SysUtils modulban definiált kivételek, és az őket kiváltó állapotok:

EAbort

Abort() utasítás által kiváltott kivétel.

EAbstractError

Absztrakt metódus hívása által kiváltott kivétel.

EAccessViolation

Érvénytelen memóriacímre való hivatkozás által kiváltott kivétel.

EAssertionFailed

Assert eljárás false paraméterrel való hívása által kiváltott kivétel, ha a $ASSERTIONS ON érvényben van.

EControlC

Konzol alkalmazás futása közben ütött Ctrl+C által kiváltott kivétel.

EConvertError

Konverziós függvény végrehajtása során fellépő hiba által kiváltott kivétel.

EDivByZero

Egész számok nullával való osztása által kiváltott kivétel.

EExternal

Az összes Win32 rendszerre épülő kivétel ősosztálya.

EExternalException

Az EExternal leszármazottja, akkor lép fel, ha egy másik kivétel kivételkódja érvénytelen.

EHeapException

A heap memória használata során fellépő hibák által kiváltott kivétel.

EInOutError

Háttértáron lévő állományok I/O műveleteinek hibái által kiváltott kivétel.

EIntError

Aritmetikai műveletek kivételeinek őse.

EIntfCastError

Hibás interface-konverzió által kiváltott kivétel.

EIntOverflow

Egész számok túlcsordulása által kiváltott kivétel. Csak ha $Q+ érvényben van.

EInvalidCast

Az as operátor jobb és bal oldalán álló objektumok típusainak meg nem egyezése által kiváltott kivétel.

EInvalidOp

Érvénytelen matematikai művelet (pl. négyzetgyök negatív számból) által kiváltott kivétel.

EInvalidOpCode

CPU-nak küldött érvénytelen utasítás által kiváltott kivétel.

EInvalidPointer

Érvénytelen mutató által mutatott memóriaterület felszabadítása által kiváltott kivétel.

EMathError

Lebegőpontos matematikai műveletek kivételeinek ősosztálya.

EOutOfMemory

Egy adott művelethez nem elegendő szabad memóriaterület esetén kiváltott kivétel.

EOverflow

Lebegőpontos művelet túlcsordulása által kiváltott kivétel.

EPackageError

Csomagokkal kapcsolatos hibák által kiváltott kivétel.

EPrivilege

Processzor védelmi rendszerének megsértése által kiváltott kivétel.

ERangeError

Intervallumellenőrzési hiba által kiváltott kivétel. Csak $R+ érvényessége esetén.

EStackOverflow

Stack elfogyása által kiváltott kivétel.

EUnderflow

Lebegőpontos műveletek alulcsordulása által kiváltott kivétel.

EVariantError

Variáns típus hibái által kiváltott kivétel.

EWin32Error

Windows hibák által kiváltott kivétel.

EZeroDivide

Nullával való lebegőpontos osztás által kiváltott kivétel.

A SysUtils modulban a következő, kivételkezelést támogató eljárások és függvények találhatók: