AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Delphi Tutorial: Exceptions
Tutorial durchsuchen
Ansicht
Themen-Optionen

Tutorial: Exceptions

Ein Tutorial von madshi · begonnen am 21. Mär 2005 · letzter Beitrag vom 22. Mär 2005
Tutorial geschlossen
madshi
Registriert seit: 21. Mär 2005
Was sind Exceptions?

Das Wort "Exception" heißt übersetzt "Ausnahme". Man könnte sagen, wenn eine Exception auftritt, hat man einen "Ausnahmezustand". In der Regel deutet eine Exception darauf hin, daß etwas ziemlich schief gegangen ist.

Wie können Exceptions entstehen?

Es gibt grundsätzlich zwei verschiedene Möglichkeiten, wie Exceptions entstehen können:
  • Vom Betriebssystem ausgelöste Exceptions. Zum Beispiel durch Schreibzugriff auf einen geschützten Speicherbereich:

    integer(nil^) := 0; Als Ergebnis bekommen wir eine "Zugriffsverletzung", in Englisch eine "Access Violation", oft auch als "AV" abgekürzt. Die entsprechende von Delphi gezeigte Meldung sieht dann normalerweise so aus:



    Andere Beispiele für vom Betriebssystem ausgelöste Exceptions sind zum Beispiel Stacküberläufe ("Stack overflow") oder Division durch Null.
  • Man kann Exceptions auch manuell auslösen. Vom Quellcode her funktioniert es wie folgt:

    raise Exception.Create('Beispiel-Exception'); Delphi zeigt das normalerweise dann so:



    Warum das manuelle Auslösen von Exceptions Sinn machen kann, wird später noch erklärt.
Was passiert mit dem Programm, wenn eine Exception auftritt?

Wenn eine Exception ausgelöst wird, sucht das Betriebssystem nach einem Exception-Handler. Das heißt, das Betriebssystem sucht nach jemandem, der sich für die Exception zuständig fühlt und sich um das Problem kümmert.

In den Tiefen des Delphi-Quellcodes sind entsprechende Exception-Handler eingebaut, die die meisten anfallenden Exceptions abfangen und dann versuchen, sinnvoll zu reagieren. Im Normalfall zeigen die Delphi-Exception-Handler eine kleine Box an (siehe Screenshots oben) und führen dann den normalen Programmverlauf fort.

Es gibt aber einige Situationen, in denen das nicht so gut klappt, wie gewohnt. In diesen Fällen kommen dann weniger aussagekräftige Boxen wie z.B. diese hier:



Oder in noch schlimmeren Fällen kann es dazu kommen, daß ein Thread einfach stillschweigend beendet wird. Oder daß Windows selbst (bzw Dr. Watson) sich über das abgestürzte Programm beschwert. In Windows 9x kann es in Ausnahmefällen nach Exceptions sogar mal zu blauen Bildschirmen kommen.

Wieso klappt das behandeln von Exceptions manchmal besser und manchmal schlechter? Diese Frage werden wir uns später nochmal stellen. Im Moment wissen wir noch nicht genug, um das zu verstehen.

Wir können wir selbst mit aufgetretenen Exceptions umgehen?

Manchmal weiß man (oder befürchtet man), daß ein bestimmter Code eine Exception auslösen kann. In solchen Fällen kann es erwünscht sein, die mögliche Exception selbst abzufangen und entsprechend zu reagieren. Hier ein kleines Beispiel dafür:

Delphi-Quellcode:
function CreateEmptyFile(fileName: string) : boolean;
begin
  try
    TFileStream.Create(fileName, fmCreate).Free;
    result := true;
  except
    result := false;
  end;
end;
Wenn innerhalb eines "try..except"-Blocks eine Exception auftritt, wird diese abgefangen und der Code im "except..end"-Block wird ausgeführt. Das Programm wird direkt nach dem "except..end"-Block fortgesetzt. Die Exception selbst wird in dem Moment aufgelöst. Falls keine Exception auftritt, wird der Code im "except..end"-Block *nicht* ausgeführt.

