ðHgeocities.com/Vienna/Stage/4793/excp6.htmgeocities.com/Vienna/Stage/4793/excp6.htmdelayedxØÕJÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈ0¤£Ð1OKtext/htmlÏBmoÐ1ÿÿÿÿb‰.HMon, 18 Dec 2000 15:42:06 GMTæMozilla/4.5 (compatible; HTTrack 3.0x; Windows 98)en, *ØÕJÐ1 SEH

Zurück ] Nach oben ] Weiter ]

 

SEH - Structured Exception Handling

SEH ist ein Satz von Funktionen, die Windows, also das Betriebssystem, bereitstellt. In Visual C++ werden diese Funktionen und Services in nicht standardisierten Schlüsselwörtern bzw. Routinen gekapselt. SEH ist also nicht auf C++ beschränkt, sondern kann über die WinAPI angesprochen werden. Doch hier beschränken wir uns auf die Implementation im VC++.

  • __except

  • __finally

  • __leave

  • __try

Um diese Spracherweiterung nutzen zu können, muß man unter Projekt Settings unter C++/Customize den Schalter "Disable Lanugage Extensions" ausgeschaltet lassen.

Weiterhin bietet Microsoft das Headerfile excpt.h, welches von windows.h einbezogen wird. Dort sind interne als auch folgende Definitionen zu finden:

  • Makros für die Filterwerte bei __except

  • Makros, die die Win32 Funktionen kapseln und Funktionen, die Statusinformationen liefern

  • Pseudo-Schlüsselwörter für die Spracherweiterung ohne die zwei Underscores. Also leave wird zu __leave. Hier ergibt sich das Problem, daß auch __try als Makro try zur Verfügung steht. Man kann also nicht problemlos Standard C++ Exceptionhandling mit SEH vermischen[1].

Syntax

Die Hauptstruktur innerhalb SEH ist der try-Block. Er kann wie folgt aussehen:

__try { statements } handler

wobei handler entweder

__except (filterexpression) { statements } // exception handler

oder

__finally { statements } // termination handler

sein kann.

Um einen try-Block zu verlassen kann man folgendes kodieren:

__try {
    ...
    __leave;
    ...
    }

Hinweise:

  • Es darf genau nur ein Handler in einem gegebenen try-Block verwendet werden.

  • Alle Anweisungen müssen von geschweiften Klammen umgeben sein, auch einzelne Anweisungen.

  • Die filterexpression sollte vom Typ int sein.

Semantik

Die fünf Phasen einer Exception werden mit SEH wie folgt abgebildet.

  • Das Betriebsystem bzw. der Programmierer findet einen Hardwarefehler bzw. einen Softwarefehler (Stufe 1)

  • Das Betriebssystem, bzw. der User mit der Funktion RaiseException(), erzeugt und wirft ein Exceptionobjekt (Stufe 2). Dieses Objekt ist eine interne Struktur, die in Exceptionhandlern verfügbar ist.

  • Solch ein Handler kann diese Struktur "sehen" und hat die Möglichkeit sie zu bearbeiten (Stufe 3 und 4). Abhängig vom Handler, ist die Exception resuming bzw. terminating (Stufe5).

Eine Exception wird vom User wie folgt geworfen:

VOID RaiseException( DWORD dwExceptionCode,       // exception code
    DWORD dwExceptionFlags,       // continuable exception flag
    DWORD nNumberOfArguments,       // number of arguments in array
    CONST DWORD *lpArguments       // address of array of arguments
);

dwExceptionCode

Hier kann ein selbstdefinierter Exceptioncode übergeben werden.. Innerhalb des exception blocks des Handlers kann auf dieser Code mit der Funktion GetExceptionCode() abgefragt werden..

Hinweis: Das System löscht das 28. Bit von dwExceptionCode. Dieses Bit ist ein reserviertes Exceptionbit, welches das System für eigene Zwecke benutzt. Zum Beispiel würde dwExceptionCode mit dem Wert 0xFFFFFFFF nach dem Aufruf von RaiseException eine Meldung liefern, daß der Wert nun 0xEFFFFFFF ist.

dwExceptionFlags

