═══ 1. OOP Referenz fБr SpeedSoft Sibyl Version 2.0 ═══ This help is incomplete in this version ! It is only available in german for now ! Klassen Sichtbarkeit Instanzen Zuweisungen Felder Methoden Eigenschaften Metaklassen Vordefinierte Klassen Exceptions Vordefinierte Exceptions ═══ 1.1. Klassen ═══ ▄▄▄▄▄▄▄ Klassen ▀▀▀▀▀▀▀ Was sind Klassen? Die Basis der objektorientierten Programmierung ist der Begriff der Klasse. Ein Klasse ist ein strukturierter Datentyp, Дhnlich einem Record, jedoch weitaus mДchtiger. Ein ReprДsentant einer Klasse wird eine Instanz oder ein Objekt der Klasse genannt. Objekte sind dynamische Datentypen, die zur Laufzeit erzeugt und zerstФrt werden. Eine Klasse legt den Bauplan fБr jede ihrer Instanzen fest. Die Deklaration einer neuen Klasse kann drei Arten von Komponenten aufweisen: Felder, Methoden und Eigenschaften. ZusДtzlich kФnnen Klassen Бber den Mechanismus der Vererbung die Komponenten einer bestehenden Klasse Бbernehmen. Jeder Komponente einer Klasse kann individuell ein Typ von Sichtbarkeit zugeordnet werden. Dadurch wird festgelegt, wer die Komponente sehen und darauf zugreifen kann. Deklaration von Klassen Klassen werden auf die folgende Weise deklariert: Klassen kФnnen nur im Дuсersten GБltigkeitsbereich eines Moduls deklariert werden. Es ist nicht mФglich, Klassen lokal in Prozeduren oder Funktionen zu deklarieren. Es ist ebenfalls nicht mФglich, Klassen im Rahmen einer Variablen-Deklaration einzufБhren. Es ist in Speed-Pascal Бblich, wenngleich nicht zwingend vorgeschrieben, daс die Bezeichner von Klassen mit einem 'T' beginnen, um sie als Typbezeichner kenntlich zu machen. Komponenten einer Klasse Die Deklaration einer Klasse kann drei Arten von Komponenten aufweisen: Felder, Methoden und Eigenschaften.  Felder sind Variablen innerhalb einer Klasse und werden auf die gleiche Weise deklariert und benutzt wie die Felder eines Records. Jedes Objekt einer Klasse verfБgt Бber einen kompletten eigenen Satz aller Felder der Klasse.  Methoden sind Prozeduren oder Funktionen, deren FunktionalitДt eng mit der Klasse verknБpft ist. Methoden werden Бblicherweise in Verbindung mit einer Instanz aufgerufen. Alle Instanzen einer Klasse teilen sich den gleichen Satz Methoden, so daс deren Code nur einmal im Programm vorhanden ist.  Eigenschaften stellen sich dem Benutzer einer Klasse Дhnlich wie Felder dar, bieten jedoch weitaus mehr MФglichkeiten. Lesende und schreibende Zugriffe auf eine Eigenschaft kФnnen sowohl auf ein Feld als auch auf eine Methode umgelenkt werden. Im letzteren Fall wird bei der AusfБhrung einer Zuweisung implizit die zugrundeliegende Methode aufgerufen. Auсerdem kФnnen die ZugriffsmФglichkeiten einer Eigenschaft auf nur-Lesen oder nur-Schreiben eingeschrДnkt werden. Vererbung Bei der Deklaration einer neuen Klasse kann eine bereits bestehende Klasse als Vorfahr angegeben werden, wodurch die neue Klasse automatisch Бber sДmtliche Komponenten des Vorfahren verfБgt, sofern es deren Sichtbarkeit zulДсt. ZusДtzlich kФnnen neue Komponenten deklariert oder vom Vorfahren geerbte Komponenten Бberschrieben werden. Ъberschreiben bedeutet, es wird fБr eine Komponente der neuen Klasse ein Bezeichner gewДhlt, der bereits im Vorfahren verwendet wird. Dadurch wird die Бberschriebene Komponente innerhalb der neuen Klasse verdeckt. Das Ъberschreiben hat aber keinen Einfluс auf den Vorfahren selbst. Es kФnnen nur Methoden und Eigenschaften Бberschrieben werden, nicht jedoch Felder. Beim Ъberschreiben von Methoden ist es wichtig, das Laufzeitverhalten der verschiedenen Arten von Methoden zu kennen und die geeignete Art auszuwДhlen. Achtung: Bei Borland Delphi ist es auch mФglich, Felder des Vorfahren zu Бberschreiben. Bei Speed-Pascal besteht diese MФglichkeit derzeit nicht. Die Vererbung ist ein transitive Beziehung. Wenn zum Beispiel eine Klasse C von einer Klasse B abstammt, die wiederum von einer Klasse A abstammt, dann erbt B als Nachkomme von A deren Komponenten. C ist Nachkomme von B und damit auch Nachkomme von A. Zu den Komponenten, die C von B erbt, gehФren also auch die von A. Im folgenden Beispiel werden zwei Klassen TWindow und TEditorWindow deklariert. Da TEditorWindow ein Nachfahre von TWindow ist, besitzt die Klasse sДmtliche Komponenten von TWindow (FX, FY, FWidth, FHeight und die Methode Draw) sowie die beiden neuen Felder FCursorRow und FCursorColumn. type TWindow = class FX, FY, FWidth, FHeight: Integer; procedure Draw; end; TEditorWindow = class(TWindow) FCursorRow, FCursorColumn: Integer; end; Durch mehrmalige Anwendung der Vererbung entsteht eine baumartige Hierarchie von Klassen. Es gibt keine BeschrДnkung fБr die Anzahl der Nachkommen, die eine Klasse besitzen kann. Jede Klasse hat aber immer genau einen Vorfahren. Wenn nicht explizit ein Vorfahr angegeben wird, dann nimmt der Compiler die in der Unit System vordefinierte Klasse TObject als Vorfahren an. Damit sind die folgenden beiden Klassendeklarationen Дquivalent: type TMyObject = class // impliziter Nachkomme von TObject ... end; type TMyObject = class(TObject) // expliziter Nachkomme von TObject ... end; Die Hierarchie der Klassen schlДgt sich auch in den Regeln fБr ZuweisungskompatibilitДt von Objekten nieder, die sich von denen fБr traditionelle Datentypen unterscheiden. ═══ 1.2. Sichtbarkeit ═══ ▄▄▄▄▄▄▄▄▄▄▄▄ Sichtbarkeit ▀▀▀▀▀▀▀▀▀▀▀▀ Was ist Sichtbarkeit? Die Sichtbarkeit einer Komponente bestimmt, wer diese Komponente sehen und darauf zugreifen kann. Innerhalb des Moduls, in dem eine Klasse deklariert wird, sind immer alle ihre Komponenten uneingeschrДnkt sichtbar. Die Sichtbarkeit einer Komponente auсerhalb dieses Moduls kann mit Hilfe der SchlБsselwФrter public, protected und private abgestuft eingeschrДnkt werden. ZusДtzlich existiert mit published eine Art von Sichtbarkeit, die Komponenten einer Klasse im Objekt-Inspektor der Sibyl-Umgebung editierbar macht und spezielle Laufzeitinformationen erzeugt, die von SPCC-Anwendungen benФtigt werden. Ein Sichtbarkeitsbezeichner wird in der Deklaration der Klasse vor einer Komponente eingefБgt. Er gilt ab dieser Stelle entweder fБr alle folgenden Komponenten der Klasse, oder bis eine andere Art der Sichtbarkeit festgelegt wird. Die Sichtbarkeit einer Komponente vererbt sich auf die Nachfahren der Klasse, kann aber in Nachfahren geДndert werden, wenn die Komponente dort sichtbar ist.  public - Щffentliche Komponenten Wenn keine andere Art der Sichtbarkeit vorgegeben wird, dann wДhlt der Compiler automatisch public. Komponenten, die als public gekennzeichnet werden, sind sowohl innerhalb als auch auсerhalb des Moduls, in dem die Klassendeklaration beheimatet ist, uneingeschrДnkt sichtbar. public ist die Art von Sichtbarkeit, die Бblicherweise fБr Komponenten gewДhlt wird, die dem Anwender einer Klasse zur VerfБgung stehen sollen.  protected - GeschБtzte Komponenten protected stellt eine etwas eingeschrДnktere Art von Sichtbarkeit dar als public. Komponenten, die als protected gekennzeichnet werden, sind innerhalb des Moduls, in dem ihre Klasse deklariert wird, uneingeschrДnkt sichtbar. Auсerhalb des Moduls sind sie nur innerhalb von Methoden von Nachfahren der Klasse sichtbar. protected ist die Art von Sichtbarkeit, die Бblicherweise fБr Komponenten gewДhlt wird, die zwar dem Entwickler von Nachfahren der Klasse zur VerfБgung stehen, aber vor dem Anwender einer Klasse verborgen werden sollen.  private - Versteckte Komponenten private ist die eingeschrДnkteste Art von Sichtbarkeit. Komponenten die als private gekennzeichnet werden, sind innerhalb des Moduls, in dem ihre Klasse deklariert wird, uneingeschrДnkt sichtbar. Auсerhalb des Moduls sind sie nicht sichtbar. private ist die Art von Sichtbarkeit, die Бblicherweise fБr Komponenten gewДhlt wird, die im Rahmen der Kapselung gДnzlich verborgen werden sollen.  published - VerФffentlichte Komponenten published stellt eine besondere Art von Sichtbarkeit dar, die innerhalb der Sibyl-Umgebung zum Einsatz kommt. Komponenten, die als published gekennzeichnet werden, sind sowohl innerhalb als auch auсerhalb des Moduls, in dem ihre Klasse deklariert wird, uneingeschrДnkt sichtbar. Damit verhalten sie sich exakt so wie Komponenten, die als public gekennzeichnet werden. ZusДtzlich erzeugt der Compiler aber fБr Felder und Eigenschaften mit der Sichtbarkeit published spezielle Laufzeitinformationen, die fБr die Zusammenarbeit mit dem Objekt-Inspektor und fБr das Laufzeitverhalten von SPCC-Anwendungen von Bedeutung sind. published ist die Art von Sichtbarkeit, die fБr Komponenten gewДhlt werden muс, die innerhalb des Objekt-Inspektors editierbar sein sollen. ZusДtzlich gelten folgende EinschrДnkungen fБr Komponenten mit der Sichtbarkeit published: - Handelt es sich bei der Komponente um ein Feld, so muс dieses vom Typ einer Klasse sein. - Handelt es sich bei der Komponente um eine Eigenschaft, so muс diese einfach, also nicht-indiziert sein. Auсerdem ist ihr Typ auf eine der folgenden MФglichkeiten beschrДnkt: -- AufzДhlungstyp -- Ganzzahliger Typ -- Flieсkommazahl -- String -- Mengentyp mit nicht mehr als 16 Elementen -- Objekttyp -- Methodenzeiger ═══ 1.3. Instanzen ═══ ▄▄▄▄▄▄▄▄▄ Instanzen ▀▀▀▀▀▀▀▀▀ Was sind Instanzen? Ein ReprДsentant einer Klasse wird eine Instanz oder ein Objekt der Klasse genannt. Objekte sind dynamische Datentypen, die zur Laufzeit erzeugt und zerstФrt werden. Eine Klasse legt den Bauplan fБr jede ihrer Instanzen fest. Das Erzeugen und ZerstФren von Objekten geschieht mithilfe spezieller Methoden. Zum Erzeugen eines Objekts wird ein Konstruktor aufgerufen, der den vom Objekt benФtigten Speicherbereich auf dem Heap reserviert und initialisiert. Wird das Objekt nicht mehr benФtigt, sollte es durch den Aufruf eines Destruktors zerstФrt werden, wodurch auch der reservierte Speicher wieder freigegeben wird. Das Objektmodell von Speed-Pascal basiert auf Referenzen. Eine Variable vom Typ einer Klasse enthДlt kein Objekt, sondern einen Zeiger. Dieser Zeiger kann zur Laufzeit ein Objekt referenzieren oder den Wert nil enthalten, um anzudeuten, daс er gerade kein gБltiges Objekt referenziert. Beim Erzeugen eines neuen Objekts wird dem Zeiger die Adresse des Speicherbereiches zugewiesen, der fБr das Objekt reserviert wurde. Es ist weder notwendig noch mФglich, den Zeiger explizit zu dereferenzieren. Der Compiler erledigt dies bei Bedarf.  Beim Zugriff auf die Elemente eines Objekts wird der Zeiger automatisch dereferenziert. Dadurch stellt sich ein Objekt trotz dynamischer Allokation wie eine statische Variable dar.  Beim Zugriff auf das gesamte Objekt wird der Zeiger nicht dereferenziert. Stattdessen wird mit dem Wert des Zeigers, also der Adresse der Instanz gearbeitet. Das wirkt sich insbesondere bei Zuweisungen aus. Erzeugen von Instanzen Instanzen werden erzeugt, indem eine spezielle Art von Methode aufgerufen wird, ein sogenannter Konstruktor. Der Konstruktur reserviert den fБr die Instanz nФtigen Speicherbereich auf dem Heap und initialisiert das neue Objekt. Die Adresse der Instanz wird in eine Instanzvariable geschrieben, die zuvor deklariert sein muс. Genauere Informationen zur Implementierung und zum Aufruf von Konstruktoren finden Sie im entsprechenden Abschnitt. ZerstФren von Instanzen Objektinstanzen werden zerstФrt, indem eine spezielle Art von Methode, ein sogenannter Destruktor aufgerufen wird. Der Destruktor fБhrt zuerst Code aus, der notwendig ist, um die Benutzung er Objektinstanz korrekt zu beenden. Das schlieсt insbesondere die Freigabe untergeordneter Objekte ein. Dieser Code wird vom Programmierer festgelegt. Dann gibt der Destruktor den vom Objekt auf dem Heap belegten Speicherplatz wieder frei. Genauere Informationen zur Implementierung und zum Aufruf von Destruktoren finden Sie im entsprechenden Abschnitt. ═══ 1.4. Zuweisungen ═══ ▄▄▄▄▄▄▄▄▄▄▄ Zuweisungen ▀▀▀▀▀▀▀▀▀▀▀ Was geschieht bei Zuweisungen? Das Objektmodell von Speed-Pascal basiert auf Referenzen. Eine Variable vom Typ einer Klasse bietet nicht Platz fБr eine Objektinstanz, sondern nur fБr einen Zeiger, der eine Objektinstanz referenzieren kann. Bei einer Zuweisung wird demzufolge keine Kopie des Quellobjektes erstellt. Stattdessen wird die Adresse des Objektes auf der rechten Seite der Zuweisung in die Variable auf der linken Seite der Zuweisung geschrieben. Dadurch referenzieren beide Variablen nach der Zuweisung das gleiche Objekt, was auch das folgende Beispiel verdeutlicht. var A, B: TObject; begin A.Create; // Erzeugt eine Instanz von TObject. Die // Variable A referenziert diese Instanz. B := A; // Weist B den Wert von A zu. Beide Variablen // referenzieren nun das gleiche Objekt. B.Destroy; // ZerstФrt die von B referenzierte Instanz. // Damit ist auch der Wert von A ungБltig. end. ZuweisungskompatibilitДt Durch den Vererbungsmechanismus ergeben sich verwandtschaftliche Beziehungen zwischen Klassen. Diesen Beziehungen wird durch erweiterte MФglichkeiten bei Zuweisungen Rechnung getragen. Auсer mit sich selbst ist eine Klasse zuweisungskompatibel mit all ihren Vorfahren. Das schlieсt sowohl den einen direkten als auch jeden weiteren indirekten Vorfahren ein. Zur Laufzeit kann eine Variable vom Typ einer Klasse A also eine Instanz genau dieser Klasse A oder eines beliebigen Nachfahren von A referenzieren. Umgekehrt gilt diese ZuweisungskompatibilitДt nicht. Eine Klasse ist mit keinem ihrer Nachfahren zuweisungskompatibel. Zwischen gДnzlich unverwandten Klassen, die vФllig verschiedenen Оsten der Objekthierarchie entstammen, besteht ebenfalls keinerlei ZuweisungskompatibilitДt. Folgendes Beispiel demonstriert eine Reihe von legalen und illegalen Zuweisungen. Illegale Zuweisungen werden vom Compiler erkannt und nicht akzeptiert. type TVehicle = class(TObject); TShip = class(TVehicle); TSubmarine = class(TShip); TAirplane = class(TVehicle); var Vehicle: TVehicle; Ship: TShip; Submarine: TSubmarine; Airplane: TAirplane; begin // Die folgenden Zuweisungen sind gБltig. Vehicle := Ship; Vehicle := Submarine; Vehicle := Airplane; Ship := Submarine; // Die folgenden Zuweisungen sind ungБltig // und werden vom Compiler nicht akzeptiert. Ship := Vehicle; // TShip ist Nachkomme von TVehicle. Submarine := Vehicle; // TSubmarine ist Nachkomme von TVehicle. Airplane := Vehicle; // TAirplane ist Nachkomme von TVehicle. Submarine := Ship; // TSubmarine ist Nachkomme von TShip. Ship := Airplane; // TShip und TAirplane sind nicht verwandt. Airplane := Ship; // TAirplane und TShip sind nicht verwandt. end. Typwandlungen Bei Objekten kФnnen - wie bei anderen Datentypen auch - Typwandlungen durchgefБhrt werden. Durch eine Typwandlung wird die TypprБfung von Speed-Pascal bewuсt unterlaufen, wodurch sich potentielle Gefahren ergeben. Um diese Gefahren zu minimieren, bietet Speed-Pascal mit is und as zwei Operatoren, die unter anderem sichere Typwandlungen ermФglichen.  Der Operator is Der Operator is dient dazu, zur Laufzeit eine TypprБfung fБr ein Objekt durchzufБhren. Die Syntax ist: MyObject is MyClass Das Ergebnis dieses Ausdrucks ist vom Typ Boolean. Zur Laufzeit wird der tatsДchliche Typ des Objekts MyObject mit der Klasse MyClass verglichen. Ist der Typ von MyObject identisch mit MyClass oder einem beliebigen direkten oder indirekten Nachfahren von MyClass, dann ist das Ergebnis des Ausdrucks True. In allen anderen FДllen, insbesondere wenn MyObject nil ist, liefert die Anwendung des Operators den Wert False. type TVehicle = class(TObject); TShip = class(TVehicle); TAirplane = class(TVehicle); var MyVehicle: TVehicle; begin ... if MyVehicle is TShip then WriteLn('Es ist ein Schiff.') else if MyVehicle is TAirplane then WriteLn('Es ist ein Flugzeug.'); ... end. Beachten Sie, daс MyClass nicht unmittelbar den Namen einer Klasse angeben muс, sondern auch eine Variable vom Typ einer Metaklasse sein kann. Die tatsДchliche Klasse wird dann zur Laufzeit dieser Variablen entnommen. NДheres dazu entnehmen Sie bitte dem Abschnitt Бber Metaklassen.  Der Operator as Der Operator as dient dazu, zur Laufzeit sichere Typwandlungen durchzufБhren. Die Syntax ist: MyObject as MyClass Das Ergebnis des Ausdrucks ist vom Typ MyClass. Zur Laufzeit wird anhand des tatsДchlichen Typs von MyObject geprБft, ob die Typwandlung sicher ist. Als sicher gilt die Typwandlung genau dann, wenn MyClass identisch mit dem tatsДchlichen Typ von MyObject oder ein direkter oder indirekter Vorfahre davon ist. Ist diese Bedingung nicht erfБllt, dann wird eine Exception ausgelФst. Sie kФnnen den Operator mit with..do kombinieren, um eine Reihe von Operationen auf einem Objekt nur dann durchzufБhren, wenn das Objekt zuweisungskompatibel zu einer bestimmten Klasse ist. var MyStream: TStream; begin ... with MyStream as THandleStream do // Wenn MyStream zuweisungskompatibel begin // zu THandleStream ist, dann wird der WriteLn('Handle: ', Handle); // Block ausgefБhrt. Sonst wird eine end; // Exception ausgelФst. ... end; Beachten Sie, daс MyClass nicht unmittelbar den Namen einer Klasse angeben muс, sondern auch eine Variable vom Typ einer Metaklasse sein kann. Die tatsДchliche Klasse wird dann zur Laufzeit dieser Variablen entnommen. NДheres dazu entnehmen Sie bitte dem Abschnitt Бber Metaklassen. Beachten Sie auсerdem, daс die Typwandlung auch dann als sicher gilt, wenn MyObject den Wert nil enthДlt. Wenn Sie nicht sicher sind, ob MyObject wirklich ein gБltiges Objekt referenziert, dann sollten Sie dies zuerst prБfen. Anderenfalls riskieren Sie den Abbruch des Programms mit einer Schutzverletzung. ═══ 1.5. Felder ═══ ▄▄▄▄▄▄ Felder ▀▀▀▀▀▀ Was sind Felder? Felder sind Variablen innerhalb einer Klasse. Sie entsprechen in Deklaration und Anwendung weitgehend den Feldern von Records. Jedes Objekt verfБgt Бber einen eigenen Satz aller Felder seiner Klasse, insbesondere auch der Felder, die die Klasse von ihren Vorfahren geerbt hat. Das Оndern eines Feldes von Objekt A hat keinen Einfluс auf das gleichnamige Feld eines Objekts B der gleichen Klasse, solange die Variablen A und B nicht die gleiche Instanz referenzieren. Hingegen teilen sich alle Objekte einer Klasse den gleichen Satz Methoden, wodurch deren Code nur einmal vorhanden ist. Deklaration von Feldern Felder werden auf die folgende Weise deklariert: Es ist in Speed-Pascal Бblich, wenngleich nicht zwingend vorgeschrieben, daс Feldbezeichner mit einem F beginnen. Folgendes Beispiel zeigt die Deklaration von Feldern. type TCity = class FName: string; FLongitude, FLatitude: Integer; FPopulation: LongWord; end; Alle Instanzen der Klasse TCity verfБgen Бber ihre eigenen Felder zur Aufnahme von Name, geographischer Position und BevФlkerungszahl einer Stadt. Benutzen von Feldern Die Zugriffe auf die Felder eines Objekts entsprechen den Zugriffen auf Felder von Records. Einzelne Felder kФnnen Бber den Punkt und den Bezeichner des Feldes selektiert werden. Auсerdem kann eine Operation auf einem Objekt durch with..do angefБhrt werden, wodurch alle Felder des Objekts direkt ansprechbar werden. type TCompiler = class FName: string; FVersionHi, FVersionLo: Integer; end; var MyCompiler: TCompiler; begin ... // Qualifizierter Zugriff auf die Felder des Objekts. MyCompiler.FName := 'Speed-Pascal/2'; MyCompiler.FVersionHi := 2; MyCompiler.FVersionLo := 0; // Zugriff auf die Felder mit Hilfe von WITH..DO. Die // folgende Sequenz hat die gleiche Wirkung wie die // vorangehende. with MyCompiler do begin FName := 'Speed-Pascal/2'; FVersionHi := 2; FVersionLo := 0; end; // Lesender Zugriff, WITH..DO ohne BEGIN..END. with MyCompiler do WriteLn(FName, ' Version ', FVersionHi, '.', FVersionLo); ... end. ═══ 1.6. Methoden ═══ ▄▄▄▄▄▄▄▄ Methoden ▀▀▀▀▀▀▀▀ Was sind Methoden? Methoden sind Prozeduren oder Funktionen, deren FunktionalitДt fest mit einer Klasse verknБpft ist. Methoden werden Бblicherweise in Verbindung mit einer Instanz der Klasse aufgerufen. Bei Klassenmethoden ist auch ein Aufruf in Verbindung mit einer Klasse mФglich. Felder, Methoden und Eigenschaften einer Klasse sind innerhalb der Implementierung ihrer Methoden verfБgbar. Methoden werden an Nachfahren vererbt. Alle Instanzen einer Klasse teilen sich den gleichen Satz Methoden, so daс deren Code nur einmal im Programm vorhanden ist. Es gibt eine Reihe von SchlБsselwФrtern, die das Laufzeitverhalten einer Methode beeinflussen. WДhrend bei statischen Methoden die Aufrufadresse bereits zur Zeit der Compilierung feststeht, wird sie bei virtuellen und dynamischen Methoden erst zur Laufzeit ermittelt. ZusДtzlich existieren botschaftsverarbeitende Methoden, die das Zusammenspiel verschiedener Bildschirmelemente mit dem Betriebssystem Бber den Austausch von Nachrichten koordinieren. Zwei spezielle Arten von Methoden, Konstruktoren und Destruktoren, dienen zum Erzeugen und ZerstФren von Objektinstanzen. Deklarieren von Methoden Methoden werden auf die folgende Weise deklariert: Folgendes Beispiel deklariert eine Klasse TWindow zur Darstellung eines Fensters auf dem Bildschirm. Die Klasse enthДlt zwei Methoden zum Lesen und Schreiben des Fenstertitels. type TWindow = class ... function GetTitle: string; procedure SetTitle(const S: string); ... end; Implementieren von Methoden Die Methoden einer Klasse werden nach der Deklaration der Klasse implementiert, Дhnlich der Implementierung einer als forward deklarierten Prozedur oder Funktion. Falls die Klasse in einer Unit deklariert wird, findet die Implementierung der Methoden im implementation-Teil der Unit statt. Jeder einzelnen Methode geht bei der Implementierung der Name der Klasse gefolgt von einem Punkt voran. Die Liste formaler Parameter der Methode kann, muс aber nicht bei der Implementierung wiederholt werden. Wenn sie wiederholt wird, dann muс sie exakt mit der Parameterliste der Deklaration Бbereinstimmen. type TWindow = class X, Y, Width, Height: Integer; Title: string; ... procedure Draw; procedure SetPosition(X, Y: Integer); procedure SetTitle(const S: string); end; // Draw besitzt keine Parameter. procedure TWindow.Draw; begin ... end; // Bei SetPosition wird die gesamte Parameterliste wiederholt. procedure TWindow.SetPosition(X, Y: Integer); begin ... end; // Bei SetTitle wird die gesamte Parameterliste weggelassen. procedure TWindow.SetTitle; begin ... end; Der GБltigkeitsbereich aller Komponenten einer Klasse erstreckt sich unter anderem auf die Implementierung aller Methoden der Klasse. Dadurch sind alle Felder, Methoden und Eigenschaften der Klasse innerhalb der Methode direkt zugreifbar. procedure TWindow.SetTitle(const S: string); begin FTitle := S; Draw; end; ZusДtzlich existiert in jeder Methode ein impliziter Parameter Self, der vom Typ der Klasse ist, zu der die Methode gehФrt. Self referenziert zur Laufzeit stets das Objekt, auf dem die Methode aufgerufen wurde. Wenn durch eine with..do-Anweisung oder durch formale Parameter der Methode Komponenten der Klasse aus dem aktuellen GБltigkeitsbereich verdrДngt werden, dann kann Бber Self weiterhin auf diese Komponenten zugegriffen werden. procedure TWindow.SetPosition(X, Y: Integer); begin // Der Konflikt zwischen den Feldern X, Y und den // gleichnamigen formalen Parametern wird durch // Self gelФst. Self.X := X; Self.Y := Y; Draw; end; Falls innerhalb der Implementierung einer Methode einer Klasse auf eine gleichnamige Methode des Vorfahren zugegriffen werden soll, die in der neuen Klasse Бberschrieben oder verdeckt wurde, dann ist dies mit dem SchlБsselwort inherited mФglich. type TEditorWindow = class(TWindow) procedure Draw; end; procedure TWindow.Draw; begin DrawFrameAndTitle; // Soll den den Rahmen und die Titelzeile zeichnen. end; procedure TEditorWindow.Draw; begin inherited Draw; // Ruft zuerst TWindow.Draw auf. DrawContents; // Soll den Inhalt des Editor-Fensters zeichnen. end; Aufruf von Methoden Der Aufruf einer Methode geschieht durch Angabe der Instanz, auf der die Methode operieren soll, gefolgt von einem Punkt, dem Namen der Methode und der Liste aktueller Parameter. Innerhalb einer with..do-Anweisung, die eine Objektinstanz in den aktuellen GБltigkeitsbereich rБckt, kann die Angabe der Instanz auch entfallen. type TWindow = class procedure SetPosition(X, Y: Integer); end; var MyWindow: TWindow; begin ... MyWindow.SetPosition(100, 200); // Qualifizierter Aufruf. ... with MyWindow do begin SetPosition(100, 200); // Aufruf innerhalb von WITH..DO end; // benФtigt keine Angabe der Instanz. ... end. Bei Klassenmethoden besteht zusДtzlich die MФglichkeit, die Methode mit dem Bezeichner einer Klasse anstelle der Objektinstanz aufzurufen. Welche Methode zur Laufzeit tatsДchlich aufgerufen, hДngt wesentlich davon ab, welches Laufzeitverhalten fБr die Methode gewДhlt wurde. WДhrend bei statischen Methoden die Bindung bereits zur Zeit der Compilierung durchgefБhrt wird, ermittelt bei virtuellen oder dynamischen Methoden das Laufzeitsystem anhand des tatsДchlichen Typs der Instanz, welche Methode aufgerufen wird. Spezielle Arten von Methoden  Statische Methoden Sofern bei der Deklaration einer Methode keine andere Art der Methodenzuteilung gewДhlt wird, ist die Methode statisch. Die Adresse der aufzurufenden Methode steht dann bereits zur Zeit der Compilierung fest und wird vom Linker fest ins Programm eingetragen.  Virtuelle Methoden Das Laufzeitverhalten einer Methode wird als virtuell festgelegt, wenn an die Deklaration der Methode das SchlБsselwort virtual angehДngt wird. Bei virtuellen Methoden steht die Adresse des Aufrufes nicht bereits zur Zeit der Compilierung fest. Stattdessen wird diese Entscheidung auf die Laufzeit des Programms verschoben. Das Laufzeitsystem ermittelt anhand des tatsДchlichen Typs der Instanz, welche Methode aufgerufen wird. Wenn eine Methode einmal als virtuell festgelegt wird, dann gilt dies automatisch auch fБr alle Nachfahren der Klasse, zu der die Methode gehФrt. Das bedeutet insbesondere, daс die einmal festgelegte Liste formaler Parameter beim Ъberschreiben der Methode beibehalten werden muс. Zum Ъberschreiben einer virtuellen Methode wird das SchlБsselwort override anstelle von virtual eingesetzt. Folgendes Beispiel zeigt einen typischen Einsatzfall fБr eine virtuelle Methode. Verschiedene graphische Bildschirmelemente besitzen eine Methode Draw, mit der sie neu gezeichnet werden. Dadurch, daс diese Methode virtuell ist, wird zur Laufzeit stets die zur Instanz passende Version von Draw aufgerufen. Bei einer statischen Methode hingegen wБrde stets die Version von Draw aufgerufen, die zum deklarierten Typ der Variablen gehФrt, im Beispiel also TWindow.Draw. type TWindow = class(TObject) procedure Draw; virtual; // Kennzeichnet die Methode als virtuell. end; TEditorWindow = class(TWindow) procedure Draw; override; // Ъberschreibt die virtuelle Methode. end; ... var MyWindow: TWindow; begin MyWindow := TWindow.Create; // Erzeugt eine Instanz von TWindow. MyWindow.Draw; // Ruft TWindow.Draw auf. MyWindow.Destroy; // ZerstФrt die Instanz. MyWindow := TEditorWindow.Create; // Erzeugt eine neue Instanz von // TEditorWindow MyWindow.Draw; // Ruft TEditorWindow.Draw auf. WДre // Draw nicht virtuell, wБrde hier // auch TWindow.Draw aufgerufen, was // in diesem Fall sicher nicht sehr // sinnvoll ist. MyWindow.Destroy; // ZerstФrt die Instanz. end. Es ist auch mФglich, eine virtuelle Methode in einem Nachfahren neu zu deklarieren. Dadurch wird die geerbte virtuelle Methode in der neuen Klasse verdeckt, analog zu einer statischen Methode, die durch die Neudeklaration einer statischen Methode gleichen Namens verdeckt wird. Die neu deklarierte Methode muс dabei wieder eine als virtual gekennzeichnete virtuelle Methode sein, jedoch kann sich ihre Liste formaler Parameter von der der verdeckten Methode unterscheiden. Das Verhalten von auf diese Weise neu deklarierten virtuellen Methoden ist aber unter UmstДnden schwerer zu durchschauen als das von Бberschriebenen virtuellen Methoden. Sie sollten deshalb eine einmal als virtuell gekennzeichnete Methode stets mit override Бberschreiben. Achtung: Im Gegensatz zu Borland Delphi ist es bei Speed-Pascal derzeit nicht mФglich, daс eine virtuelle Methode durch die Neudeklaration einer statischen Methode in einem Nachfahren verdeckt wird. Der Compiler verbietet dies, um Fehler seitens des Programmierers abzufangen. Normalerweise handelt es sich dabei eher um ein vergessenes override als um eine gewollte Neudeklaration. Diese Art von Fehler ist aber meist schwer zu entdecken, weil das Programm korrekt compiliert wird, und sich das falsche Verhalten der Methode nur in einzelnen FДllen und oft nur auf sehr subtile Weise bemerkbar macht. Virtuelle Methoden werden vom Compiler mit Hilfe einer Tabelle virtueller Methoden realisiert. Jede Klasse besitzt eine solche Tabelle mit den Einsprungadressen aller ihrer virtuellen Methoden. Zur Laufzeit wird die Adresse der aufzurufenden Methode aus dieser Tabelle ermittelt. Der Aufrufmechanismus ist sehr schnell, benФtigt aber dennoch minimal mehr Zeit als der Aufruf einer statischen Methode. Virtuelle Methoden sind ein mДchtiges Hilfsmittel, um Polymorphie zu erreichen.  Dynamische Methoden Das Laufzeitverhalten einer Methode wird als dynamisch festgelegt, wenn an die Deklaration der Methode das SchlБsselwort dynamic angehДngt wird. Dynamische Methoden verhalten sich exakt so wie virtuelle Methoden, insbesondere was das Ъberschreiben und die Neudeklaration betrifft. Achtung: Das SchlБsselwort existiert nur aus GrБnden der KompatibilitДt zu Borland Delphi. Programme, die auf dynamische Methoden zurБckgreifen, werden unter Speed-Pascal problemlos funktionieren. Jedoch werden sie vermutlich geringfБgige Unterschiede in AusfБhrungsgeschwindigkeit und Speicherbedarf aufweisen.  Abstrakte Methoden Eine virtuelle oder dynamische Methode wird zu einer abstrakten Methode, wenn an die Deklaration der Methode das SchlБsselwort abstract angehДngt wird. Nur virtuelle und dynamische Methoden kФnnen abstrakt sein, nicht jedoch statische. Das SchlБsselwort abstract muс dabei dem SchlБsselwort virtual oder dynamic folgen. Abstrakte Methoden werden nicht implementiert, kФnnen jedoch von anderen Methoden oder von Code, der nicht Teil einer Methode ist, benutzt werden. Ihr Sinn liegt darin, ein funktionsloses Interface bereitzustellen, das von Nachfahren einer Klasse aufrechterhalten werden muс. Spezialisierte Nachfahren mБssen abstrakte Methoden mit override Бberschreiben, wenn die Methode tatsДchlich genutzt werden soll. Der Aufruf einer abstrakten Methode auf einer Instanz, deren Klasse die Methode nicht Бberschreibt, fБhrt zu einem Laufzeitfehler. Innerhalb der Implementierung einer Methode, die eine abstrakte Methode Бberschreibt, kann nicht mit inherited auf die Methode des Vorfahren zugegriffen werden. Folgendes Beispiel deklariert eine Basisklasse TContainer fБr Container-Objekte. Sie verfБgt Бber zwei abstrakte Methoden zum Vergleich und zum Vertauschen zweier Elemente anhand ihrer Indizes. Auсerdem beinhaltet die Klasse eine Sortiermethode. Innerhalb der Sortiermethode kФnnen die beiden abstrakten Methoden type TContainer = class FSize: Integer; function BiggerThan(Item1, Item2: Integer): Boolean; virtual; abstract; procedure Swap(Item1, Item2: Integer); virtual; abstract; procedure BubbleSort; end; procedure TContainer.BubbleSort; var N, I: Integer; begin for N := FSize downto 2 do for I := 1 to N - 1 do if BiggerThan(I, I + 1) then Swap(I, I + 1); end;  Botschaftsverarbeitende Methoden Eine Methode wird zu einer botschaftsverarbeitenden Methode, indem an ihre Deklaration das SchlБsselwort message gefolgt von einer ganzzahligen, positiven Konstanten angehДngt wird. ZusДtzlich muс die Methode eine Prozedurmethode sein, die Бber genau einen var-Parameter beliebigen Typs verfБgt. Auch ein untypisierter var-Parameter ist erlaubt. type TMyButton = class(TButton) ... procedure WMButton1Down(var Message); message WM_BUTTON1DOWN; ... end; Botschaftsverarbeitende Methoden dienen Бblicherweise zum Austausch von Nachrichten mit dem Betriebssystem, insbesondere bei der Programmierung von graphischen OberflДchen wie dem OS/2 Presentation Manager oder Windows. So wird zum Beispiel bei einem Tastendruck oder einem Mausklick vom Betriebssystem eine Botschaft an das zugehФrige Bildschirmobjekt gesandt, das diese Botschaft dann verarbeiten muс. Es wird genau die botschaftsverarbeitende Methode aufgerufen, deren hinter message angegebene Nummer mit der Identifikation der Nachricht Бbereinstimmt. Es ist mФglich, weitere Informationen an eine solche Methode zu Бbergeben. Dazu dient der var-Parameter, der die Ъbergabe fast beliebiger Typen erlaubt. Wichtig fБr das Funktionieren des Botschaftsmechanismus ist nur, daс die ersten vier Byte des Parameters die Nummer der Botschaft enthalten. Die darauf folgenden Daten kФnnen vom Programmierer frei gewДhlt werden. Damit sehen alle zulДssigen Parameter-Typen etwa wie folgt aus: type TMessageRec = record MessageID: LongWord; // Die Identifikation der Nachricht. ... // Ab hier beliebige weitere Informationen. end; Der OS/2 Presentation Manager und Windows erlauben beim Austausch die Ъbergabe von zwei zusДtzlichen 32 Bit breiten Parametern, die je nach Art der Nachricht in mehrere logische 16 oder 8 Bit breite Parameter unterteilt sein kФnnen. Daher ist fБr Anwendungen, die auf die SPCC-Bibliothek zurБckgreifen, in der Unit Classes bereits ein Botschaftstyp TMessage deklariert, der diesen Fall abdeckt. Es ist mФglich und ausgesprochen sinnvoll, botschaftsverarbeitende Methoden bei Nachfahren einer Klasse zu Бberschreiben, um ein vom Vorfahren abweichendes Verhalten bei bestimmten Nachrichten zu realisieren. Anders als bei virtuellen Methoden ist hier beim Ъberschreiben das SchlБsselwort override nicht notwendig. Botschaftsverarbeitende Methoden sind immer dynamisch gebunden; das heiсt, die Adresse des Aufrufes wird immer zur Laufzeit anhand des tatsДchlichen Typs der Instanz ermittelt. Dazu legt der Compiler fБr jede Klasse eine Tabelle von botschaftsverarbeitenden Methoden mit deren Nummern und Einsprungsadressen an. Die Informationen dieser Tabelle werden von einer Methode Dispatch ausgewertet, die von TObject bereitgestellt wird. Da alle Objekte Nachfahren von TObject sind, steht diese Methode in jedem Objekt zur VerfБgung. procedure Dispatch(var Message); Botschaftsverarbeitende Methoden werden Бblicherweise nicht direkt aufgerufen, sondern Бber einen Aufruf von Dispatch mit einem geeigneten Parameter, der die Nummer der Nachricht und mФglicherweise weitere Informationen enthДlt. Dispatch erledigt das Auffinden der zur Botschaft passenden Methode und ruft diese dann auf. Dazu sucht Dispatch sucht zuerst in der Tabelle der tatsДchlichen Klasse der Instanz nach einer Methode mit der in der Botschaft enthaltenen Nummer. Ist dort keine solche Methode enthalten, wird die Tabelle des Vorfahren der Klasse durchsucht, dann die von dessen Vorfahren, und so weiter. Es ist wichtig zu wissen, daс fБr den Aufruf einer botschaftsverarbeitenden Methode Бber Dispatch nur deren Nummer entscheidend ist. Der Name der Methode ist vФllig irrelevant. Es ist insbesondere mФglich, eine botschaftsverarbeitende Methode durch eine Methode vФllig anderen Namens zu Бberschreiben, solange nur die Nummern Бbereinstimmen. EnthДlt beim Aufruf von Dispatch der gesamte in Frage kommende Zweig der Klassenhierarchie keine Methode mit der angegebenen Nummer, dann wird eine virtuelle Methode DefaultHandler aufgerufen, die von der Basisklasse TObject wie folgt bereitgestellt wird: procedure DefaultHandler(var Message); virtual; Diese Methode tut Бberhaupt nichts. Es ist aber mФglich, DefaultHandler zu Бberschreiben, um ein anderes Standardverhalten bei unbehandelten Nachrichten zu implementieren, zum Beispiel die Ausgabe einer Fehlermeldung. Die graphischen Bildschirmelemente der SPCC-Bibliothek Бberschreiben diese Methode derart, daс vom Objekt nicht behandelte Nachrichten an eine Betriebssystem-Routine Бbergeben werden, die eine passende Standard-Aktion fБr das entsprechende Bildschirmelement durchfБhrt.  Konstruktoren Konstruktoren sind spezielle Methoden, deren Aufruf ein neues Objekt erzeugt und initialisiert. Konstruktoren werden bei Deklaration und Implementierung durch das SchlБsselwort constructor eingeleitet. Ein Konstruktor darf eine beliebige Liste formaler Parameter haben, aber keinen Wert zurБckliefern. Er hat also die Gestalt einer Prozedurmethode. Es gibt zwei Varianten, einen Konstruktor aufzurufen. Die eine entspricht dem Aufruf einer gewФhnlichen Methode auf einer Instanzvariablen, nur daс diese Variable vor dem Aufruf des Konstruktors noch kein gБltiges Objekt referenziert. Es wird ein Objekt erzeugt und initialisiert, dessen Typ dem der Instanzvariablen entspricht. Die Adresse dieses Objekts wird in die Instanzvariable geschrieben. type TCustomer = class(TObject) private FName: PString; public constructor Create(Name: string); end; constructor TCustomer.Create(Name: string); begin FName := NewStr(Name); end; var Customer: TCustomer; begin // Erzeuge eine Instanz von TCustomer. Customer.Create('Thomas Jefferson'); end. Die andere Variante benФtigt die Angabe einer Klasse anstelle der Instanzvariablen. Das Ergebnis dieses Aufrufes muс im Rahmen einer Zuweisung an eine Instanzvariable Бbergeben werden. Bei dieser Variante ist es mФglich, Objekte zu erzeugen, deren Typ nicht exakt dem der Variablen entspricht, sofern die Regeln fБr ZuweisungskompatibilitДt eingehalten werden. type TMailOrderCustomer = class(TCustomer) private FAddress: PString; public constructor Create(Name, Address: string); end; constructor TMailOrderCustomer.Create(Name, Address: string); begin inherited Create(Name); FAddress := NewStr(Address); end; var Customer1, Customer2: TCustomer; begin // Erzeuge eine Instanz von TCustomer. Customer1 := TCustomer.Create('Thomas Jefferson'); // Erzeuge eine Instanz von TMailOrderCustomer. Da diese Klasse // zuweisungskompatibel zu TCustomer ist, kann Customer2 diese // Instanz referenzieren. Customer2 := TMailOrderCustomer.Create('George Washington', 'White-House, Washington, DC'); end. Achtung: Bei Borland Delphi ist nur die zweite Variante mФglich. Wenn Programme entwickelt werden, deren Quellcode zwischen Speed-Pascal und Delphi ausgetauscht werden soll, dann muс die erste Variante vermieden werden, was aber keinerlei EinschrДnkung der FunktionalitДt des Programms darstellt. Beim Aufruf eines Konstruktors werden automatisch folgende Aktionen ausgefБhrt: - Der fБr die Objektinstanz benФtigte Speicher wird auf dem Heap reserviert. Die Adresse des Speicherbereiches wird in die Instanzvariable geschrieben, mit der Konstruktor aufgerufen wurde. - Der reservierte Speicherbereich wird mit Nullen gefБllt. Dadurch erhДlt jedes Feld des neuen Objekts einen definierten Zustand. Welcher Zustand dies ist, hДngt vom Typ des Feldes ab: -- Ganzzahlen und Flieсkommazahlen erhalten den Wert Null. -- Zeiger und Objektreferenzen erhalten den Wert nil. -- Einzelne Zeichen erhalten das Nullzeichen Chr(0). -- Strings erhalten die LДnge Null. -- Felder vom Typ Boolean erhalten den Wert False. -- Bei strukturierten Feldern wie Records und Arrays gelten diese Regeln fБr alle Elemente. - Der Benutzercode des Konstruktors, also die Folge der Anweisungen zwischen begin und end, wird ausgefБhrt. Bei der Implementierung eines Konstruktors sollten die Schritte ausgefБhrt werden, die nФtig sind, um eine neue Instanz korrekt zu initialisieren. Es ist mФglich, mit inherited den Konstruktor des Vorfahren aufzurufen. Dabei wird nur der Benutzercode des geerbten Konstruktors ausgefБhrt, nicht aber eine weitere Instanz erzeugt. Es ist Бblich, zuerst den Konstruktor des Vorfahren aufzurufen, um die von Vorfahren geerbten Fehler zu initialisieren. Im Anschluс werden meist die zusДtzlichen Fehler der neuen Klasse initialisiert. Eine Klasse, die keine Felder zusДtzlich zu denen ihres Vorfahren deklariert oder deren Felder bei der Initialisierung alle auf Null gesetzt werden mБssen, kommt eventuell ohne eigenen Konstruktor aus. Der Konstruktor des Vorfahren, im Extremfall der von TObject, kann diese Aufgabe Бbernehmen. Wenn wДhrend des Konstruktoraufrufes an einer beliebigen Stelle eine Exception ausgelФst wird, dann wird zuerst zum Destruktor verzweigt, bevor die Exception-Behandlungsroutine angesprungen oder ein Laufzeitfehler ausgelФst wird. Deshalb ist es notwendig, daс der Destruktor auf diesen Fall reagieren kann, also auch in der Lage ist, nur teilweise initialisierte Objekte zu zerstФren. Konstruktoren kФnnen virtuell sein. Dadurch wird polymorphes Erzeugen von Objekten mФglich, also von Objekten, deren exakter Typ zur Zeit der Compilierung noch nicht bekannt ist. Ein virtueller Konstruktor muс immer in der Zuweisungsvariante aufgerufen werden. Genaueres dazu befindet sich im Abschnitt Бber Metaklassen.  Destruktoren Destruktoren sind spezielle Methoden, deren Aufruf ein Objekt zerstФrt. Destruktoren werden bei Deklaration und Implementierung durch das SchlБsselwort destructor eingeleitet. Ein Destruktor darf eine beliebige Liste formaler Parameter haben, was aber Бblicherweise nicht der Fall ist. Ein Destruktor darf keinen Wert zurБckliefern, hat also die Gestalt einer Prozedurmethode. Destruktoren sollten virtuell sein, damit sichergestellt ist, daс zur Laufzeit der zur Instanz passende Destruktor aufgerufen wird. Jede Klasse erbt von TObject einen virtuellen, parameterlosen Destruktor Destroy. Es wird empfohlen, daс Sie diesen Destruktor in Ihren eigenen Klassen mit override Бberschreiben. Ein Destruktor wird auf die gleiche Weise aufgerufen wie eine gewФhnliche Methode. Er fБhrt zuerst den Benutzercode, also die Anweisungsfolge zwischen begin und end aus, dann gibt er den von der Instanz benФtigten Speicher auf dem Heap frei. Es ist mФglich, innerhalb eines Destruktors mit inherited auf den Destruktor des Vorfahren zuzugreifen. In diesem Fall wird nur der Benutzercode des geerbten Destruktors ausgefБhrt, die Instanz wird aber erst beim Beenden des Бbergeordneten Destruktoraufrufes zerstФrt. type TCustomer = class(TObject) private FName: PString; public constructor Create(Name: string); destructor Destroy; override; end; constructor TCustomer.Create(Name: string); begin FName := NewStr(Name); end; destructor TCustomer.Destroy; begin DisposeStr(FName); end; var Customer: TCustomer; begin // Erzeuge eine Instanz. Customer.Create('Thomas Jefferson'); // ZerstФre die Instanz wieder. Customer.Destroy; end. Eine Klasse, die keine Felder zusДtzlich zu denen ihres Vorfahren deklariert oder deren Felder nur einfache, nicht-verzeigerte Strukturen enthalten, benФtigt mФglicherweise keine speziellen AufrДumaktionen zum ZerstФren des Objekts. In diesem Fall kann der Destruktor des Vorfahren benutzt werden, und es muс kein eigener Destruktor deklariert werden. Ob dies so ist, mБssen Sie von Fall zu Fall entscheiden. Wenn wДhrend eines Konstruktoraufrufes eine Exception auftritt, dann wird zuerst zum Destruktor verzweigt, bevor die Exception-Behandlungsroutine angesprungen oder ein Laufzeitfehler ausgelФst wird. Deshalb ist es notwendig, daс ein Destruktor auf diesen Fall reagieren kann, also auch in der Lage ist, nur teilweise initialisierte Objekte zu zerstФren. Das ist insbesondere wichtig, wenn das Objekt Бber Felder von Zeiger- oder Objekttypen verfБgt. Der Destruktor sollte diese Felder auf nil prБfen, bevor er versucht, die zugehФrigen Strukturen freizugeben. Das ZerstФren eines Objektes kann sicherer gestaltet werden, wenn statt des Destruktors die Methode Free aufgerufen wird, die alle Klassen von TObject erben. Free prБft, ob die Instanz den Wert Nil enthДlt, und ruft nur dann den Destruktor Destroy auf, wenn das nicht der Fall ist.  Klassenmethoden Klassenmethoden sind Prozedur- oder Funktionsmethoden, denen bei Deklaration und Implementierung zusДtzlich das SchlБsselwort class vorangestellt wird. Sie kФnnen virtuell sein, dБrfen dann aber wieder nur durch Klassenmethoden Бberschrieben werden. Die Regeln sind hierbei die gleichen, die auch bei gewФhnlichen virtuellen Methoden gelten. type TConfigFile = class ... class function GetDefaultName: string; ... end; Eine Klassenmethode kann sowohl auf einem Objekt als auch auf einer Klasse operieren. Damit stehen Klassenmethoden auch dann zur VerfБgung, wenn keine einzige Instanz einer Klasse existiert. begin WriteLn(TConfigFile.GetDefaultName); end. Innerhalb der Implementierung einer Klassenmethode darf auсer auf globale Bezeichner nur auf andere Klassenmethoden zugegriffen werden. Ein Zugriff auf Felder, Eigenschaften oder gewФhnliche Methoden der Klasse wird vom Compiler unterbunden. Der auch in Klassenmethoden vorhandene implizite Parameter Self enthДlt keine Objektreferenz, sondern eine Referenz auf die Klasse, fБr die die Methode tatsДchlich aufgerufen wurde. Beim Aufruf der Klassenmethode auf einer Instanz enthДlt Self den tatsДchlichen Typ dieser Instanz. Klassenmethoden sind dazu gedacht, bestimmte Prozeduren oder Funktionen einer Klasse auch dann verfБgbar zu machen, wenn keine Instanz der Klasse existiert. Dabei handelt es sich Бblicherweise um Methoden, die auf irgendeine Weise Informationen Бber die Klasse liefern. TObject vererbt seinen Nachkommen eine Reihe von Klassenmethoden, mit denen zum Beispiel der Name einer Klasse ermittelt werden kann, der Name des Moduls, in dem die Klasse deklariert wurde, oder die GrФсe einer Instanz der Klasse. NДheres dazu entnehmen Sie der Beschreibung der Klasse TObject. ═══ 1.7. Eigenschaften ═══ ▄▄▄▄▄▄▄▄▄▄▄▄▄ Eigenschaften ▀▀▀▀▀▀▀▀▀▀▀▀▀ Was sind Eigenschaften? Eigenschaften sind benannte Attribute eines Objekts. Sie stellen sich dem Benutzer Дhnlich wie Felder dar, da sie Бber einen Typ verfБgen und ihr Wert in Form einer Zuweisung geДndert wird. Die MФglichkeiten von Eigenschaften gehen aber Бber die von Feldern weit hinaus, da es mФglich ist, sowohl den lesenden als auch den schreibenden Zugriff auf die Eigenschaft einzeln zu kontrollieren oder zu verbieten. Jeder der beiden mФglichen Zugriffsarten einer Eigenschaft liegt entweder ein Feld oder eine Methode zugrunde. Im ersten Fall wird der Wert der Eigenschaft aus dem Feld gelesen oder in dieses geschrieben. Im zweiten Fall wird durch das Lesen oder Schreiben der Eigenschaft implizit die Methode aufgerufen, so daс durch eine einfache Zuweisung komplexe Operationen in Gang gesetzt werden kФnnen. Es ist mФglich, indizierte Eigenschaften zu deklarieren, die fБr den Benutzer die Gestalt eines Arrays haben. Da beiden Zugriffsarten einer indizierten Eigenschaft immer eine Methode zugrunde liegt, kann sie im Gegensatz zu gewФhnlichen Arrays Бber beliebige Typen indiziert werden. Eine indizierte Eigenschaft kann zur voreingestellten Eigenschaft erklДrt werden, so daс sich das gesamte Objekt dem Benutzer als Array darstellt. Es ist in diesem Fall nicht mehr notwendig, bei einem Zugriff auf die Eigenschaft deren Bezeichner anzugeben. FБr Eigenschaften, die als published gekennzeichnet werden, erzeugt der Compiler spezielle Informationen, die zur Laufzeit des Programms oder fБr die Zusammenarbeit mit dem Objekt-Inspektor der Sibyl-Umgebung von Bedeutung sind. Das Verhalten einer Eigenschaft in diesem Kontext kann mit einigen speziellen SchlБsselwФrtern kontrolliert werden. Deklarieren von Eigenschaften Eigenschaften werden auf die folgende Weise deklariert: Die Deklaration einer Eigenschaft wird durch das SchlБsselwort property eingeleitet, gefolgt vom Bezeichner der Eigenschaft und von deren Typ. Es folgen in beliebiger Reihenfolge die Definitionen dessen, was bei lesendem oder schreibendem Zugriff auf die Eigenschaft geschieht. Die Definition der mit dem Lesen der Eigenschaft assoziierten Aktion wird eingeleitet durch das SchlБsselwort read, die der schreibenden Aktion analog durch write. Wenn eine der beiden Aktionen nicht definiert wird, dann existiert die entsprechende Zugriffsart nicht. Der Versuch, den Wert einer nur zum Lesen freigegebenen Eigenschaft zu Дndern, wird vom Compiler ebenso abgewiesen wie der Versuch, eine nur zum Schreiben freigegebene Eigenschaft in einem Ausdruck als Wert einzusetzen und damit implizit auszulesen. Sowohl das Lesen als auch das Schreiben einer Eigenschaft kann entweder mit einem Feld oder mit einer Methode assoziiert werden. Wenn das Lesen mit einem Feld assoziiert ist, dann wird beim Einsetzen der Eigenschaft in einen Ausdruck der Wert des Feldes herangezogen. Wenn das Schreiben mit einem Feld assoziiert ist, dann wird beim Zuweisen eines Wertes an die Eigenschaft dieser Wert in das entsprechende Feld geschrieben. In beiden FДllen muс das Feld vom gleichen Typ sein wie die Eigenschaft. Wird das Lesen einer einfachen Eigenschaft mit einer Methode assoziiert, so muс die Methode eine parameterlose Funktion sein, deren Ergebnistyp dem Typ der Eigenschaft entspricht. Der Compiler ersetzt jede Benutzung der Eigenschaft in einem Ausdruck durch einen Aufruf der Methode und bezieht das Funktionsergebnis dann in den Ausdruck ein. Wird das Schreiben einer einfachen Eigenschaft mit einer Methode assoziiert, so muс diese Methode eine Prozedur sein, deren einziger Parameter dem Typ der Eigenschaft entspricht. Der Compiler ersetzt jede Оnderung des Wertes der Eigenschaft in Form einer Zuweisung durch einen Aufruf der Methode, wobei er den neuen Wert der Eigenschaft als aktuellen Parameter der Methode einsetzt. Die Methoden, die einer Eigenschaft zugrundeliegen, erhalten in Speed-Pascal Бblicherweise einen Bezeichner, der dem Bezeichner der Eigenschaft entspricht, wobei der lesenden Methode ein Get und der schreibenden ein Set vorangestellt wird. Sowohl Felder als auch Methoden, die einer Eigenschaft zugrundeliegen, sind meist private. Folgendes Beispiel demonstriert die Arbeit mit einer Eigenschaft, die auf zwei Methoden basiert. type TCustomer = class(TObject) private FName: PString; FID: Integer; function GetName: string; procedure SetName(const S: string); public constructor Create(Name: string); destructor Destroy; override; property ID: Integer // Auf diese Eigenschaft kann nur lesend read FID; // zugegriffen werden. Sie liefert den // Wert des Feldes FID. property Name: string // Auf diese Eigenschaft kann lesend und read GetName write SetName; // schreibend zugegriffen werden. Sie // ruft implizit die Methoden GetName // und SetName auf. end; ... function TCustomer.GetName: string; begin Result := FName^; end; procedure TCustomer.SetName(const S: string); begin AssignStr(FName, S); end; var Customer: TCustomer; begin ... with Customer do begin WriteLn('ID: ', ID); // Liest implizit das Feld FID aus. WriteLn('Name: ', Name); // Ruft implizit die Funktion GetName auf. end; ... end. Zugriff auf Eigenschaften Der Zugriff auf Eigenschaften eines Objekts entspricht aus der Sicht des Benutzers dem Zugriff auf Felder. Der Wert der Eigenschaft wird gelesen, indem die Eigenschaft in AusdrБcken eingesetzt wird. Eine Eigenschaft erhДlt einen neuen Wert durch eine Zuweisung. Falls einer der beiden Zugriffsarten eine Methode zugrunde liegt, wird diese an den entsprechenden Stellen des Programms automatisch aufgerufen. Da Eigenschaften nicht notwendigerweise tatsДchlich vorhandene Felder zugrundeliegen, kann der Operator @ nicht benutzt werden, um die Adresse einer Eigenschaft zu ermitteln. Ebenso ist es nicht mФglich, eine Eigenschaft als aktuellen Parameter an eine Prozedur oder Funktion zu Бbergeben, die dort einen var-Parameter erwartet. Der Compiler fДngt diese Fehler ab. Indizierte Eigenschaften Eine indizierte Eigenschaft stellt sich dem Benutzer wie ein Array dar. Bei der Deklaration einer indizierten Eigenschaft wird zwischen ihrem Bezeichner und ihrem Typ in eckigen Klammern eine beliebige Liste von formalen Parametern angegeben, Бber die die Eigenschaft indiziert wird. Dabei ist jeder Typ erlaubt, der in der formalen Parameterliste einer Prozedur erlaubt ist; indizierte Eigenschaften sind nicht wie Arrays auf AufzДhlungstypen beschrДnkt. Die mit dem Lesen und Schreiben einer indizierten Eigenschaft assoziierten Aktionen mБssen Methoden sein. Sie werden auf die gleiche Weise angegeben wie bei einfachen Eigenschaften, aber sie mБssen besondere Bedingungen erfБllen:  Die mit dem Lesen der Eigenschaft assoziierte Methode muс eine Funktion sein. Ihre formale Parameterliste muс der in eckigen Klammern angegebenen Parameterliste der Eigenschaft entsprechen. Der Typ des Funktionsergebnisses muс dem Typ der Eigenschaft entsprechen.  Die mit dem Schreiben der Eigenschaft assoziierte Methode muс eine Prozedur sein. Ihre formale Parameterliste muс der in eckigen Klammern angegebenen Parameterliste der Eigenschaft entsprechen. ZusДtzlich muс sie einen weiteren Parameter am Ende ihrer Parameterliste besitzen, dessen Typ mit dem Typ der Eigenschaft Бbereinstimmt. type TStrings = class(TObject) protecedD function GetValue(const Name: string): string; virtual; procedure SetValue(const Name, Value: string); virtual; ... public property Values[const Name: string]: string read GetValue write SetValue; end; ... var MyStrings: TStrings; begin ... MyStrings.Values['Compiler'] := 'Speed-Pascal/2 Version 2.0'; WriteLn(MyStrings.Values['Compiler']); ... end. Indizierte Eigenschaften dБrfen nicht als published gekennzeichnet werden, weil der Compiler fБr sie keine Laufzeitinformationen erzeugen kann. Voreingestellte Eigenschaften Es ist mФglich, eine indizierte Eigenschaft zur voreingestellten Eigenschaft der Klasse zu erklДren. Dies geschieht durch AnhДngen des SchlБsselwortes default an die Deklaration der Eigenschaft. Auf eine voreingestellte Eigenschaft kann zugegriffen werden, ohne daс der Bezeichner der Eigenschaft angegeben werden muс. Der gewБnschte Index der Eigenschaft folgt in eckigen Klammern und ohne Punkt unmittelbar auf den Bezeichner der Instanzvariablen. Damit stellt sich dem Benutzer das gesamte Objekt wie ein Array dar. Er kann jedoch weiterhin auf alle Komponenten des Objektes Бber deren Bezeichner zugreifen. type TStrings = class(TObject) protected function Get(Index: LongInt): string; virtual; abstract; procedure Put(Index: LongInt; const S: string); virtual; abstract; ... public property Strings[Index: LongInt]: string read Get write Put; default; ... end; ... var MyStrings: TStrings; begin ... MyStrings[1] := 'Speed-Pascal/2 Version 2.0'; WriteLn(MyStrings[1]); ... end. Eine Klasse kann nur eine voreingestellte Eigenschaft besitzen. Die Voreinstellung einer Eigenschaft vererbt sich an Nachfahren, so daс die Eigenschaft auch dort voreingestellt ist und bleibt. Auch die Sichtbarkeit einer voreingestellten Eigenschaft kann in Nachfahren nicht geДndert werden. Beachten Sie, daс der Angabe von default zum Festlegen einer voreingestellten Eigenschaft ein Semikolon vorangeht, im Gegensatz zum Festlegen des Standardwertes einer Eigenschaft im Objekt-Inspektor, der ebenfalls mit dem SchlБsselwort default, aber ohne Semikolon angegeben wird. Gemeinsame Zugriffsmethoden Das SchlБsselwort index wird derzeit nicht unterstБtzt. Eigenschaften und Laufzeitinformationen Die SchlБsselwФrter default, nodefault und stored werden derzeit nicht unterstБtzt. ═══ 1.8. Metaklassen ═══ ▄▄▄▄▄▄▄▄▄▄▄ Metaklassen ▀▀▀▀▀▀▀▀▀▀▀ Was sind Metaklassen? Eine Metaklasse ist ein Datentyp, dessen Werte Klassen sind. Оhnlich wie eine Klasse einen Typ definiert, der Objektreferenzen aufnehmen kann, kann eine Variable vom Typ einer Metaklasse Referenzen auf Klassen enthalten. Deshalb werden Metaklassen auch als Klassenreferenztypen bezeichnet. Der Wertebereich einer Metaklasse umfaсt die Klasse, fБr die sie deklariert wurde, und alle deren Nachfahren, insbesondere auch jene, die erst zu einem spДteren Zeitpunkt deklariert werden. Einer Variablen vom Typ einer Metaklasse kФnnen also genau die Klassen als Werte zugewiesen werden, die mit der Klasse zuweisungskompatibel sind, fБr die die Metaklasse deklariert wurde. Metaklassen kФnnen Бberall dort im Programm eingesetzt werden, wo auch direkt mit Klassen operiert wird. Das betrifft insbesondere den Aufruf von Klassenmethoden, das PrБfen und Wandeln von Typen mit den Operatoren is und as, sowie das polymorphe Konstruieren von Objekten. In allen FДllen muс die tatsДchliche Klasse nicht bereits zur Zeit der Compilierung feststehen, sondern es wird die Klasse benutzt, die zur Laufzeit als Wert der Variablen vorgefunden wird. Deklarieren von Metaklassen Metaklassen werden auf die folgende Weise deklariert: Eine Metaklasse wird mit den SchlБsselwФrtern class of deklariert. Die Klasse, zu der die Metaklasse gehФrt, muс bereits deklariert sein, zumindestens mit einer forward-Deklaration. type TVehicle = class ... end; TVehicleClass = class of TVehicle; Die Unit System deklariert bereits eine Metaklasse TClass als class of TObject. Benutzen von Metaklassen Variablen vom Typ einer Metaklasse kФnnen als Werte alle Klassen aufnehmen, die mit der Klasse, fБr die die Metaklasse deklariert wurde, zuweisungskompatibel sind. ZusДtzlich kann einer solchen Variablen der Wert nil zugewiesen werden, um anzudeuten, daс die Variable momentan keine gБltige Klasse referenziert. type TVehicle = class(TObject); ... end; TShip = class(TVehicle) ... end; TVehicleClass = class of TVehicle; var AllowedVehicles: TVehicleClass; begin ... AllowedVehicles := TVehicle; ... AllowedVehicles := TShip; ... end. Variablen vom Typ einer Metaklasse kФnnen Бberall dort im Programm eingesetzt werden, wo auch direkt mit Klassen operiert wird. Insbesondere kФnnen sie auf der rechten Seite eines der Operatoren is und as auftauchen, bei Aufruf von Klassenmethoden und beim Konstruieren von Objekten.  Metaklassen und Klassenoperatoren Metaklassen kФnnen auf der rechten Seite eines Ausdrucks benutzt werden, der mit den Operatoren is und as gebildet wird. Es gelten dabei prinzipiell die gleichen Regeln wie bei der direkten Ang  Metaklassen und Klassenmethoden Metaklassen kФnnen benutzt werden, um eine Klassenmethode aufzurufen. Der entsprechende Variablenbezeichner wird dabei gefolgt von einem Punkt vor den Methodenbezeichner gesetzt, analog zum Aufruf der Methode Бber einen Klassenbezeichner. Der tatsДchliche Typ der Klasse steht dann nicht bereits zur Zeit der Compilierung fest, sondern wird erst zur Laufzeit der Variablen entnommen. Es wird die Klassenmethode aufgerufen, die dem tatsДchlichen Wert der Klassenreferenz entsricht. Achten Sie darauf, daс Klassenmethoden, die Sie auf diese Weise benutzen wollen, als virtuell gekennzeichnet sind. Ansonsten findet die Bindung wie gewohnt bereits zur Zeit der Compilierung statt. Es wird dann stets die Methode der Klasse aufgerufen, zu der die Metaklasse der Variablen deklariert wurde.  Metaklassen und Konstruktoren Metaklassen kФnnen beim Aufruf eines Konstruktors zum Erzeugen eines neuen Objekts benutzt werden. Bedingung dafБr ist, daс die Zuweisungsvariante des Konstruktoraufrufes gewДhlt wird, da die einfache Variante nur die Angabe einer Instanzvariablen, nicht aber einer Klasse erlaubt. Diese Art des Aufrufes von Konstruktoren ermФglicht das polymorphe Konstruieren von Objekten, also von Objekten, deren tatsДchlicher Typ nicht bereits zur Zeit der Compilierung feststeht. Die Klasse, von der die neue Instanz erzeugt wird, wird zur Laufzeit der Variablen entnommen. Achten Sie darauf, daс Konstruktoren, die Sie zum polymorphen Konstruieren von Objekten benutzen wollen, als virtuell gekennzeichnet sind. Ansonsten findet die Bindung wie gewohnt bereits zur Zeit der Compilierung statt. Es wird dann stets eine Instanz der Klasse erzeugt, zu der die Metaklasse der Variablen deklariert wurde. type TVehicle = class constructor Create; virtual; end; TShip = class(TVehicle) constructor Create; override; end; TVehicleClass = class of TVehicle; var VehicleType: TVehicleClass; MyVehicle: TVehicle; begin ... VehicleType := TShip; MyVehicle := TVehicleType.Create; ... end. ═══ 1.9. Vordefinierte Klassen ═══ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ Vordefinierte Klassen ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ TObject und TClass Die in der Unit System deklarierte Klasse TObject ist die Basis der Objekthierarchie von Speed-Pascal. TObject besitzt rudimentДre Konstruktoren und Destruktoren sowie eine Reihe von Methoden, die fБr die Zusammenarbeit von Objekten mit der Sibyl-Umgebung wichtig sind. TObject ist letztlich Vorfahr jeder anderen Klasse. Wenn bei der Deklaration einer neuen Klasse nicht explizit ein Vorfahr angegeben wird, dann nimmt der Compiler TObject als Vorfahren an. Damit ist sichergestellt, daс alle Objekte auf eine gemeinsame Basis zurБckgreifen kФnnen. TClass ist die Metaklasse von TObject. Da alle anderen Klassen aufgrund ihrer Abstammung zuweisungskompatibel zu TObject sind, kann eine Variable vom Typ TClass als Werte sДmtliche Klassen aufnehmen, die jemals mit Speed-Pascal deklariert werden. Deklaration von TObject und TClass Die Klasse TObject ist zusammen mit ihrer Metaklasse TClass in der Unit System auf die folgende Weise deklariert: type TObject = CLASS; TClass = CLASS OF TObject; TObject = CLASS CONSTRUCTOR Create; DESTRUCTOR Destroy; VIRTUAL; PROCEDURE Free; CLASS FUNCTION NewInstance: TObject; VIRTUAL; PROCEDURE FreeInstance; VIRTUAL; CLASS FUNCTION InitInstance(Instance: Pointer): TObject; CLASS FUNCTION ClassType: TClass; CLASS FUNCTION ClassName: STRING; CLASS FUNCTION ClassUnit: STRING; CLASS FUNCTION ClassParent: TClass; CLASS FUNCTION GetClassInfo: POINTER; CLASS FUNCTION InstanceSize: LONGWORD; CLASS FUNCTION InheritsFrom(AClass: TClass): BOOLEAN; PROCEDURE DefaultHandler(VAR Message); VIRTUAL; PROCEDURE DefaultFrameHandler(VAR Message); VIRTUAL; PROCEDURE Dispatch(VAR Message); PROCEDURE DispatchCommand(VAR Message;Command:LONGWORD); PROCEDURE FrameDispatch(VAR Message); CLASS FUNCTION MethodAddress(CONST Name: STRING): POINTER; CLASS FUNCTION MethodName(Address: POINTER): STRING; FUNCTION FieldAddress(Name: STRING): POINTER; END; Methoden von TObject Viele Methoden von TObject werden entweder intern vom Laufzeitsystem benФtigt oder dienen zum Austausch von Informationen mit der visuellen Entwicklungsumgebung Sybil und der SPCC-Bibliothek. Eine Reihe von Methoden, unter anderem Konstruktor, Destruktor und die Methoden zur Botschaftsverarbeitung, kФnnen und sollen vom Benutzer verwendet werden.  CONSTRUCTOR Create; Der Konstruktor Create erzeugt eine neue, leere Instanz von TObject und fБllt sie durch einen Aufruf von InitInstance mit Nullbytes. In Nachfahren von TObject mБssen Sie den Konstruktor mit hoher Wahrscheinlichkeit Бberschreiben, um zusДtzliche Felder des Objektes korrekt zu initialisieren. Wenn bei der Initialisierung alle Felder auf Null gesetzt werden, oder die neue Klasse keine Felder besitzt, dann kann auch der Konstruktor von TObject diese Aufgabe Бbernehmen.  DESTRUCTOR Destroy; VIRTUAL; Der Destruktor Destroy zerstФrt die Instanz und gibt den von ihr belegten Speicher auf dem Heap frei. Wenn in einem Nachfahren von TObject keine weiteren Aktionen zum ZerstФren einer Instanz notwendig sind, dann kann der Destruktor von TObject auch als Destruktor fБr diese Klasse benutzt werden. Wenn die Klasse komplexe Unterstrukturen besitzt, die aufgerДumt werden mБssen, dann benФtigt die Klasse einen eigenen Destruktor. Es wird empfohlen, daс jede Klasse, die einen Destruktor benФtigt, Destroy prinzipiell als parameterlosen Destruktor beibehДlt und mittels override Бberschreibt.  PROCEDURE Free; Die Methode Free prБft eine Instanz auf nil und ruft genau dann den Destruktor Destroy auf, wenn der Wert der Instanz nicht nil ist, die Variable also ein gБltiges Objekt referenziert. Benutzen Sie Free, um mehr Sicherheit beim ZerstФren eines Objektes zu erhalten. Ein direkter Aufruf eines Destruktors auf einer Instanzvariablen, die nil enthДlt, bricht das Programm mit einem Laufzeitfehler ab. Die Verwendung von Free verhindert diesen Fehler.  CLASS FUNCTION NewInstance: TObject; VIRTUAL; Diese Methode wird derzeit nicht verwendet.  PROCEDURE FreeInstance; VIRTUAL; Diese Methode wird derzeit nicht verwendet.  CLASS FUNCTION InitInstance(Instance: Pointer): TObject; Die Klassenmethode InitInstance fБllt die Бbergebene Objektinstanz mit Nullbytes, wodurch alle ihre Felder zurБckgesetzt werden. Die Methode wird beim Erzeugen eines neuen Objektes vom Konstruktor Create aufgerufen.  CLASS FUNCTION ClassType: TClass; Die Klassenmethode ClassType liefert tatsДchlichen Typ einer Klasse oder einer Klassenreferenz. Wird die Methode auf einem Objekt aufgerufen, dann ist dies die tatsДchliche Klasse des Objekts zur Laufzeit.  CLASS FUNCTION ClassName: STRING; Die Klassenmethode ClassName liefert den Namen einer Klasse. Wird die Methode auf einem Objekt aufgerufen, dann ist dies der Name der tatsДchlichen Klasse des Objekts zur Laufzeit. Der Name wird komplett in Groсbuchstaben zurБckgegeben.  CLASS FUNCTION ClassUnit: STRING; Die Klassenmethode ClassUnit liefert den Namen des Moduls, in dem eine Klasse deklariert wurde.  CLASS FUNCTION ClassParent: TClass; Die Klassenmethode ClassParent liefert den tatsДchlichen Typ des unmittelbaren Vorfahren einer Klasse oder einer Klassenreferenz zurБck. Wird die Methode auf einem Objekt aufgerufen, dann ist dies der Vorfahre der tatsДchlichen Klasse des Objekts.  CLASS FUNCTION GetClassInfo: POINTER; Die Klassenmethode GetClassInfo liefert einen Zeiger auf interne Informationen Бber die Klasse. Achtung: Bei Borland Delphi heiсt diese Klassenmethode ClassInfo. Sie wurde in Speed-Pascal umbenannt, da das OS/2-API bereits eine Funktion mit dem gleichen Namen bereitstellt.  CLASS FUNCTION InstanceSize: LONGWORD; Die Klassenmethode InstanceSize liefert die GrФсe einer Instanz einer Klasse.  CLASS FUNCTION InheritsFrom(AClass: TClass): BOOLEAN; Die Klassenmethode InheritsFrom prБft, ob die Klasse, auf der die Methode aufgerufen wird, ein direkter oder indirekter Nachfahre von AClass ist. Sie gibt entsprechend True oder False zurБck.  PROCEDURE Dispatch(VAR Message); Die Methode Dispatch dient zum Verteilen von Nachrichten an botschaftsverarbeitende Methoden. Dispatch sucht die zur Identifikation der Nachricht passende Methode heraus und ruft diese auf. Existiert weder in der tatsДchlichen Klasse des Objekts noch in einem beliebigen Vorfahren eine Methode, die die Nachricht behandeln kann, dann wird die Methode DefaultHandler aufgerufen, die eine Standardbehandlung der Nachricht durchfБhrt.  PROCEDURE DispatchCommand(VAR Message;Command Die Methode DispatchCommand dient zum Verteilen von Nachrichten des Typs WM_COMMAND an botschaftsverarbeitende Methoden. DispatchCommand sucht die zur Identifikation der Nachricht passende Methode heraus und ruft diese auf. Existiert weder in der tatsДchlichen Klasse des Objekts noch in einem beliebigen Vorfahren eine Methode, die die Nachricht behandeln kann, dann wird die Methode DefaultHandler aufgerufen, die eine Standardbehandlung der Nachricht durchfБhrt.  PROCEDURE FrameDispatch(VAR Message); Die Methode FrameDispatch dient zum Verteilen von Nachrichten an botschaftsverarbeitende Methoden. Im Gegensatz zu Dispatch wird FrameDispatch fБr Nachrichten benutzt, die an das Rahmenfenster eines Bildschirmobjektes gehen sollen. FrameDispatch fБr sucht die zur Identifikation der Nachricht passende Methode heraus und ruft diese auf. Existiert weder in der tatsДchlichen Klasse des Objekts noch in einem beliebigen Vorfahren eine Methode, die die Nachricht behandeln kann, dann wird die Methode DefaultFrameHandler aufgerufen, die eine Standardbehandlung der Nachricht durchfБhrt.  PROCEDURE DefaultHandler(VAR Message); VIRTUAL; Die Methode DefaultHandler wird von Dispatch aufgerufen, wenn keine Methode zur Behandlung einer bestimmten Botschaft gefunden werden konnte. DefaultHandler tut nichts, wird aber von graphischen Bildschirmelementen derart Бberschrieben, daс eine Routine des Betriebssystems aufgerufen wird, die eine Standardbehandlung der Nachricht durchfБhrt.  PROCEDURE DefaultFrameHandler(VAR Message); VIRTUAL; Die Methode DefaultFrameHandler wird von FrameDispatch aufgerufen, wenn keine Methode zur Behandlung einer bestimmten Botschaft gefunden werden konnte. DefaultFrameHandler tut nichts, wird aber von graphischen Bildschirmelementen derart Бberschrieben, daс eine Routine des Betriebssystems aufgerufen wird, die eine Standardbehandlung der Nachricht durchfБhrt.  CLASS FUNCTION MethodAddress(CONST Name: STRING): POINTER; Die Klassenmethode MethodAddress ermittelt die Einsprungadresse einer Methode anhand von deren Namen.  CLASS FUNCTION MethodName(Address: POINTER): STRING; Die Klassenmethode MethodName ermittelt den Namen einer Methode anhand ihrer Einsprungadresse.  FUNCTION FieldAddress(Name: STRING): POINTER; Die Methode FieldAddress liefert die Adresse eines Feldes anhand von dessen Namen. ═══ 1.10. Exceptions ═══ ▄▄▄▄▄▄▄▄▄▄ Exceptions ▀▀▀▀▀▀▀▀▀▀ Was sind Exceptions? Exceptions sind ein Hilfsmittel zur Behandlung von Ausnahmesituationen in Programmen, insbesondere zur Fehlerbehandlung. Wenn eine Exception ausgelФst wird, verzweigt das Programm sofort zu einem Exception-Handler, der auf den Fehler reagieren kann. Existiert kein solcher Exception-Handler dann wird das Programm mit einer Fehlermeldung abgebrochen. Durch die Benutzung von Exceptions wird der normale Kontrollfluс des Programms vom Kontrollfluс fБr FehlerfДlle getrennt, wodurch das Programm besser strukturiert und leichter zu warten wird. Exceptions sind Objekte. Neue Exceptions kФnnen von der Vererbung Gebrauch machen, wodurch sich eine baumartige Hierarchie von Exceptions ergibt. Von dieser Struktur profitieren Exception-Handler, indem sie viele spezielle Exceptions, die einen gemeinsamen Vorfahren besitzen, gleichzeitig behandeln. Als Objekte kФnnen Exceptions auсerdem beliebige weitere Komponenten besitzen, zum Beispiel Felder fБr zusДtzliche Informationen, die beim AuslФsen der Exception gefБllt werden. Auch von diesen Informationen kann der Exception-Handler Gebrauch machen, etwa indem eine detaillierte Fehlermeldung angezeigt wird. Deklarieren von Exceptions Die Deklaration einer Exception entspricht der Deklaration einer Klasse, da Exceptions Objekte sind. Exceptions sollten, mБssen aber nicht unbedingt von der vordefinierten Klasse Exception oder einer ihrer Nachfahren abstammen. In sehr vielen FДllen ist nur die Art des aufgetretenen Fehlers relevant. Diese findet sich dank der zur Laufzeit verfБgbaren Typinformationen bereits in der Klasse der Exception wieder. Deshalb werden Exceptions meist sehr einfach deklariert: type EMyException = class(Exception); Es ist in Speed-Pascal Бblich, die Bezeichner von Exceptions stets mit dem Buchstaben 'E' zu beginnen. Genau wie gewФhnliche Klassen kФnnen auch Exceptions durch geschickte Ausnutzung der Vererbung hierarchisch strukturiert werden. Die vordefinierten Exceptions unterteilen sich zum Beispiel in Prozessorfehler, Speicherfehler, Ein- / Ausgabefehler und mathematische Fehler bei Integer- und Flieсkommaoperationen. Dieses System sollten Sie auch bei neuen Exceptions beibehalten, da es Бbersichtlicher ist und die Implementierung von Exception-Handlern erleichtert. Wenn Sie eine Reihe von Exceptions fБr einen neuen Anwendungsfall oder fБr eine neue Klasse deklarieren, dann tun sie dies, indem Sie zuerst eine Basis-Exception einfБhren, von der Sie die weiteren ableiten. type ENetworkError = class(Exception); EWrongUserName = class(ENetworkError); EWrongPassword = class(ENetworkError); Es ist mФglich, einer Exception beliebige Komponenten in Form von Feldern, Methoden oder Eigenschaften hinzuzufБgen. Diese Komponenten, meist werden es Felder mit detaillierten Fehlerinformationen sein, kФnnen dann vom Exception-Handler benutzt werden. Die Basisklasse Exception besitzt bereits Felder zur Aufnahme einer Fehlermeldung und einer Fehleradresse. Die Exceptions, die sich mit Ein- / Ausgabefehlern beschДftigen, fБgen ein Feld mit dem Fehlercode der fehlgeschlagenen Operation hinzu. type EInOutError = class(Exception) ErrorCode: Integer; end; Achtung: Im Gegensatz zu Borland Delphi mБssen Sie nicht die zusДtzliche Unit SysUtils einbinden, um die Fehlerbehandlung Бber Exceptions zu aktivieren. SДmtliche Fehler des Laufzeitsystems werden bereits Бber Exceptions abgewickelt. Der Basistyp Exception ist in der Unit System deklariert. AuslФsen von Exceptions Eine Exception wird durch das SchlБsselwort raise und die Angabe einer Exception-Instanz ausgelФst. Diese Instanz kann und wird meistens genau an dieser Stelle durch einen Konstruktor-Aufruf erzeugt. Es ist aber durchaus mФglich, wenngleich meist БberflБssig, die Instanz vorher zu erzeugen. Der Konstruktor von Exception erwartet als Parameter einen String. Sie kФnnen darin beim AuslФsen der Exception eine Fehlermeldung angeben. Sollte das Programm mangels eines passenden Exception-Handlers abgebrochen werden, gibt es zuvor diese Meldung aus. if Password <> 'top secret' then raise EWrongPassword.Create('Illegal password.'); Achtung: Im Unterschied zu Borland Delphi ist es derzeit nicht mФglich, eine Fehleradresse mit Hilfe des SchlБsselwortes at anzugeben. Als Fehleradresse wird stets die Adresse innerhalb des Codesegmentes angenommen, an der die Exception ausgelФst wurde. Behandeln von Exceptions Wenn Sie nicht wollen, daс Ihr Programm beim Auftreten einer Exception mit einer Fehlermeldung abbricht, dann mБssen Sie auf die Exception reagieren. Dazu Бberwachen Sie kritische Codesequenzen und fБgen ihnen einen Exception-Block hinzu. Der Exception-Block enthДlt Code, der nur im Fehlerfall ausgefБhrt werden soll. Er kann einen einen oder mehrere Exception-Handler bereitstellen, die Fehler innerhalb der Бberwachten Codesequenz behandeln. Wenn innerhalb des Бberwachten Abschnitts eine Exception ausgelФst wird, tritt sofort der Exception-Block in Aktion. Wird der Бberwachte Abschnitt fehlerfrei beendet, wird die AusfБhrung hinter dem Exception-Block wiederaufgenommen. Bei verschachtelten Бberwachten Codesequenzen genieсt stets der Exception-Block die hФchste PrioritДt, der zur innersten Бberwachten Sequenz gehФrt. Behandelt er eine aufgetretene Exception nicht, wird sie nach auсen durchgereicht. Ъberwachte Codesequenzen und Exception-Handler Eine Anweisungsfolge, die von den SchlБsselwФrtern try..except eingeschlossen wird, ist eine Бberwachte Codesequenz. Wenn innerhalb dieser Codesequenz eine Exception ausgelФst wird, dann verzweigt das Programm unmittelbar zu der Anweisungsfolge, die sich zwischen den SchlБsselwФrtern except..end befindet und als Exception-Block bezeichnet wird. Dieser Exception-Block kann einen oder mehrere Exception-Handler bereitstellen, um bestimmte Fehler zu behandeln. Er kann auch einen universellen Exception-Handler beinhalten, der jede Art von Fehler behandelt. try /*Die folgenden Anweisungen sind kritisch. Wenn die Datei nicht geФffnet werden kann, oder sich wДhrend des Lesens Probleme einstellen, dann wird eine Exception ausgelФst. Das Programm verzweigt dann unmittelbar zum EXCEPT..END- Block, der den Fehler behandeln solle. */ Assign(F, 'c:\userdata\report.txt'); Reset(F); ... Close(F); except ... end; Der Exception-Block kann auf die Fehlerbedingung reagieren, muс dies aber nicht tun. Ob ein Exception-Block auf eine bestimmte Art von Exception reagiert, kann vom Programmierer festgelegt werden. Im allgemeinsten Fall werden die Exceptions nicht weiter differenziert. Es ergibt sich ein universeller Exception-Handler, der jede Art von Exception behandelt. except /*Dies ist ein universeller Exception-Handler. Der Code zwischen EXCEPT..END wird in jedem Fehlerfall ausgefБhrt. Der Fehler wird dadurch beseitigt. */ WriteLn('Unbekannter Fehler beim Lesen der Datei.'); end; Durch Einsatz der SchlБsselwФrter on..do kann die Arbeit des Exception-Blockes auf eine oder mehrere Arten von Exceptions eingeschrДnkt werden. Jedes on..do definiert einen Handler fБr eine bestimmte Klasse von Exceptions. except /*Dieser Exception-Handler bearbeitet nur Fehler vom Typ 'EFileNotFound'. Alle weiteren Exceptions reicht er an die nДchsthФhere Ebene weiter. */ on EFileNotFound do WriteLn('Die angegebene Datei existiert nicht.'); end; Es kФnnen mit on..do beliebig viele Exception-Handler angegeben werden. Das Laufzeitsystem geht sie im Fehlerfall der Reihe nach durch. Sobald ein passender Exception-Handler gefunden wird, wird dessen Code ausgefБhrt, und der Fehler wird beseitigt. Es wird niemals mehr als ein Exception-Handler ausgefБhrt. Beachten Sie, daс ein Exception-Handler auch immer alle Nachfahren der Klasse behandelt, fБr die er installiert wurde. Deshalb sollten speziellere Exception-Handler in der Liste stets weiter vorn stehen, da sie sonst niemals erreicht werden. Ausgehend davon, daс alle Exceptions, die im Zusammenhang mit Dateioperationen stehen, einen gemeinsamen Vorfahren EInOutError besitzen, kФnnte eine Fehlerbehandlung etwa wie folgt aussehen: except /*Diese Exception-Handler bearbeiten eine Reihe von Fehlern. Die spezielleren Handler stehen dabei weiter vorn, die allgemeineren am Ende der Liste. */ on EFileNotFound do WriteLn('Die angegebene Datei existiert nicht.'); on EAccessDenied do WriteLn('Zugriff auf die Datei verweigert.'); on EInvalidFilename do WriteLn('UngБltiger Dateiname.'); on EInOutError do WriteLn('Ein-/Ausgabefehler beim Lesen der Datei.'); end; Bei Angabe einer Liste von Exception-Handlern mit on..do ist es auсerdem mФglich, im Anschluс an den letzten Exception-Handler mit else einen weiteren anzugeben, der alle Exceptions behandelt, auf die nicht bereits einer der speziellen Handler reagiert hat. except /*Diese Exception-Handler bearbeiten eine Reihe von Fehlern. Die spezielleren Handler stehen dabei weiter vorn, die allgemeineren am Ende der Liste. Fehler, die nicht mit der Ein-/Ausgabeoperation in Zusammenhang stehen, werden durch den ELSE-Teil behandelt. */ on EFileNotFound do WriteLn('Die angegebene Datei existiert nicht.'); on EAccessDenied do WriteLn('Zugriff auf die Datei verweigert.'); on EInvalidFilename do WriteLn('UngБltiger Dateiname.'); on EInOutError do WriteLn('Ein-/Ausgabefehler beim Lesen der Datei.'); else WriteLn('Unbekannter Fehler beim Lesen der Datei.'); end; Wird eine Exception innerhalb des Exception-Blocks behandelt, dann wird sie beseitigt, und die AusfБhrung wird unmittelbar hinter dem except..end-Block wiederaufgenommen. Behandelt keiner der Exception-Handler den aufgetretenen Fehler, dann bleibt die Exception bestehen. Der eingebaute Exception-Handler von Speed-Pascal tritt in Aktion und beendet das Programm mit einer Fehlermeldung, es sei denn, der gesamte Abschnitt befindet sich innerhalb einer weiteren mittels try..except Бberwachten Codesequenz. Tritt innerhalb der Бberwachten Codesequenz gar keine Exception auf, dann verzweigt das Programm nach Beendigung des try..except-Blockes unmittelbar hinter das Ende des Exception-Blockes. Es besteht in diesem Fall keine Veranlassung, die Fehlerbehandlung aufzurufen. Beachten Sie, daс es sich bei der Verzweigung im Fehlerfall um einen unmittelbaren Sprung handelt, nicht um einen Prozeduraufruf. Der Stack wird soweit aufgerДumt, daс er sich im gleichen Zustand befindet wie beim Betreten der Бberwachten Codesequenz. Danach wird die Exception-Behandlung angesprungen. Nach der Behandlung der Exception kann kein RБcksprung an die Fehleradresse mehr stattfinden. Stattdessen wird die AusfБhrung hinter dem except..end-Block wiederaufgenommen. Geschachtelte Exception-Handler Es ist mФglich, Бberwachte Codesequenzen zu schachteln. Dies kann entweder direkt durch Schachtelung von try..except-Anweisungen geschehen oder indirekt dadurch, daс innerhalb der Бberwachten Codesequenz aufgerufene Prozeduren oder Funktionen selbst lokal Codesequenzen Бberwachen. In beiden FДllen werden die zugehФrigen except..end-BlФcke auf eine Art Stapel gelegt, wobei sich der zum zuletzt betretenen try..except-Block gehФrende zuoberst befindet. Tritt eine Exception auf, so wird zum obersten auf dem Stapel befindlichen except..end-Block verzweigt. Dieser gehФrt zu genau der Бberwachten Codesequenz, die zuletzt betreten und noch nicht wieder beendet wurde. Falls hier ein Exception-Handler existiert, der die aufgetretene Exception behandelt, findet die Behandlung statt, und die Exception wird gelФscht. Die AusfБhrung wird hinter dem except..end-Block wiederaufgenommen. Behandelt keiner der Exception-Handler die aufgetretene Exception, so wird der except..end-Block vom Stapel abgerДumt, und es wird zum nДchsten except..end-Block verzeigt. Dies geschieht solange, bis ein Exception-Handler den aufgetretenen Fehler behandelt. Existiert kein solcher Exception-Handler, tritt die eingebaute Fehlerbehandlung von Speed-Pascal in Aktion. Das Programm wird abgebrochen. AuslФsen von Exceptions im Exception-Handler Bei Schachtelung von Бberwachten Codesequenzen kann es sinnvoll sein, lokal auf den Fehler zu reagieren, aber die Fehlerbedingung bestehen zu lassen, damit die nДchsthФhere Ebene auch darauf reagieren kann. In diesem Fall kann innerhalb des except..end-Blockes durch Einsatz von raise ohne jeden weiteren Parameter dafБr gesorgt werden, daс die aufgetretene Exception unmittelbar wieder ausgelФst wird. Der except..end-Block wird vom Stapel abgerДumt, und es wird so verfahren, als wДre die Exception nicht behandelt worden. Diese spezielle syntaktische Variante von raise kann nur innerhalb eines Exception-Handlers benutzt werden. Es ist auch mФglich, mittels der normalen Syntax von raise eine andere Exception innerhalb eines except..end-Blockes auszulФsen. Wird diese Exception nicht lokal innerhalb des except..end-Blockes behandelt, dann verdrДngt sie die ursprБngliche Exception. Diese wird gelФscht, der except..end-Block wird vom Stapel abgerДumt, und die Behandlung der neuen Exception wird an die nДchsthФhere Ebene verwiesen. Dies kann manchmal sinnvoll sein, wenn mehrere lokal auftretende Exceptions abgefangen und in einen gemeinsamen Typ von Exception umgewandelt werden sollen, der von der nДchsthФheren Ebene behandelt wird. Es kann aber auch zu unerwБnschten Ergebnissen fБhren, wenn nicht beachtet wird, daс innerhalb eines - mФglicherweise sehr komplexen - Exception-Handlers wiederum Fehler auftreten kФnnen. In diesem Fall sollten Sie den kritischen Bereich innerhalb des Exception-Handlers wiederum in einen try..except-Block mit anschlieсendem except..end-Block einschlieсen. Exceptions, die innerhalb des inneren Exception-Handlers nicht bearbeitet werden, verdrДngen die Exception des Дuсeren Handlers und werden an die nДchsthФhere Ebene verwiesen. Zugriff auf die Exception-Instanz Es ist mФglich, innerhalb eines Exception-Handlers auf die Exception-Instanz zuzugreifen. Das ist sinnvoll, wenn die aufgetretene Exception zusДtzliche Informationen enthДlt, die vom Handler ausgewertet werden sollen. Zum Zugriff auf die Exception-Instanz kann einem durch on..do eingeleiteten Exception-Handler ein Bezeichner gefolgt von einem Doppelpunkt vorangestellt werden. Innerhalb dieses einen Exception-Handlers steht dann unter dem angegebenen Bezeichner die Instanz der aufgetretenen Exception zur VerfБgung. Der Typ des Bezeichners entspricht der Exception-Klasse, die der Handler bearbeitet, ist also eine passende Objektreferenz. Das folgende Beispiel soll die MФglichkeiten verdeutlichen. Die Exception-Klasse EInOutError und deren Nachfahren besitzen ein Feld ErrorCode, das den Fehlercode des aufgetretenen Fehlers enthДlt. Diesen Fehlercode kФnnte sich ein Exception-Handler zunutze machen. try Assign(F, 'c:\userdata\report.txt'); Reset(F); ... Close(F); except on E: EInOutError do WriteLn('Ein-/Ausgabefehler ', E.ErrorCode, ' beim Lesen der Datei.'); else WriteLn('Unbekannter Fehler beim Lesen der Datei.'); end; Beachten Sie, daс es nicht nФtig ist, die Variable mittels var zu deklarieren. Der Bezeichner wird an der entsprechenden Stelle generiert und ist nur innerhalb des zugehФrigen Exception-Handlers gБltig. Achtung: Sie haben Бber den Bezeichner auch Zugriff auf alle Methoden der Instanz. Gehen Sie sehr vorsichtig mit dieser MФglichkeit um. Versuchen Sie insbesondere niemals, die Exception-Instanz mittels Destroy oder Free selbst zu lФschen. Das ist Aufgabe des Exception-Handlers. Der Versuch, dies 'von Hand' zu erledigen, wБrde das Laufzeitsystem derart durcheinander bringen, daс das Programm mit hoher Wahrscheinlichkeit abstБrzt. SchБtzen von Resourcen Wenn innerhalb einer fehleranfДlligen Codesequenz Resourcen belegt werden, dann ist es notwendig, diese auch beim Auftreten einer Exception wieder freigeben zu kФnnen. Anderenfalls gehen die Resourcen mФglicherweise dauerhaft verloren, da das Auftreten der Exception einen direkten Sprung zum Exception-Handler zufolge hat. Die Anweisungen, die die Benutzung der Resource abschlieсen und diese freigeben, werden im Fehlerfall gar nicht erreicht. Die try..finally-Anweisung dient dazu, die Freigabe von Resourcen auch im Fehlerfall zu garantieren. Um zu garantieren, daс eine bestimmte Codesequenz auch beim Auftreten einer Exception ausgefБhrt wird, gehen Sie wie folgt vor:  Setzen Sie die Anweisungen, die die Resource allokieren, vor den try..finally-Block.  Schlieсen Sie die Arbeit mit der Resource in den try..finally-Block ein.  Schlieсen Sie die Anweisungen zum Freigeben der Resource in den finally..end-Block ein. /*Zuerst wird die Resource allokiert. In diesem Fall wird versucht, eine Datei zu Фffnen. */ Assign(F, 'c:\userdata\report.txt'); Reset(F); try // Lesen der Datei. MФglicherweise treten hier Fehler auf. ... finally // Die Datei soll in jedem Fall geschlossen werden. Close(F); end; Wird innerhalb des try..finally-Blocks eines Exception ausgelФst, verzweigt das Programm zuerst zum finally..end-Block. Wenn dieser beendet ist, wird die Exception an den zustДndigen Exception-Handler weitergereicht, falls ein solcher existiert. Beachten Sie, daс die Exception innerhalb eines finally..end-Abschnittes nicht behandelt wird. Dieser Bereich ist kein Exception-Handler, er garantiert nur die AusfБhrung einer Reihe von Anweisungen unabhДngig davon, ob ein Fehler auftritt oder nicht. Um auf den Fehler zu reagieren, muс der gesamte Abschnitt geschБtzt und mit einem Exception-Handler versehen werden. Es ist nicht mФglich, finally und except zu kombinieren, etwa derart, daс der except-Block auf den finally-Block folgt. try /*Zuerst wird die Resource allokiert. In diesem Fall wird versucht, eine Datei zu Фffnen. */ Assign(F, 'c:\userdata\report.txt'); Reset(F); try // Lesen der Datei. MФglicherweise treten hier Fehler auf. ... finally // Die Datei soll in jedem Fall geschlossen werden. Close(F); end; except WriteLn('Es trat ein Fehler beim Lesen der Datei auf.'); end; Es ist mФglich, try..finally-BlФcke ineinander zu schachteln. Es ist dann auf jeder Ebene sichergestellt, daс die Anweisungen zwischen finally..end ausgefБhrt werden, bevor zur nДchsthФheren Ebene verzweigt wird. Achtung: Falls innerhalb eines finally..end-Blockes eine Exception ausgelФst wird, die dort nicht lokal behandelt wird, verdrДngt sie die ursprБngliche Exception und verzeigt zum nДchsten zustДndigen Exception-Handler. Das Verhalten ist zwar definiert, aber es ist nicht empfehlenswert, daс Sie davon Gebrauch machen. Stellen Sie stattdessen sicher, daс Exceptions, die in finally..end-BlФcken auftreten, auch dort behandelt werden. Exceptions und Objekte Exceptions kФnnen ohne EinschrДnkung innerhalb von Objektmethoden verwendet werden. Es gibt jedoch eine Besonderheit zu beachten. Wenn innerhalb eines Konstruktor-Aufrufes eine Exception auftritt, die dort nicht behandelt wird, dann wird zuerst der Destruktor aufgerufen, bevor zum mФglicherweise vorhandenen Exception-Handler verzweigt wird. Damit verhalten sich Konstruktoren exakt so, als wДren sie auf die folgende Weise implementiert: constructor TMyClass.Create; begin try // GewБnschte Anweisungen zwischen BEGIN und END except Destroy; raise; end; end; Aufgrund dieses Verhaltens ist es wichtig, daс ein Destruktor auch mit nicht vollstДndig initialisierten Objekten umzugehen weiс, denn er kann theoretisch von jeder beliebigen Stelle des Konstruktors aus aufgerufen werden. ═══ 1.11. Vordefinierte Exceptions ═══ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ Vordefinierte Exceptions ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ Basisklasse Exception SДmtliche Laufzeitfehler von Speed-Pascal werden Бber Exceptions abgewickelt. Dazu sind in der Unit System bereits eine Reihe von Exceptions vordefiniert. Exception = class(TObject) public constructor Create(const Msg: String); destructor Destroy; override; property Message: string; // lesen / schreiben property MessagePtr: PString; // nur lesen end; ExceptClass = class of Exception. Exception ist die Basisklasse fБr Ausnahmebedingungen. Von ihr sollten alle weiteren Exception-Klassen abgeleitet werden. Exception selbst wird selten ausgelФst, meist werden es spezialisierte Nachkommen sein, die auf bestimmte Fehler hinweisen. Alle Exception-Klassen erben von Exception Konstruktor, Destruktor sowie die Eigenschaft Message, die die Fehlermeldung enthДlt, die beim AuslФsen der Exception an den Konstruktor Бbergeben wird. Software-generierte Exceptions  EProcessTerm = class(Exception); EProcessTerm wird vom Betriebssystem ausgelФst, wenn der Prozeс beendet werden soll. Hardware-generierte Exceptions  EProcessorException = class(Exception); EProcessorException ist der Vorfahr fБr weitere vom Prozessor ausgelФste Exceptions. Es wird niemals EProcessorException selbst ausgelФst, sondern stets ein Nachkomme, der auf eine bestimmte Ausnahmebedingung hinweist. Im Unterschied zu EFault weist EProcessorException nicht unbedingt auf einen Fehler hin, denn die Nachfahren EBreakpint und ESingleStep dienen zum Debuggen eines Programms.  EFault = class(EProcessorException); EFault ist der Vorfahr fБr alle vom Prozessor ausgelФsten Exceptions, die auf einen Fehler hinweisen. Es wird niemals EFault ausgelФst, sondern stets ein Nachkomme, der auf eine bestimmte Fehlerbedingung hinweist. Im Gegensatz zu EProcessorException weist EFault immer auf einen Fehler hin.  EGPFault = class(EFault); EGPFault wird ausgelФst, wenn eine allgemeine Schutzverletzung auftritt. Eine Schutzverletzung liegt vor, wenn ein Prozeс auf Speicherbereiche zugreift, die nicht innerhalb seines Adreсraums liegen. Meist deutet dieser Fehler darauf hin, daс eine illegale Zeigeroperation durchgefБhrt wurde oder daс mit einem nicht korrekt initialisierten Objekt gearbeitet wurde.  EStackFault = class(EFault); EStackFault wird ausgelФst, wenn nicht genБgend Stackspeicher vorhanden ist, um die aktuelle Operation durchzufБhren. Prozeduren und Funktionen reservieren beim Aufruf den Speicher fБr ihre lokalen Variablen auf dem Stack. Wenn dieser Fehler auftritt, dann teilen Sie dem Programm mehr Stackspeicher zu oder vermeiden Sie die Benutzung zu vieler lokaler Variablen, insbesondere bei Rekursion. Diese Exception kann nur auftreten, wenn die StackprБfung aktiviert ist, die Operation also im Modus $S+ compiliert wird.  EPageFault = class(EFault); EPageFault wird vom Prozessor ausgelФst, wenn ein Seitenfehler auftritt. Ein Seitenfehler liegt vor, wenn der Prozeс auf eine Speicherseite zugreifen will, die momentan ausgelagert ist. Dieser Fehler sollte normalerweise nicht auftreten, da sich das Betriebssystem um die Verwaltung des physikalischen und virtuellen Speichers kБmmert und die entsprechende Seite automatisch wieder einlagert.  EInvalidOpCode = class(EFault); EInvalidOpCode wird vom Prozessor ausgelФst, wenn dieser auf eine illegale Maschineninstruktion trifft.  EBreakpoint = class(EProcessorException); EBreakpoint wird vom Prozessor ausgelФst, wenn dieser auf einen Breakpoint trifft.  ESingleStep = class(EProcessorException); ESingleStep wird vom Prozessor nach jedem durchgefБhrten Befehl ausgelФst, wenn das Programm im Einzelschrittmodus abgearbeitet wird. Exceptions bei Speicher-Operationen  EOutOfMemory = class(Exception); EOutOfMemory wird ausgelФst, wenn eine angeforderte Menge Speicher nicht mehr auf dem Heap zur VerfБgung steht.  EInvalidPointer = class(Exception); EInvalidPointer wird ausgelФst, wenn ein Programm versucht, einen Speicherbereich zu deallokieren, der nicht zuvor allokiert wurde oder bereits freigegeben ist.  EInvalidHeap = class(Exception); EInvalidHeap wird ausgelФst, wenn der Heapbereich der Applikation beschДdigt ist. Dies kann vorkommen, wenn die Applikation durch fehlerhafte Zeigeroperationen Speicherbereiche Бberschreibt, die fБr die Heapverwaltung wichtig sind. Exceptions bei Ein- / Ausgabeoperationen  EInOutError = class(Exception) public ErrorCode: Integer; end; EInOutError ist der Vorfahr fБr eine Reihe von Exceptions, die auf Ein- / Ausgabe-Fehler hinweisen. Alle Exceptions dieser Art besitzen ein zusДtzliches Feld, das den Fehlercode aufnehmen kann, der bei der Operation aufgetreten ist. Damit diese Exception und ihre Nachfahren auftreten kФnnen, muс die Ein- / Ausgabe-ЪberprБfung aktiviert sein, die entsprechende Operation muс also im Modus $I+ compiliert werden.  EAccessDenied = class(EInOutError); EAccessDenied wird ausgelФst, wenn das Щffnen einer Datei fehlschlДgt, weil entweder die Attribute der Datei ein Щffnen zum Schreiben nicht erlauben, oder weil die Datei bereits von einem anderen Prozeс exklusiv geФffnet wurde.  EDiskFull = class(EInOutError); EDiskFull wird ausgelФst, wenn die Diskette oder Festplatte, auf die der Prozeс schreiben will, voll ist.  EEndOfFile = class(EInOutError); EEndOfFile wird ausgelФst, wenn der Prozeс versucht, Бber das Ende einer Datei hinaus zu lesen.  EFileNotFound = class(EInOutError); EFileNotFound wird ausgelФst, wenn der Prozeс versucht, eine Datei zu Фffnen, die nicht existiert.  EInvalidFileName = class(EInOutError); EInvalidFileName wird ausgelФst, wenn ein illegaler Dateiname benutzt wird, der zum Beispiel zu lang ist oder Sonderzeichen enthДlt, die nicht Teil eines Dateinamens sein dБrfen.  EInvalidInput = class(EInOutError); EInvalidInput wird ausgelФst, wenn der Benutzer des Programms eine ungБltige Eingabe macht.  ETooManyOpenFiles = class(EInOutError); ETooManyOpenFiles wird ausgelФst, wenn zu viele Dateien gleichzeitig geФffnet sind. Wenn der Fehler auftritt, erhФhen Sie die entsprechende Einstellung des Betriebssystems, um mehr gleichzeitig geФffnete Dateien zu erlauben. Mathematische Exceptions bei Integer-Operationen  EIntError = class(Exception); EIntError ist der gemeinsame Vorfahr einer Reihe von Exceptions, die auf Fehler bei mathematischen Operationen mit Integer-Werten hinweisen.  EDivByZero = class(EIntError); EDivByZero wird ausgelФst, wenn eine Division durch Null versucht wird.  ERangeError = class(EIntError); ERangeError wird ausgelФst, wenn fБr das Ergebnis einer mathematischen Operation eine BereichsprБfung fehlschlДgt. Damit diese Exception auftreten kann, muс die Operation im Modus $R+ compiliert werden.  EIntOverflow = class(EIntError); EIntOverflow wird ausgelФst, wenn das Ergebnis einer mathematischen Operation einen Ъberlauf produziert hat. Damit dieser Fehler auftreten kann, muс die Operation im Modus $Q+ compiliert werden. Mathematische Exceptions bei Flieсkomma-Operationen  EMathError = class(Exception); EIntError ist der gemeinsame Vorfahr einer Reihe von Exceptions, die auf Fehler bei mathematischen Operationen mit Integer-Werten hinweisen.  EInvalidOp = class(EMathError); EInvalidOp wird ausgelФst, wenn eine ungБltige mathematisch Operation versucht wird, z.B. das Ziehen der Wurzel aus einer negativen Zahl.  EZeroDivide = class(EMathError); EZeroDivide wird ausgelФst, wenn eine Division durch Null versucht wird.  EOverflow = class(EMathError); EOverflow wird ausgelФst, wenn das Ergebnis einer mathematischen Operation einen Ъberlauf produziert hat.  EUnderflow = class(EMathError); EUnderflow wird ausgelФst, wenn das Ergebnis einer mathematischen Operation so klein ist, daс es mit der PrДzision der Ergebnisvariablen nicht mehr dargestellt werden kann. Dieser Fehler tritt normalerweise nicht auf. Stattdessen liefert Speed-Pascal den Wert Null als Ergebnis zurБck. Durch Оndern des Kontrollwortes des mathematischen Koprozessors kann das AuslФsen dieser Exception jedoch ermФglicht werden. Exceptions bei Typwandlungen  EInvalidCast = class(Exception); EInvalidCast wird ausgelФst, wenn eine Typwandlung mit Hilfe des Operators as fehlschlДgt.  EConvertError = class(Exception); EConvertError wird bei einer Reihe von Konvertierungsfunktionen ausgelФst, wenn der Quellwert ungБltig ist.