Wichtig zu beachten ist, daß beim Auftreten einer Exception das Betriebssystem nach so einem "try..except"-Block sucht. Die aufgerufenen Funktionen werden dabei rückwärts durchgegangen, solange bis endlich ein "try..except"-Block gefunden wurde. Zur Verdeutlichung ein weiteres Beispiel:

Delphi-Quellcode:
procedure Function3;
begin
  ShowMessage('3.1');
  if Random(10) = 5 then
    raise Exception.Create('Zufall!');
  ShowMessage('3.2');
end;

procedure Function2;
begin
  ShowMessage('2.1');
  Function3;
  ShowMessage('2.2');
end;

procedure Function1;
begin
  ShowMessage('1.1');
  try
    ShowMessage('1.2');
    Function2;
    ShowMessage('1.3');
  except
    ShowMessage('1.4');
  end;
  ShowMessage('1.5');
end;
Was passiert in diesem Beispiel, wenn *keine* Exception ausgelöst wird? Die "except..end"-Blöcke werden in dem Fall nicht durchlaufen. Also würden wir die Meldungen in folgender Reihenfolge sehen: 1.1, 1.2, 2.1, 3.1, 3.2, 2.2, 1.3 und 1.5.

Was passiert, wenn eine Exception ausgelöst wird? In dem Fall bekommen wir 1.1, 1.2, 2.1, 3.1, 1.4 und 1.5. Im dem Moment wo die Exception ausgelöst wird, sucht das Betriebssystem nach dem nächst gelegenen "try..except" Block. Solange keiner gefunden wird, werden alle aufgerufenen Funktionen (also hier Function3 und Function2) sofort verlassen. Sobald ein "try..except"-Block gefunden wird, wird der entsprechende "except..end"-Block ausgeführt. Das Programm wird dann direkt nach diesem Block fortgeführt.

In bestimmten Ausnahmefällen kann es für ein Programm Sinn machen, die Ausführung einer bestimmten Kette von Funktionen sofort zu unterbrechen und direkt an eine gewünschte Code-Stelle in einer tiefer liegenden Funktion zurückzuspringen. Solche Fälle kann man lösen, indem man manuell eine Exception auslöst und dann an der gewünschten Stelle abfängt.

Was passiert mit Exceptions, die ich selbst nicht behandle?

Die meisten Exceptions passieren während der Phase eines Programmes, wo das Hauptformular aktiv ist. Diese Phase wird durch "Application.Run" in der Projekt-Datei (*.dpr) gestartet. Delphi hat in neueren Versionen die Haupt-Message-Behandlungs-Schleife in "Application.Run" durch einen "try..except"-Block geschützt. Das heißt, die meisten unbehandelten Exceptions enden in diesem "try..except"-Block. Delphi zeigt dann eine einfache Box an mit einer kleinen Beschreibung der Exception. Nachher läuft die Message-Schleife in "Application.Run" einfach weiter.

Wieso kommt Delphi mit manchen Exceptions nicht klar?

Es gibt Fälle, in denen Delphi nur einen "Runtime error xxx" anzeigt, statt eine vernünftige Meldung zu bringen. Manchmal fängt Delphi eine Exception auch gar nicht ab, so daß Windows selbst die Exception-Behandlung übernimmt. In beiden Fällen ist eine vernünftige Fortführung des Programmes nicht mehr möglich. Wie kommt es zu diesen Situationen?

Die Antwort ist relativ einfach: Exceptions, die außerhalb von "Application.Run" auftreten, sind nicht mehr durch den "try..except"-Block in "Application.Run" geschützt. Delphi fängt manche dieser Exceptions dann in einer noch tieferen Ebene ab. Das funktioniert in etwa so:

Delphi-Quellcode:
program blabla;

begin
  try
    Application.Initialize;
    Application.CreateForm(...);
    Application.Run;
  except
    ShowMessage('Runtime error xxx');
  end;
  ExitProcess(0);
