Hallo,
im Hochsprachenkonstrukt try / catch kann man durchaus sagen, dass die Exception irgendwo geworfen und von den catch-Blöcken gefangen wird. Je nachdem, was die Catch-Blöcke dann tun, wird die Exception verschluckt, weitergeworfen (rethrow) oder durch eine neu geworfe Exception ersetzt.
Kurzer Regress zu dem, was ich über Perl gelernt habe: Wenn ich in Perl Exceptions fangen will, implementiere ich das so, dass ich den Code in eine eval-Klammer lege. Die Eval-Klammer fängt die Exception und stellt mir den geworfenen Wert in $@ bereit. Hier ist die Aufgabenteilung also anders: eval() macht die ganze Arbeit und danach kann der Programmierer die Trümmer begutachten.
Aber - das ist alles Highlevel und eine Abstraktion dessen, was auf Betriebssystemebene passiert. Schon das Wort Betriebssystem lässt erkennen, dass es jetzt spezifisch wird - jedes Betriebssystem anders. Übrigens sind 32-bit Windows und 64-bit Windows zwei unterschiedliche Betriebssysteme!
Win32: Jeder Windows Thread verfügt über einen Thread-Informationsblock (TIB), der an der Adresse FS:0 zu finden ist (ja, die guten alten Segmentregister). Der erste Eintrag des TIB ist ein Zeiger auf eine EXCEPTION_REGISTRATION Struktur aus zwei DWORDs: Ein Previous-Pointer (auf eine ältere Exception-Registration) und eine Prozedur-Adresse. Diese Struktur liegt nicht irgendwo, sondern auf dem Stack, im Stackframe der Procedure die einen try-Block aufbauen will. Wenn eine Exception geworfen wird (Win23 API Funktion), folgt Windows der Liste der registrierten Exceptionhandler und ruft den Callback auf. Der bekommt einige Informationen zur Exception und kann sagen: "Kann ich behandeln" oder "Lass fliegen". Sagt er "kann ich behandeln", wird die Handlerkette nochmal aufgerufen, diesmal mit einem "Unwind" Parameter. Das ist die Aufforderung für die Handler, eventuell fällige Aufräumarbeiten durchzuführen. Der Handler, der "kann ich behandeln" sagt, legt dann fest, wo die Programmausführung weitergeht und wie der Stackpointer zu justieren ist, damit der Code so weiterlaufen kann als wäre nichts geschehen. Das ist nicht trivial, ich habe es auch noch nie selbst gemacht, Microsoft hat es mies dokumentiert und die beiden Texte im Netz, die dazu was beschreiben, habe ich nur partiell verstanden 😀.
Win64: funktioniert ganz anders, da wird nichts auf den Stack gelegt. Weil - ein Buffer Overrun könnte das manipulieren oder zerstören. Statt dessen erzeugt man im EXE eine spezielle Steuertabelle, in der steht, in welchem Programmbereich welche Exceptionregeln gelten sollen (ich habe aufgehört, das genauer durchdringen zu wollen) und Win64 baut daraus den Stack Unwind selbsttätig zusammen. Vorteil ist, dass man Null Overhead hat um einen try-Block zu markieren. Ob man in einem try-Block ist, wird erst geprüft wenn die Exception fliegt.
Außer dem Structured Exception Handling (das es schon in Win95 gab) existiert seit WinXP noch Vectored Exception Handling - damit kann man einen zentralen Handler für alle Exception hinterlegen.
All das sind Informationen für Compilerbauer und Assemblerprogrammierer. Microsoft dokumentiert das deshalb so schlecht, weil man das gar nicht selbst nutzen soll. Auf Assemblerebene ist das alles eher simpel und frei von OOP: Man registriert einen Exception Handler, führt Code aus und nimmt den Handler wieder weg. Falls Betriebssystem oder eigener Code entscheiden, dass jetzt eine Exception fällig ist, wird RaiseException() aufgerufen und diese Funktion bekommt einen Exception Code, ein paar Flags und eine Liste von frei definierten Parametern übergeben. Im Handler kann man Code, Flags und Parameter abfragen. Die Laufzeitumgebung einer Hochsprache fügt hier ihre eigene Logik ein, um Destruktoren von Objekten auf dem Stack aufzurufen und Dinge wie Exception-Objekte zu erzeugen. Damit so etwa wie ein sauberer Stacktrace möglich ist, braucht eine Hochsprache außer der EXCEPTION_REGISTRATION noch ein paar Informationen mehr, um zu wissen wo genau der Stackframe beginnt, so dass außer dem Handler-Walk auch ein Stack-Walk möglich ist um den Stacktrace aufzubauen.
Und was machen Perl oder PHP? Die compilieren ihre Programme nicht in Maschinencode. Sie übersetzen Sourcecode in einen Zwischencode und interpretieren den. Wie von PHP ein try-Block aufgebaut wird, habe ich allerdings nicht durchschaut, es gibt in der ZEND-Engine keinen Opcode dafür (nur für throw und catch). Im PHP Wiki ist das auch nicht dokumentiert. Es ist hier also schwierig für jemanden, der in der ZEND Engine die Bits nicht beim Vornamen kennt, hier durchzublicken. Ich würde allerdings vermuten, dass PHP auch in der Windows Implementierung nicht das SEH von Windows verwendet, sondern sein eigenes Süppchen kocht.
Rolf
sumpsi - posui - clusi