Spezifiziert die Exception Flags. Der Wert Null bezeichnet eine fortführbare Exception bzw. EXCEPTION_NONCONTINUABLE für eine nichtfortführbare. Versucht man bei einer nichtfortführbaren Exception trotzdem die Fortführung, kriegt man eine EXCEPTION_NONCONTINUABLE_EXCEPTION um die Ohren gehauen.

nNumberOfArguments

Die Anzahl der Argumente im  lpArguments Array. Die Anzahl darf EXCEPTION_MAXIMUM_PARAMETERS nicht übersteigen. Der Parameter wird ignoriert, wenn lpArguments NULL ist.

lpArguments

Pointer auf ein Array von 32-bit Argumenten. Dieser Parameter kann NULL sein. Diese Argumente können userspezifische Daten enthalten, um im Exceptionhandler korrekt verarbeitet werden zu können.

Exceptionhandling und Filter

Sobald eine Exception geworfen bzw. "geraised" wird, werden alle Exceptionhandler, also die Handler mit __except, den Callerstack hinauf gesucht und deren Filterwert geprüft. Was passiert, ist vom filter value abhängig.

<excpt.h> definiert drei filter values:

EXCEPTION_CONTINUE_EXECUTION= -1 Dies stellt eine resuming exception dar. Die Anweisung, die die Exception erzeugt hat, wird neu aufgerufen.
EXCEPTION_CONTINUE_SEARCH= 0 Der Handler weist jegliche Verantwortung zurück und leitet die Exception unbehandelt weiter.
EXCEPTION_EXECUTE_HANDLER= 1 Dies stellt eine terminating exception dar. Das stack unwinding wird bis zu diesem Handler durchgeführt, der Handler bearbeitet und das Programm macht nach dem TRY-Block des betreffenden Handlers weiter.

Hinweis: EXCEPTION_CONTINUE_EXECUTION sollte vorsichtig verwendet werden!

Auch wenn das Aufsetzen an der Stelle, an der die Exception auftrat, ein nettes Feature ist, sollte man es nicht ohne folgende Warnung benutzen.

Abhängig vom Compiler könnte es sein, daß z.B. für die Anweisung *lpBuffer = 'J'; zwei Maschinen­befehle generiert werden. Die erste lädt den Inhalt von lpBuffer in ein Register, und die zweite versucht, ein 'J' in die Adresse zu schreiben. Die zweite Anweisung löst nun die Ausnahme aus. Der dafür vorgesehene Filter fängt diese Ausnahme ab, korrigiert den Wert in lpBuffer und weist das System an, die zweite Anweisung zu wiederholen.

Das Problem hier ist, daß der Inhalt des Registers nicht mit dem neuen Wert aktualisiert wird und das erneute Ausführen der Anweisung damit wieder eine Ausnahme auslöst. Moderne Compiler werden dieses Problem handlen können, aber trotzdem kann es sein, daß man bei gewissen unerklärbaren Fehlern bis in den Assemblercode schauen muß, um diese zu erkennen.

Terminationhandling

Wie es der Name schon sagt, werden Terminationhandler, also die mit dem Schlüsselwort __finally, gerufen, wenn eine termination exception geworfen wird. Ihre Rolle ist hauptsächlich das Aufräumen und sie könnten mit dem Destruktorcode eines Objekts verglichen werden. Dieser Vergleich hinkt aber, da diese Handler nicht nur im Falle einer Exception, sondern auch im normalen Programmverlauf verarbeitet werden.

Dieser Umstand bedeutet auch, daß so ein Handler nicht unbedingt weiß, ob er aufgrund einer geworfenen exception bzw. einer korrekten Verarbeitung durchlaufen wird. Um zu bestimmen, warum diese Funktion gerufen wurde, gibt es die Funktion AbnormalTermination(). Diese liefert TRUE, wenn keine Exception geworfen wurde, sonst FALSE.

Hinweis: Diese Funktion kann ausschließlich im Handler benutzt werden und nicht in einer Funktion, die in dem Handler gerufen wird.

 
[1] Dies führt bei SEH-Verfechtern zu Verwirrung, während die andere Alternative Standard C++ Programmierern Kopfzerbrechen bereitet, da sie hinter try ihr C++ Schlüsselwort erwarten. Deswegen wird angeraten, wenn man dieses Headerfile nutzt, #undef try zu kodieren und statt den Pseudoschlüsselwörtern die tatsächlichen Schlüsselwörter zu nutzen (also __try statt try).