end.
Wie man deutlich erkennen kann, gibt es nach dem Anzeigen einer "Runtime error xxx"-Meldung gar keine andere Möglichkeit mehr, als das Programm sofort zu beenden.

Eine weitere Art von Exception, die Delphi oft nicht abfängt, sind Exceptions in Threads. In neueren Delphi-Versionen sind TThread-Threads intern durch einen "try..except"-Block geschützt. Abgefangene Exceptions werden dann zum Hauptthread geschickt, während der TThread einfach beendet wird. In D5 war es noch so, daß der TThread einfach kommentarlos beendet wurde und die aufgetretene Exception totgeschwiegen wurde. In D4 wurden Exceptions in TThread gar nicht abgefangen, sondern Windows (bzw Dr. Watson) meldete sich dann und wollte das Programm beenden.

Threads, die nicht über TThread, sondern direkt über CreateThread erzeugt werden, sind auch in neusten Delphi-Versionen nicht automatisch durch einen "try..except"-Block geschützt. Falls in so einem Thread eine Exception auftritt, meldet sich wiederum Windows/Dr. Watson und möchte das ganze Programm beenden.

Was mache ich, wenn mein Programm bei jemand anderem abstürzt - aber nicht bei mir?

Die meisten Programmierer kennen wahrscheinlich das Problem: Auf dem eigenen PC läuft das selbst geschriebene Programm problemlos. Aber auf dem PC eines anderen stürzt es ständig ab. Und meistens wohnt dieser andere dann irgendwo am anderen Ende der Welt, so daß eine direkte Untersuchung des Problems vor Ort nicht möglich ist. Was kann man dann tun?

Abstürze in Windows-Programmen sind meistens mit Exceptions gleichzusetzen. Das heißt, wenn ein Programm abstürzt, liegt meistens eine Exception vor. Was wir nun brauchen, um den Abstürzen auf den Grund zu gehen, sind mehr (viel mehr) Informationen darüber, was genau passiert ist. Die mit Abstand wichtigste Information ist: An welcher Stelle im Quellcode ist die Exception genau ausgelöst worden? Die zweitwichtigste Information ist wahrscheinlich: Über welchen Weg ist das Programm an diese Stelle gekommen? Beides sind Informationen, die uns Delphi leider nicht gibt.

Bei Zugriffsverletzungen (aber auch nur da) meldet uns Delphi eine Code-Adresse, die wir mit etwas Aufwand so interpretieren können, daß wir die Stelle im Quellcode finden, die die Zugriffsverletzung ausgelöst hat. Das klappt allerdings nur, wenn wir genau (100%ig, ohne auch nur die geringste Abweichung) die gleichen Quellen und die gleichen Versionen aller Drittanbieter-Komponenten zur Verfügung haben, mit denen das abgestürzte Programm kompiliert wurde. Und selbst dann ist es schwierig genug, die Stelle im Quellcode zu finden.

