Behandlung der von Visual Basic nicht behandelten Ausnahmen
Einleitung
Visual Basic erlaubt zwar die Behandlung von Laufzeitfehlern, einige Fehler werden aber von der Visual-Basic-eigenen Fehlererkennung nicht automatisch in das Gewand eines Visual-Basic-Fehlers gekleidet. In diesem Artikel wird beschrieben, wie man diese Fehler trotzdem behandeln und damit unter Umständen den unkontrollierten Absturz der Anwendung verhindern kann. In den meisten Projekten sind die vorgestellten Techniken nicht erforderlich, da die Fehlerbehandlung von Visual Basic ausreichend ist.
Fehlerbehandlung in Visual Basic
Visual Basic besitzt einen Mechanismus zur Fehlerbehandlung, der als Frame Based Exception Handling bezeichnet wird. Dabei bestehen Prozeduren aus der eigentlichen Implementierung der Funktionalität und Bereichen, in denen die Fehlerbehandlung durchgeführt wird. Die Fehlerbehandlung erfolgt über On Error GoTo…
. Mittels On Error Resume Next
können Laufzeitfehler unterdrückt und das Beenden der Anwendung mit einer Fehlermeldung unterbunden werden. Durch das Behandeln von Laufzeitfehlern kann eine Anwendung mit einem definierten Zustand beendet werden.
Auch wenn der Mechanismus von Visual Basic bei einfachen Anwendungen, die ohne die Benutzung von Win32-API-Funktionen auskommen, ausreicht, gibt es Laufzeitfehler, die nicht von Visual Basic selbst in einen Visual-Basic-Fehler verpackt werden. Sie überraschen den Benutzer der Anwendung unliebsam mit einer vom Betriebssystem angezeigten Fehlermeldung. Diese Fehler, beispielsweise Zugriffskonflikte, können auch nicht mit den vorgestellten Methoden zur Fehlerbehandlung unterdrückt oder behandelt werden.
Insbesondere bei Verwendung von Win32-API-Funktionen kommt es durch unsachgemäße Anwendung zu Laufzeitfehlern, meist sogenannte Schutzverletzungen (engl.: protection faults). Ein Beispiel dafür ist die Verwendung der Funktion CopyMemory
zum Kopieren eines bestimmten Speicherbereichs an einen anderen. Fehlen der Anwendung die Rechte, um auf die betreffenden Bereiche zuzugreifen, wird eine Schutzverletzung ausgelöst. Im Sinne des Benutzers wäre es wünschenswert, wenn in solchen Fällen das Programm vor dem Beenden Aufräumarbeiten wie das Speichern von Änderungen in eine Sicherungsdatei durchführen könnte.
Laufzeitfehler in Visual-Basic-Anwendungen können in folgende Kategorien eingeteilt werden:
-
Behandelte Visual-Basic-eigene Laufzeitfehler. Bei Auftreten dieser Fehler kann die Ausführung der Anwendung fortgesetzt werden.
-
Unbehandelte Visual-Basic-eigene Laufzeitfehler. Die Anwendung bricht mit einer Visual-Basic-eigenen Fehlermeldung ab und kann keinen Code zum Aufräumen ausführen.
-
Von Visual Basic nicht behandelte Laufzeitfehler. Die Anwendung bricht mit einer Windows-eigenen Fehlermeldung ab und kann keine Aufräumarbeiten erledigen.
In der Praxis sind besonders Fehler der Typen 2 und 3 störend. Fehlertyp 2 kann vermieden werden, indem entsprechende Fehlerbehandlungsroutinen dem Quellcode hinzugefügt werden, beispielsweise On Error GoTo…
und On Error Resume Next
. Um Fehlertyp 3 in eine handhabbare Form überzuführen, muß man auf die Win32-API zurückgreifen.
Laufzeitfehler unter Microsoft Windows
Auslösen von Laufzeitfehlern
In Windows werden Fehler entweder von der Hardware direkt oder über die Funktion RaiseException
, die in der kernel32.dll
enthalten ist, ausgelöst. Die Deklaration dieser Funktion für Visual Basic ist im Folgenden angegeben:
Im ersten Parameter der Funktion RaiseException
, dwExceptionCode
wird die Nummer der auszulösenden Ausnahme angegeben. Will man in der eigenen Anwendung solche Ausnahmen werfen, sollte man als Nummer eine selbst gewählte Zahl festlegen. Bestimmte Werte sind bereits für Windows-eigene Fehler vorbelegt. Die entsprechenden Werte kann man der Headerdatei WinBase.h
entnehmen (Definitionen für EXCEPTION_ACCESS_VIOLATION
etc.). Der Parameter dwExceptionflags
kann entweder den Wert der Konstanten EXCEPTION_NONCONTINUABLE
(= 1) annehmen, der besagt, daß die Anwendung nach Eintreten der Ausnahme beendet werden muß. Wird stattdessen der Wert 0 angegeben, kann die Anwendung die Ausnahme behandeln und muß nicht zwingend beendet werden.
Die meisten „Standardfehler“ in Visual Basic, also beispielsweise Fehler 11 (Division durch Null), stellen nur eine Kapselung der Windows-eigenen Ausnahmen dar. Der nachfolgende Aufruf der API-Funktion RaiseException
führt in Visual Basic zum Auslösen von Laufzeitfehler 11:
Die Laufzeitumgebung für Visual-Basic-Anwendungen filtert bereits einige der Laufzeitfehler und wandelt sie in Visual-Basic-eigene Fehler um. Dies wird jedoch nicht bei allen möglichen Fehlern getan. In Folge werden für nicht von der Laufzeitumgebung umgewandelte Fehler von Visual Basic keine vordefinierten Meldungsfelder anzeigt und die Anwendung mit einer entsprechenden Fehlermeldung von Windows beendet. Das ist beispielsweise bei Ausnahmen des Typs EXCEPTION_ACCESS_VIOLATION
der Fall.
Diese Unzulänglichkeit von Visual Basic kann man jedoch in den Griff bekommen und im Falle einiger Ausnahmen kann die Anwendung sogar weiterlaufen. Dennoch sollte nach Auftreten eines unbehandelten und damit nicht bekannten Fehlers die Anwendung aus Sicherheitsgründen beendet werden, damit der Benutzer nicht aufgrund daraus resultierenden Folgefehlern Daten verliert. Ein Sichern des Programmstatus ist daher in diesem Fall unerläßlich. Die vorgestellte Methode eignet sich auch zur Behandlung von Fehlern, die in Komponenten von Drittanbietern, also Steuerelementen und DLLs, auftreten oder von diesen ausgelöst werden.
Von Visual Basic nicht behandelte Laufzeitfehler behandeln
Um von Visual Basic nicht in das Gewand eines Visual-Basic-Fehlers gekleidete Laufzeitfehler zu behandeln und damit einen Programmabsturz zu verhindern, kann die Win32-Win32-API-Funktion SetUnhandledExceptionFilter
aus der kernel32.dll
herangezogen werden. Diese Funktion ersetzt die durch Windows vorgegebene Standardfehlerbehandlung durch eine Fehlerbehandlungsroutine in der eigenen Anwendung. Standardmäßig zeigt Windows bei Eintreten eines unbehandelten Fehlers, der nicht von Visual Basic erkannt wird, ein Meldungsfeld mit Informationen zum Fehler an, das in Windows XP die Möglichkeit bietet, einen Fehlerbericht an Microsoft zu übermitteln:
SetUnhandledExceptionFilter
erwartet im Parameter lpTopLevelExceptionFilter
einen Zeiger auf eine Prozedur, die folgende Schnittstelle aufweist:
Häufig soll die Anwendung bei Auftreten eines Fehlers weiterlaufen und nicht sofort beendet werden. Zu diesem Zweck muß die Funktion den Wert EXCEPTION_CONTINUE_EXECUTION
zurückgeben. Dies kann durch die Anweisung ExceptionHandler = EXCEPTION_CONTINUE_EXECUTION
erreicht werden. Das Umleiten der Fehlerbehandlung in die Funktion ExceptionHandler
kann folgendermaßen vorgenommen werden:
Der Rückgabewert von SetUnhandledExceptionFilter
ist ein Funktionszeiger auf die Windows-eigene Standardprozedur zur Fehlerbehandlung. Dieser Zeiger wird wieder benötigt, wenn die Fehlerbehandlung an Windows übergeben werden soll.
Damit die Fehlermeldung von Windows nicht angezeigt wird, sollte ein Aufruf der Win32-API-Funktion SetErrorMode
mit SEM_NOOPENFILEERRORBOX
als Wert des ersten Parameters erfolgen. Tests unter Windows XP zeigten, daß dies nicht erforderlich ist – bei Umleitung der Fehler in eine eigene Fehlerbehandlungsroutine zeigte Windows auch keine Fehlermeldungen für die Anwendung an. Die Win32-API-Funktion SetErrorMode
wird folgendermaßen deklariert:
Folgendes Listing zeigt die Deklarationen der für die Fehlerbehandlung nötigen Strukturen. Die benutzerdefinierte Fehlerbehandlungsprozedur, die im Beispiel ExceptionHandler
heißt, bekommt von Windows im Parameter lpException
eine Struktur des Typs EXCEPTION_POINTERS
übergeben, die wiederum Zeiger auf Strukturen der Typen EXCEPTION_RECORD
sowie CONTEXT
enthält. Die Struktur EXCEPTION_RECORD
besteht aus Informationen über den aktuellen Fehler sowie einen Zeiger auf eine weitere EXCEPTION_RECORD
-Struktur, der über das Mitglied pExceptionRecord
zugänglich ist. Die Struktur CONTEXT
ist je nach eingesetztem Prozessor unterschiedlich aufgebaut. Das Aussehen kann den entsprechenden C-Headerdateien entnommen werden:
Für die Fehlerbehandlung sind die Mitglieder der Struktur EXCEPTION_RECORD
von besonderem Interesse, da sie Informationen zum eingetretenen Fehler bereitstellen. Das Mitglied ExceptionCode
enthält den Fehlercode der aufgetretenen Ausnahme. Dabei kann es sich entweder um einen der Standardfehlernummern handeln, die beispielsweise bei Überlauf, Unterlauf und Divison durch Null auftreten, oder um einen benutzerdefinierten Fehler, der beispielsweise in einer in der Anwendung verwendeten ActiveX-Komponente auftritt. ExceptionFlags
bietet Informationen darüber, ob die Anwendung weiter ausgeführt werden kann oder ob ein Beenden unumgänglich ist. Das Mitglied pExceptionRecord
enthält, wie bereits angeführt, einen Zeiger auf eine weitere Fehlerstruktur. Beim Wert in ExceptionAddress
handelt es sich um die Speicheradresse der Anweisung, die den Fehler ausgelöst hat. Die anderen Mitglieder können weiterreichende Informationen enthalten, auf die hier nicht näher eingegangen werden soll.
Besondere Aufmerksamkeit muß den verschachtelten Fehlern gewidmet werden. Im Falle verschachtelter Fehler enthält die im Parameter lpException
der Prozedur ExceptionHandler
übergebene Struktur nicht einen Verweis auf den eigentlichen Fehler, sondern auf einen Fehler, der durch den entstandenen Fehler ausgelöst wurde. Es kann sich dabei um eine ganze Kette von Fehlern handeln, in der jeder Fehler durch eine EXCEPTION_RECORD
-Struktur dargestellt wird. Die einzelnen Fehler sind in Reihenfolge ihres Auftretens über Zeiger im Mitglied pExceptionRecord
miteinander verbunden. Um nun an den ursprünglichen Fehler zu gelangen, muß die einfach verkettete Liste von Fehlern bis an ihr Ende, das eigentlich der Anfang ist (der am weitesten zurückliegende Fehler), vordringen. Dazu kann man eine abgewandelte Form der Win32-API-Funktion RtlMoveMemory
, die auch unter dem Namen CopyMemory
bekannt ist, eingesetzt werden:
Folgendes Listing zeigt, wie der ursprüngliche Fehler ermittelt wird:
Für den praktischen Einsatz der beschriebenen Art der Fehlerbehandlung in Visual-Basic-Anwendungen bestehen zwei Möglichkeiten. So können innerhalb der Fehlerbehandlungsprozedur über die Methode Raise
des Err
-Objekts entsprechende Visual-Basic-eigene Laufzeitfehler ausgelöst werden. Bestehende Fehlerbehandlungsroutinen müssen in diesem Fall nicht erweitert werden, da der Fehler wie jeder andere Visual-Basic-eigene Fehler behandelt werden kann. Für die Beispielanwendung wurde ein anderer Ansatz gewählt: Eine Klasse löst bei Auftreten eines Laufzeitfehlers ein Ereignis aus, in das Informationen über den Fehler übergeben werden. Dadurch besteht bei der Fehlerbehandlung auch Zugriff auf Informationen zur Anweisung, welche den Laufzeitfehler ausgelöst hat.
Schlußwort
Dieser Artikel deckt die für Visual Basic relevanten Bereiche der erweiterten Fehlerbehandlung zu einem großen Teil ab. Zusätzliche Informationen zu eingetretenen Fehlern können mittels eigener Win32-API-Funktionen ermittelt werden. Zu diesem Thema existiert auf der MSDN-Website ein Artikel aus der Artikelreihe Bugslayer, MSJ, August 1998. Darin wird eine in C++ entwickelte DLL vorgestellt, die auch von Visual-Basic-Anwendungen aus genutzt werden kann, um an zusätzliche Informationen zum eingetretenen Fehler zu gelangen.
Weiterführende Informationen
Weitere Informationen zur Behandlung von Laufzeitfehlern, die von Visual Basic nicht behandelt werden, finden sich im Artikel No Exception Errors, My Dear Dr. Watson von Jonathan Lunman, der in der Ausgabe des Visual Basic Programmer’s Journal vom Mai 1999, S. 108, erschienen ist. Außerdem wurden Informationen aus dem in der selben Zeitschrift erschienenen Artikels Swat Tough Bugs von Ken Cowan (Ausgabe Juli 1998, S. 117 ff.) berücksichtigt.
Downloads
- Beispielprojekt (
ExceptionHandler.zip
) Projekt im Visual-Basic-6.0-Format.