An dieser Stelle darf ich (mit Erlaubnis der Forum-Verantwortlichen etwas Werbung für meine "madExcept"-Komponente machen.

Was ist madExcept und was macht es?

madExcept integriert sich automatisch in jedes Delphi-Programm (nur win32, nicht DotNet) und übernimmt die komplette Exception-Verarbeitung von Delphi. Dabei werden folgende Verbesserungen erzielt:
  • Zu jeder Exception werden detaillierte Informationen über die Code-Stelle, wo die Exception ausgelöst wurde, ermittelt.
  • Der ganze Weg ("Callstack"), wie das Programm zur Absturz-Stelle gekommen ist, wird ermittelt.
  • Sämtliche noch laufenden Threads werden aufgelistet - mit komplettem Callstack (also womit sie gerade beschäftigt sind).
  • Haufenweise zusätzliche Informationen (geladene Dlls, Speicherverbrauch, Betriebssystem, Sprache, Hardware, usw) werden gesammelt.
  • Alle Exceptions aller Threads werden automatisch mit abgefangen.
  • Auch Exceptions außerhalb von "Application.Run" werden mit vollständigem Funktionsumfang behandelt. Keine "Runtime error xxx"-Meldungen mehr.
  • Die ganzen Informationen werden in Form eines Fehlerberichts aufbereitet und können samt optionalem Screenshot direkt an den verantwortlichen Programmierer gemailt werden. Die entsprechenden Mail-Routinen usw sind alle bereits in madExcept enthalten.
Wie kann ich madExcept in meine Anwendung einbinden?

Zuerst einmal muß der Installer her:

http://madshi.net/madCollection.exe

Nach der Installation von madExcept bitte Delphi starten und das gewünschte Projekt laden. Im Delphis "Projekt"-Menü gibt es jetzt einen neuen Eintrag:



Diesen Eintrag bitte anklicken. Es erscheint folgendes Bild:



Bitte dort die beiden Haken "handle exceptions" und "append map file to binary" anklicken. Danach das Projekt neu kompilieren - fertig!

Wie wirkt sich madExcept zur Laufzeit aus?

Erst einmal merkt man keinen Unterschied, da madExcept nur dann eingreift, wenn wirklich eine Exception auftritt. Nehmen wir an, wir drücken auf den "Button1" bei folgendem Code:

Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
begin
  integer(nil^) := 0;
end;
Innerhalb der Delphi IDE meldet sich dann erst den Debugger. Bitte mit F9 das Programm weiter laufen lassen. Dann kommt die madExcept Exception-Box. Außerhalb der Delphi IDE kommt sofort die madExcept Exception-Box. Das sieht dann so aus:



Wem die englischen Texte nicht gefallen, kann das ganze im (weiter oben schon gezeigten) Einstellungsfenster eindeutschen. Der Endbenutzer kann jetzt die Anwendung fortführen, neu starten oder beenden. Außerdem kann er sich einen detaillierten Fehlerbericht anzeigen lassen oder den Fehlerbericht an den Programmierer mailen. Der Fehlerbericht sieht dann wie folgt aus (stark gekürzte Fassung):

Code:
physical memory  : 717/1022 MB (free/total)
free disk space  : (C:) 68.54 GB
allocated memory : 3.44 MB
executable       : Project1.exe
exception class  : EAccessViolation
exception message : Zugriffsverletzung bei Adresse 00472910 in Modul 'Project1.exe'. Schreiben von Adresse 00000000.

main thread ($5d4):
00472910 Project1.exe Unit1    28 TForm1.Button1Click
00471302 Project1.exe Forms      TApplication.Run
00472be7 Project1.exe Project1 14 initialization

disassembling:
0047290c   public TForm1.Button1Click:     ; function entry point
0047290c 28   xor    eax, eax
0047290e     xor    edx, edx
00472910    > mov    [eax], edx
00472912 29   ret
Den vollständige Fehlerbericht habe ich als Attachment angehängt (siehe bugReport.txt). Die einzelnen Bestandteile des Fehlerberichts lassen sich auf Wunsch auch ausschalten oder nach Wunsch einstellen.

Was kostet madExcept und wo finde ich mehr Informationen?

madExcept ist kostenlos für nicht-kommerzielle Zwecke. Für kommerzielle Zwecke kostet es USD 50 pro Programmierer. Die vollständige Dokumentation ist auch online zugänglich, und zwar hier:

http://help.madshi.net/madExcept.htm

[edit=sakura] Schönheitsfehler beseitigt. Mfg, sakura[/edit]
Angehängte Dateien
Dateityp: txt bugreport_211.txt (7,0 KB, 159x aufgerufen)
 
Benutzerbild von sakura
sakura

 
Delphi 11 Alexandria
 
#2
  Alt 22. Mär 2005, 07:06
Wenn jemand Fragen hierzu hat, so bitte einen neuen Thread aufmachen, welchen ich dann von hier verlinke. Mir ggf. eine kurze PM senden

......
Daniel W.
 
Tutorial geschlossen


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 15:30 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz