53
An imprint of Pearson Education München Boston San Francisco Harlow, England Don Mills, Ontario Sydney Mexico City Madrid Amsterdam Delphi 2006 Autor Elmar Warken

Delphi 2006 - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Embed Size (px)

Citation preview

Page 1: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

An imprint of Pearson Education

München • Boston • San Francisco • Harlow, EnglandDon Mills, Ontario • Sydney • Mexico CityMadrid • Amsterdam

Delphi 2006

AutorElmar Warken

Page 2: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

3 Die Sprache Delphi in der .NET-Umgebung

Dieses Kapitel beschäftigt sich mit der Sprache Delphi– auch bekannt unter dem Namen Object Pascal – undderen Umsetzung in .NET-Klassen und -Assemblies.Das Kapitel soll keine Sprachreferenz darstellen (einesolche ist bereits Teil von Delphis Online-Dokumenta-tion, im Inhalt zu finden unter Borland Hilfe – DeveloperStudio 2006 (Allgemein) – Referenz – Delphi-Referenz –Delphi-Sprachreferenz) und kann nicht alle Details derSprache aufführen. Vielmehr liegt das Ziel des Kapitelsdarin, Umsteigern und Aufsteigern eine aktuelle Ein-führung zu bieten, Hintergrundwissen zu den Zusam-menhängen zwischen Delphi und der CLR (CommonLanguage Runtime: gemeinsame Laufzeitumgebungvon .NET-Anwendungen) zu vermitteln sowie mindes-tens alle Eigenschaften der Sprache zu erläutern, die indiesem Buch verwendet werden (unter Einschränkungauf diesen Bereich ist das Kapitel also auch zum Nach-schlagen geeignet).

Wir beginnen in diesem Kapitel nicht im Kleinen beiden Typen, Variablen und Anweisungen von Delphi,sondern im Großen bei den Assemblies (Kapitel 3.1)und dem Objektmodell (Kapitel 3.2–3.4), bevor dann abKapitel 3.5 die kleinen Details behandelt werden, alsoVariablen, Konstanten, Typen, Anweisungen, Metho-dendeklarationen und Exceptions.

Page 3: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

288

EinführungObject Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist eineNachfolgesprache von »Borland Pascal mit Objekten«, die 1989 mit Turbo-Pascal 5.5eingeführt wurde, einer Entwicklungsumgebung, die noch unter DOS und kurze Zeitspäter mit »Turbo Pascal für Windows« auch unter Windows lief. Seit für die erste Del-phi-Version von 1995 grundlegende Erweiterungen und Änderungen an der Sprachevorgenommen wurden, ging die Zahl der mit jeder neuen Delphi-Version eingeführtenNeuerungen langsam zurück – die Sprache stabilisierte sich auf hohem Niveau.

Für den Schritt zu .NET sind in Delphi 8 wieder einige wichtige Spracherweiterungenhinzugekommen, doch sieht die Situation so aus, dass einen Umsteiger von C++ zu C#wesentlich mehr Neuerungen erwarten als einen Umsteiger von Delphi 7 zu Delphifür .NET, was zum einen Teil an dem erwähnten »hohen Niveau« von Delphi 7 liegt,zu dem die Sprachen von Microsoft ja erst einmal aufschließen mussten, zum anderenTeil daran, dass Borland es sehr gut gelungen ist, Detailunterschiede zwischen .NETund Delphi vor dem Programmierer zu verbergen, was besonders der Abwärtskompa-tibilität mit früheren Delphi-Versionen und der Portabilität zu anderen Delphi-Platt-formen wie Win32 und Linux zugute kommt.

Neuerungen gegenüber Delphi 7Dieses Kapitel erläutert folgende sprachliche Neuerungen gegenüber dem letztenDelphi vor dem .NET-Zeitalter:

�  die Auswirkungen der Garbage Collection, Portierung der alten Object-Pascal-Frei-gabetechniken wie Destruktoren und manuelle Freigabe mit Free (Kapitel 3.3.2)

�  verschachtelte Typdeklarationen (Kapitel 3.2.8)

�  das Konzept von Wert- und Verweistypen (Kapitel 3.6.6)

�  Boxing (Kapitel 3.6.6)

�  Records als Werttyp-Klassen mit Methoden (Kapitel 3.6.5)

�  Klassenvariablen, Klassenproperties, Klassenkonstruktoren (Kapitel 3.2.5)

�  kleine Erweiterungen: strict private, strict protected (Kapitel 3.2.2) und sealed (Kapitel3.2.6)

�  überladene Operatoren (Benutzung der überladenen Operatoren: Kapitel 3.6.2,Definition eigener Überladungen: Kapitel 3.8.3)

Page 4: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

289

Delphi und C++ / C#Object Pascal enthielt bereits in früheren Delphi-Versionen zahlreiche Merkmale, die inC++ noch nicht vorhanden waren, jetzt aber von C# in mehr oder weniger ähnlicherForm unterstützt werden:

�  virtuelle Konstruktoren, die das Konzept der Polymorphie auch bei der Konstruk-tion von Objekten anwenden (Kapitel 3.3.5) – in C# gibt es keine virtuellen Kon-struktoren, jedoch können Sie in .NET-Sprachen mit Hilfe von Reflexion dengleichen Effekt erzielen, indem Sie für ein Type-Objekt mit InvokeMember einen Kon-struktor »virtuell« aufrufen.

�  Methodenzeiger, die praktischer und effektiver sind als die Methodenzeiger in C++(Kapitel 3.8.4) – in C# finden Sie diese unter der Bezeichnung der Delegaten,genauer gesagt: ein Methodenzeiger, wie er schon von Delphi 1 unterstützt wurde,entspricht in C# dem Exemplar (einer »Instanz«) eines Delegat-Typen.

�  Exceptions, die den Exceptions im alten C-Stil entsprechen – ein Vorteil gegenüberden C++-Exceptions ist die finally-Anweisung, die es in C# ebenfalls gibt (Kapitel 3.9)

�  Typinformationen, die über die RTTI von C++ hinausgehen (Kapitel 3.3.4) – dieseTypinformationen gibt es in der .NET-Laufzeitumgebung nun in einem noch größe-ren Stil als bei Delphi 7 (Stichwort Reflection)

�  offene Array-Konstruktoren, mit denen Sie praktisch variable Parameterlistenerhalten (Kapitel 3.8.1)

�  Interfaces: Wie die Interfaces der Sprache Java erreichen sie viele Ziele, die man inC++ nur mit Mehrfachvererbung erreichen konnte, sind aber frei von den großenRisiken dieser C++-Spracheigenschaft (Kapitel 3.4)

Neben den am Anfang schon genannten Highlights von Object Pascal gibt es nocheinige weitere Besonderheiten gegenüber anderen Sprachen, die schon etwas ältersind: Mengen (Kapitel 3.6.5), offene Arrays (Kapitel 3.8.1) und die with-Anweisung(siehe Kapitel 3.7).

3.1 Namespaces und AssembliesWie schon angedeutet, strebt Borland mit Delphi für das .NET-Framework ein hohesMaß an Abwärtskompatibilität zu früheren Delphi-Versionen und Portabilität zu Del-phi für Windows und Linux an. Dies führt dazu, dass die bekannten Delphi-Begriffewie Units und Packages in die .NET-Welt übertragen und dort mit den .NET-Konzep-ten wie etwa den Namespaces und Assemblies in Einklang gebracht werden. Bevorwir zur Sichtweise von Delphi kommen, fasst der erste Teil dieses Kapitels die erfor-derlichen .NET-Grundbegriffe zusammen.

Page 5: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

290

3.1.1 Grundbegriffe der .NET-PlattformDieses Kapitel erläutert zunächst die Begriffe Assembly und Namespace und betrachtetanschließend die verwaltete Umgebung der CLR, in der die Assemblies ausgeführtwerden. Dabei werden auch weitere Begriffe wie der der Garbage Collection und desCommon Type Systems eingeführt, die in späteren Kapiteln noch eine bedeutendereRolle spielen werden.

AssembliesAssemblies sind der Grundbaustein einer .NET-Anwendung. In vielen Fällen entsprichteine Assembly einfach einer dynamisch linkbaren DLL oder einer ausführbaren EXE-Datei, jedoch ist das Konzept der Assembly weiter gefasst als das einer Datei: EineAssembly kann mehrere Dateien umfassen, die konzeptionell zu einem Baustein zusam-mengefasst werden. Als Assembly haben sie etwa eine gemeinsame Versionsnummerund eine gemeinsame »Privatsphäre« – es gibt in .NET das besondere Sichtbarkeitsattri-but Assembly, das einen Bezeichner nur aus der Assembly heraus ansprechbar macht.

Während die Einteilung in Assemblies überwiegend die Laufzeit des Programms, alsoseine physische Struktur betrifft (Assemblies werden als in sich abgeschlossene Bau-steine geladen und etwa bei einem Update als Einzelbaustein ersetzt), gibt es eineandere Einteilung, die sich nur auf die logische Programmstruktur bezieht und dievorwiegend zur Entwurfszeit relevant ist:

NamespacesNamespaces (Namensräume) dienen zur Gliederung der mit zunehmender Größeimmer schwerer überschaubaren Klassenbibliotheken in logische Kategorien. So wer-den etwa Klassen, die mit der Ausgabe von Grafik zu tun haben, in dem NamespaceSystem.Drawing zusammengefasst, Steuerelemente für Windows-Anwendungen imNamespace System.Windows.Forms, z.B. die ListBox und der Button. Nun gibt es andereSteuerelemente, die speziell für Webanwendungen gedacht sind, und auch hier heißendiese ListBox und Button, obwohl es völlig verschiedene Klassen sind. Über die Name-spaces lassen sich nun die Erstgenannten von den Letztgenannten unterscheiden: Sys-tem.Windows.Forms.ListBox ist das Listensteuerelement für Anwendungen, die auf demRechner des Anwenders laufen – sie wird mit der Grafikschnittstelle GDI+ auf demBildschirm ausgegeben; System.Web.UI.WebControls.ListBox ist das Listen-Control fürWebanwendungen, die auf einem Webserver laufen – sie wird via HTML zum Client-Rechner übertragen.

Die Verschiedenheit der Konzepte Namespace/Assembly zeigt sich darin, dass einNamespace mehrere Assemblies umfassen, aber auch eine Assembly mehrere Name-spaces enthalten kann. Genau genommen kann eine Assembly einen Namespace gar

Page 6: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

291

nicht richtig »enthalten«, denn der Namespace ist offen dafür, von anderen Assemblieserweitert zu werden. Korrekt ausgedrückt muss es daher heißen: Eine Assembly kannNamen aus mehreren Namespaces enthalten, die jedoch ihrerseits wieder über ver-schiedene Assemblies verstreut sein können.

Die Namespace-HierarchieDurch die Punkt-Schreibweise wird eine Hierarchie von Namespaces aufgebaut: DerNamespace System.Drawing.Printing z.B. ist in dieser Hierarchie dem Namespace Sys-tem.Drawing untergeordnet und dieser wiederum dem Namespace System – man kannauch sagen, der untergeordnete Namespace sei im übergeordneten Namespace »ent-halten«.

Diese hierarchische Enthaltens-Beziehung ist jedoch ausschließlich konzeptionellerNatur und dient den menschlichen Besuchern der .NET-Welt zum besseren Verständ-nis. So gibt es beispielsweise die Konvention, dass der enthaltene Namespace vomübergeordneten Namespace abhängig sein soll, aber nicht umgekehrt1.

Für die CLR spielen die Enthaltens-Beziehungen keine Rolle: Enthaltene Namespaceskönnen in eigenen Assemblies, getrennt vom umgebenden Namespace, abgelegt wer-den und diese Assemblies können grundsätzlich unabhängig von den Assemblies desumgebenden Namespaces installiert, geladen oder deinstalliert werden. Normaler-weise benutzt ein untergeordneter Namespace jedoch den übergeordneten Name-space, die entsprechenden Assemblies können dann nur geladen werden, wenn auchdie benötigten Assemblies des übergeordneten Namespaces vorhanden sind. DieseAbhängigkeit wird jedoch nicht durch den Namespace-Namen, sondern durch eineexplizite Anweisung zur Einbindung des Namespaces aufgestellt (diese Anweisungheißt in C# using, in Delphi uses).

Verwalteter Code und die CLRDer von Delphi für .NET erzeugte Code ist ein Zwischencode in der von Microsoftdefinierten Intermediate Language (MSIL), der erst zur Laufzeit von der CLR in endgül-tigen Maschinencode übersetzt wird – genau zugeschnitten auf den gerade verwende-ten Prozessor. Daher kann eine .NET-Anwendung nur auf einem System ausgeführtwerden, auf dem das .NET-Framework installiert ist.

Code, der von der CLR ausgeführt wird, wird auch als verwalteter Code bezeichnet. Auchder als unverwalteter Code bezeichnete Code, wie er von Win32-Delphi erzeugt wird, istnatürlich in gewisser Weise verwaltet, allerdings hauptsächlich vom Prozessor und nichtso sehr vom Betriebssystem, welches sich im Wesentlichen darauf beschränkt, den Code

1 Mehr dazu findet sich in der .NET-Online-Dokumentation unter dem Titel »Richtlinien zur Benen-nung von Namespaces«.

Page 7: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

292

an eine bestimmte Stelle im Hauptspeicher zu laden und eventuell vom Prozessor gemel-dete Schutzverletzungen zu behandeln, z.B. wenn das Programm über einen falschgesetzten Zeiger auf Speicherbereiche des Betriebssystems zugreifen will.

Das Maß an »Verwaltung« hingegen, welches dem verwalteten Code in der CLR zuteilwird, erreicht ganz andere Dimensionen: Die CLR kennt über die in der Assemblygespeicherten Metadaten jede Variable und jeden Typ des Programms und kann denCode bis in kleinste Details analysieren. Zugriffe auf fremde Speicherbereiche sind garnicht möglich, da es im verwalteten Code keine Zeiger gibt und die CLR auch überArray-Zugriffe wacht und Zugriffe außerhalb der gültigen Grenzen gar nicht zulassenwürde. Mit anderen Worten: Verwalteter Code kann eigentlich nur noch dann eineSchutzverletzung des Prozessors auslösen, wenn die CLR nicht richtig funktioniert.

Weitere Funktionsmerkmale der CLR, die zum Bereich der »Verwaltung« gezählt wer-den, sind die automatische Speicherfreigabe (Garbage Collection) und die Überwa-chung von Sicherheitsregeln, welche etwa einer Klasse, die aus dem Internet stammtund im Webbrowser ausgeführt wird, bestimmte Systemzugriffe wie das Speichernvon Dateien verbieten.

Das Common Type SystemAufgrund der strengen Regeln, die für verwaltete Anwendungen von der CLR auto-matisch überwacht werden, ist es nicht mehr notwendig, dass Anwendungen in eige-nen, voneinander abgeschotteten Speicherbereichen liegen, wie es in modernenBetriebssystemen sonst der Fall ist. Stattdessen können alle Anwendungen in einengemeinsamen Speicherbereich geladen werden und ein gemeinsames Exemplar derCLR benutzen. Der große Vorteil davon ist, dass die verschiedenen Anwendungen vieleinfacher miteinander kommunizieren können: Während abgeschottete Anwendungennur über trickreiche Mechanismen Daten untereinander austauschen können, können.NET-Anwendungen alle Objekte gemeinsam nutzen.

Das Revolutionäre daran ist, dass es keine Rolle spielt, in welcher Sprache die Objekteimplementiert sind: Eine C#-Anwendung kann Objekte einer Delphi-Anwendung nut-zen, und diese wiederum kann auf Objekte einer VB.NET-Anwendung zugreifen.Mehr noch: Sie können in Delphi eine Klasse schreiben, die eine C#-Klasse erweitert,wobei diese C#-Klasse ihrerseits eine Erweiterung einer .NET-Klasse darstellt.

Der Schlüssel zu dieser gemeinsamem Objektnutzung ist das Common Type System(CTS) der CLR: Es definiert das Objektmodell einer jeden .NET-Anwendung sowie diegrundlegenden Typen, mit denen jede .NET-Anwendung arbeiten muss bzw. ausdenen sie komplexere Typen und Klassen zusammensetzen kann. Der Delphi-TypSmallInt ist beispielsweise nur eine Umbenennung des .NET-Typs Int16. Jede Delphi-Klasse hat unter ihren Vorfahren zumindest die .NET-Klasse Object. Und alle ande-ren.NET-Sprachen machen auf ihre Weise Gebrauch von int16 und Object.

Page 8: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

293

Das CTS ist also ein gemeinsames Typsystem für alle .NET-Sprachen, es stellt zur Lauf-zeit gemeinsame Implementierungen aller Basistypen bereit, die von allen .NET-Anwendungen genutzt werden, und da alle .NET-Anwendungen aus Klassen beste-hen, die Klassen des CTS erweitern, kann man jede .NET-Anwendung selbst als Teil(oder als Erweiterung) des CTS auffassen.

Weitere Details zu den Zusammenhängen zwischen CTS-Typen und Delphi-Typen fin-den Sie in Kapitel 3.6.

3.1.2 Namespaces in DelphiAls Delphi-Entwickler brauchen Sie sich über die Zusammenhänge zwischen Name-spaces und Assemblies hauptsächlich dann Gedanken zu machen, wenn Sie »fremde«Namespaces und Assemblies verwenden. Bei der Programmierung in Delphi ergebensich Namespaces und Assemblies aus den älteren Object-Pascal-Begriffen der Unit, desProgramms und des Packages:

�  Jedes Delphi-Projekt wird in eine Assembly kompiliert und enthält somit dieNamespaces, die durch seine Units festgelegt werden.

�  Für jede Unit legt Delphi automatisch einen Namespace mit dem Namen der Unitan: MyUnit ist der Namespace einer Unit MyUnit.pas, und wenn diese sich in einemProjekt MyProject.dpr befindet, wird für die Projektdatei ein weiterer Namespacemit dem Namen MyProject angelegt. Eine manuelle Beeinflussung der Namespace-Benennung ist durch die Benennung der Units möglich.

Namespaces festlegenDa Sie bei der Benennung von Units und Programmen gleichzeitig auch die Namespace-Namen festlegen, haben die Sprachdesigner bei Borland nun auch Punkte innerhalb derNamen von Programmen und Units erlaubt, so dass Sie eine Projektdatei z.B. Meine-Firma.MeinProgramm.dpr nennen und eine Unit mit dem Namen MeineFirma.MeinPro-gramm.UI.Dialoge.Login.pas versehen können. Aus einem Unit-Namen, in dem Punktevorkommen, bildet der Compiler den Namespace-Namen, indem er das letzte Wort undden letzten Punkt entfernt:

// Erste Zeile in der .dpr-Datei:program MeineFirma.MeinProgramm; // -> Namespace heißt MeineFirma

// Erste Zeile in der .pas-Datei:unit MeineFirma.MeinProgramm.UI.Dialoge.Login; // -> Namespace heißt MeineFirma.MeinProgramm.UI.Dialoge

Page 9: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

294

Trotz der Punkte kennt der Delphi-Compiler den Namen einer solchen Unit nur alsGanzes: Die Verwendung eines separaten Bezeichners wie etwa MeineFirma oder Loginwürde er als unbekannt reklamieren.

Sie können die in den von Delphi erzeugten Assemblies vorkommenden Namespacesübrigens mit dem Reflection-Tool überprüfen. Dabei wird jeder Namespace durch diePunkte in seinem Namen in Hierarchie-Ebenen zerlegt und für jede Ebene ein eigenesOrdnersymbol angezeigt. Wirklich in der Assembly vorhanden sind nur die Name-spaces, deren gelbe Ordnersymbole weitere Unterknoten haben – mindestens ein Sym-bol namens »Unit«.

Wenn Sie bereits mit Delphi 8 gearbeitet haben, ändern sich mit dem Umstieg auf Delphi 2005 oder Delphi 2006 die vom Compiler generierten Namespaces: In Delphi 8 wurde noch nicht der letzte Teil des Namens weggelassen, so dass beispielsweise im letzten Beispiel auch das ».Login« noch zum Namespace-Namen gehört. Wenn man bedenkt, dass Namespaces Container für Typen sind und in einer Unit Login etwa ein Formulartyp LoginForm deklariert sein könnte, macht der neue Benennungsmechanismus mehr Sinn: Der vollständig qualifizierte Bezeichner für das For-mular wäre dann MeineFirma.MeinProgramm.UI.Dialoge.LoginForm statt dem noch längeren und am Schluss redundanten MeineFirma.MeinProgramm.UI.Dialoge.Login.LoginForm.

Abbildung 3.1: Delphi-Units im Projektmanager und die zugehörigen Namespaces im Reflection-Tool

> > > HINWEIS

Page 10: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

295

In Abbildung 3.1 etwa wurden zum Vergleich drei ähnliche Unit-Namen in ein Projekteingebunden (siehe die Auflistung im Projektmanager rechts unten im Bild), das Pro-jekt wurde kompiliert und die exe-Datei in das Reflection-Tool geladen.

Namespaces und Assemblies nutzenUm in einem Programm auf die Symbole eines anderen Namespaces zugreifen zu kön-nen, muss dieser Namespace mit uses in alle Units eingebunden werden, in denen dieSymbole aus diesem Namespace verwendet werden sollen. Die Unit MonitorForm ausdem Beispielprogramm AuslastungsMonitor (Kapitel 1.5.4) verwendet eine ganze Reihevon .NET-Klassen und muss daher auch einige Namespaces einbinden:

unit MonitorForm;

interface

uses System.Drawing, System.Collections, System.ComponentModel, System.Windows.Forms, System.Data, System.Diagnostics, Microsoft.Win32, System.Runtime.InteropServices, System.Globalization;

Zusätzlich muss der Compiler wissen, in welchen Assemblies er die Namespaces(genauer: die aus den eingebundenen Namespaces verwendeten Symbole) findenkann. Hierzu müssen entsprechende Referenzen in das Projekt eingefügt sein, undzwar im Projektmanager-Knoten Referenzen (siehe Abbildung 3.2).

Die in der Abbildung gezeigten Assemblies werden beim Anlegen eines neuen Pro-jekts bereits automatisch zum Projekt hinzugefügt, sie enthalten bereits sehr viele

Abbildung 3.2: Die im Projekt referenzierten Assemblies werden im Projektmanager angezeigt.

Page 11: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

296

wichtige .NET-Klassen. Wenn Sie eine neue Komponente in ein Formular einfügen,erweitert Delphi die Liste der referenzierten Assemblies gegebenenfalls um alle vondieser Komponente benötigten Assemblies.

Eine manuelle Erweiterung der Assembly-Liste ist nötig, wenn Sie im Programmcodeauf .NET-Klassen zugreifen wollen, die sich nicht in den standardmäßigen Assembliesbefinden; rufen Sie dann den Befehl REFERENZ HINZUFÜGEN aus dem Kontextmenü desProjektmanagers auf und wählen Sie eine oder mehrere der aufgelisteten Assemblies(Abbildung 3.3). Mit REFERENZ HINZUFÜGEN bringen Sie die gewählten Assemblies indie untere Liste NEUE REFERENZEN, die beim Bestätigen des Dialogs dem Projekt hin-zugefügt werden. Falls sich die von Ihnen benötigte Assembly nicht in der Liste befin-det, können Sie per DURCHSUCHEN-Schalter eine beliebige Assembly aus demVerzeichnissystem suchen.

Jede im Projektmanager genannte Referenz wird übrigens in der Projektdatei genannt(PROJEKT | QUELLTEXT ANZEIGEN):

program AuslastungsMonitor;

{%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\v1.1.4322\System.dll'}

Abbildung 3.3: Wahl zweier Assemblies des .NET-Frameworks, die sich mit der Entwurfszeitfunktio-nalität von Komponenten befassen

Page 12: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

297

{%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\v1.1.4322\System.Data.dll'}...

Statisches und dynamisches LinkenAssemblies des .NET-Frameworks werden immer erst zur Laufzeit, also dynamisch,zum ausführbaren Delphi-Programm gebunden. Die Teile des Delphi-Programms, alsoseine Units, werden schon vom Compiler in die ausführbare .exe-Datei gepackt (stati-sches Linken). Bei den mit Delphi mitgelieferten Units sind Sie jedoch nicht auf diesesstatische Linken beschränkt, sondern Sie können die Delphi-Laufzeitbibliothek auchdynamisch zu Ihrem Programm linken.

Hier kommt erneut der Befehl REFERENZ HINZUFÜGEN des Projektmanagers ins Spiel.Delphis mitgelieferte Bibliotheken bestehen aus einer Reihe von DLLs, die Sie in IhremProjekt referenzieren können. Die Units, die sich in solchen referenzierten DLLs befin-den, brauchen vom Compiler nicht mehr in die .exe-Datei der Anwendung gebundenzu werden.

Ein Beispiel: Die in einer Delphi-Anwendung unverzichtbare Laufzeitbibliothek vonDelphi (Unit System) befindet sich in borland.delphi.dll. Wenn Sie wie in Abbildung3.4 diese DLL zu Ihrem Projekt hinzufügen, erhalten Sie eine kleinere .exe-Datei. Ineiner minimalen WinForms-Anwendung mit einem neu angelegten leeren Formularsinkt ihre Größe von 21 auf 7 Kbyte, das einfachstmögliche Delphi-Projekt einer Kon-solenanwendung ergibt mit dynamischem Linken eine nur 5 Kbyte große ausführbareDatei. Allerdings müssen Sie bei einer Konsolenanwendung, die die Unit SysUtils ver-wendet, auch die DLL borland.vcl referenzieren, um die Einbindung dieser Unit in die.exe-Datei zu vermeiden.

Abbildung 3.4: Auch Delphis Laufzeitbibliothek kann dynamisch gelinkt werden.

Page 13: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

298

Bei dynamisch gelinkten Anwendungen müssen Sie natürlich sicherstellen, dass diereferenzierten Assemblies auf dem Rechner, auf dem die Anwendung ausgeführt wer-den soll, ebenfalls vorhanden sind.

3.1.3 Assemblies in DelphiWie in Kapitel 3.1.2 erwähnt, übersetzt der Delphi-Compiler jedes Delphi-Projekt ineine .NET-Assembly. Der Typ des Delphi-Projekts bestimmt, welche von zwei Artenvon Assemblies erzeugt werden.

EXE-AssembliesHandelt es sich um ein Delphi-Projekt mit der Dateiendung .dpr und beginnt der Pro-jektquelltext mit program, dann wird das Projekt auch als Anwendung bezeichnet undDelphi erzeugt bei der Übersetzung eine ausführbare Assembly, deren Dateiendungwie bei einer ausführbaren Win32-Anwendung .exe lautet. Die Endung weist auchdarauf hin, dass die Datei wie jede andere exe-Datei gestartet werden kann, z.B. übereinen Doppelklick im Windows-Explorer oder über einen Aufruf von der Befehlszeile.Damit dies möglich ist, enthält auch eine .NET-exe-Datei Maschinencode, der aufjedem Win32-System ausgeführt werden kann. Die einzige Aufgabe dieses Maschinen-codes besteht jedoch darin, das Vorhandensein der .NET-CLR auf dem System zu über-prüfen sowie diese im Erfolgsfall aufzurufen, damit sie die eigentliche Anwendungausführen kann.

Die Delphi-IDE verfügt über einige vorgefertigte Projektschablonen für Anwendungs-projekte wie etwa Konsolenanwendung, Windows-Forms-Anwendung oder VCL-Formular-anwendung (zu finden unter DATEI | NEU | WEITERE).

Wenn Sie feststellen wollen, ob sich in Ihrer exe-Datei noch vermeidbarer Ballast befindet, öffnen Sie die Datei einfach in der IDE, um sie im Reflection-Tool anzeigen zu lassen. Wenn sich nicht wie in Abbildung 3.1 ein Borland-Knoten darin befindet, sind keine Borland-Units in der Datei enthalten.

Die Tatsache, dass jede Delphi-Anwendung dynamisch oder statisch mit der System-Unit gelinkt werden muss, ist übrigens normal für alle .NET-Sprachen außer C#. Auch in VB.NET und C++ für .NET sind solche DLLs notwendig, um die von der CLR gelieferte Basis an die jeweilige Spra-che anzupassen. Nur in C# bedarf es keiner solchen Anpassung, weil diese Sprache extra für .NET entworfen wurde.

Die drei angesprochenen Beispiele »WinForms-Anwendung statisch gelinkt«, »WinForms-Anwendung dynamisch gelinkt« und »Konsolen-Anwendung dynamisch gelinkt« finden Sie auf der CD im Verzeichnis KAPITEL3\LINKARTEN.

> > > HINWEIS

Page 14: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

299

DLL-AssembliesDLL-Assemblies erhalten Sie, wenn Sie ein Delphi-Package kompilieren. Ein Package-Projekt wird in einer Quelltextdatei mit der Endung .dpk gespeichert, deren erstesSchlüsselwort package ist. Sie erhalten ein neues .NET-Package in der IDE mit DATEI |NEU | WEITERE | DELPHI FÜR .NET-PROJEKTE | PACKAGE.

Auch ein Package kann Referenzen auf andere Assemblies enthalten, jedoch werdendiese im Projektmanager nicht mehr als Referenzen bezeichnet, sondern unter einemRequires-Knoten aufgelistet (siehe Abbildung 3.5).

Das Beispiel aus der Abbildung (auf der CD zu finden unter Kapitel3\Packages\Form-TestPackage.dpr) ist ein Package, welches ein simples Testformular in einer DLL bereit-stellen soll. Dieses Package wurde in drei einfachen Schritten angelegt:

�  Neuanlegen des Packages mit DATEI | NEU | WEITERE | DELPHI FÜR .NET PROJEKTE

| PACKAGE (bis hier enthielt der Knoten Requires nur die Borland.Delphi.dll)

�  Neuanlegen des Formulars mit DATEI | NEU | WEITERE | DELPHI FÜR .NET PRO-JEKTE | NEUE DATEIEN | WINDOWS FORM (hierdurch kamen alle weiteren Projekt-manager-Einträge hinzu)

�  Hinzufügen eines Labels in das Formular, damit wir es später beim testweisen Auf-ruf wiedererkennen können

Explizite Unit-BenutzungDie wichtigste Regel beim Anlegen eines Packages besteht darin, dass alle im Packageverwendeten Units explizit eingebunden werden müssen, das heißt:

�  Entweder muss sich die Unit in einer DLL befinden, die per Referenz eingebundenwird (im Projektmanager unter Requires aufgelistet – wenn Sie diesem Knoten mit

Abbildung 3.5: Eine Beispiel-DLL mit einem Formular im Projektmanager

Page 15: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

300

REFERENZ HINZUFÜGEN neue Einträge hinzufügen, gelangen Sie in das schon inAbbildung 3.1.3 gezeigte Fenster).

�  Oder die Unit muss unter dem Knoten Enthält aufgeführt sein. Dieser Knotenbesitzt ein Kontextmenü mit einem Punkt HINZUFÜGEN..., der Sie in einen Dialogzur Auswahl der gewünschten Unit führt.

Dabei sollten die Laufzeit-DLLs von Delphi grundsätzlich dynamisch gelinkt werden,denn nur dann kann Ihre Package-DLL in einer Delphi-Anwendung benutzt werden,die ihrerseits die Laufzeitbibliothek benötigt. Würden Sie also den von Delphi unterdem Requires-Knoten angelegten Eintrag Borland.Delphi.dll entfernen, führte dies dazu,dass die Unit Borland.Delphi.System statisch in die DLL eingebunden würde. Sie wäredann bei der Benutzung der DLL aus einer Delphi-Anwendung doppelt vorhanden –einmal in der Anwendung und einmal in der DLL. Der Compiler bricht in einem sol-chen Fall das Kompilieren der Anwendung mit einer Meldung wie der Folgenden ab:

[Fataler Fehler] Package 'FormTestPackage' konnte nicht importiert werden, weil es die System-Unit 'Borland.Delphi.System' enthält.

Benutzung des selbst erzeugten PackagesUm ein Package in Ihr Projekt einzubinden, müssen Sie, wie in Kapitel 3.1.2 beschrie-ben, eine Referenz auf seine DLL zu Ihrem Projekt hinzufügen. Wenn Sie dies unterlas-sen und einfach nur die in dem Package enthaltenen Units mit uses einbinden, bindetder Compiler diese Units direkt in die ausführbare Datei Ihres Projekts ein.

Abbildung 3.6: Eine Beispiel-DLL mit einem Formular im Projektmanager

Page 16: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

301

Abbildung 3.6 zeigt, wie die zuletzt erzeugte DLL FormTestPackage.dll in einer Delphi-Anwendung benutzt wird. Es wurde ein neues WinForms-Projekt angelegt, die DLL mitERFORDERT | REFERENZ HINZUFÜGEN | DURCHSUCHEN in den Projektmanager eingefügtund dann die Unit der DLL per uses-Anweisung in die neue Formular-Unit aufgenom-men (bei der Erweiterung der uses-Anweisung kann sogar die Code-Vervollständigungbenutzt werden).

Dann wurde im Formular der Anwendung ein Button mit der Aufschrift DLL-FORMU-LAR AUFRUFEN erzeugt und mit folgendem trivialem Ereignis-Handler versehen:

// Buch-CD: Kapitel3\Packages\DLLNutzer.bdsproj (Delphi-Projekt)procedure TWinForm2.Button1_Click(sender: System.Object; e: System.EventArgs);begin FormularFuerDll.TWinForm2.Create.ShowDialog;end;

Mehr ist nicht erforderlich – Programm und DLL sind sofort startklar – entsprechendkann die Delphi-DLL übrigens auch aus einer C#-Anwendung aufgerufen werden. DieArbeitsschritte im C#-Builder von Borland sind praktisch identisch mit denen in Del-phi: neues Projekt anlegen, die von Delphi erzeugte DLL im Projektmanager mit REFE-RENZ HINZUFÜGEN zum Projekt hinzufügen, den Namespace einbinden (in C#: usingstatt uses) und das Formular aufrufen – hier ist lediglich eine leicht andere Syntaxerforderlich, da Konstruktoren aus der Sicht von C# nicht Create heißen, sondernnamenlos sind und über den new-Operator aufgerufen werden.

Abbildung 3.7: Das mit Delphi kompilierte Formular wird von einer C#-Anwendung aufgerufen – hier noch im historischen separaten C#-Builder, der ja in Delphi 2006 längst integriert ist.

Page 17: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

302

// Buch-CD: Kapitel3\Packages\CSharpDLLNutzer.bdsproj (C#-Builder-Projekt)using FormularFuerDll;...private void button1_Click(object sender, System.EventArgs e){ (new FormularFuerDll.TWinForm2()).ShowDialog();}

In Kapitel 6.1.4 wird das Package der Beispielkomponenten zu diesem Buch genauerunter die Lupe genommen, welches abgesehen davon, dass es lauter Komponentenenthält, ein anderes Beispiel für ein ganz normales Package ist.

DLLs mit Win32-EinsprungpunktenEine spezielle Alternative zur Erzeugung von .NET-DLLs stellt das Library-Projekt dar,das Sie in der Delphi-IDE unter DATEI | NEU | WEITERE | BIBLIOTHEK finden. Sie erhal-ten damit wieder eine .dpr–Datei, die jedoch nicht mit dem Schlüsselwort program,sondern mit library beginnt. Beim Kompilieren eines solchen Projekts erzeugt Delphiebenfalls eine dynamisch ladbare DLL-Assembly, die sich normalerweise nicht voneinem kompilierten Package unterscheidet. Eine library hat gegenüber dem Packageaber die Besonderheit, dass die erzeugte DLL optional auch von nicht verwaltetenWin32-Anwendungen aufgerufen werden kann. Damit der Compiler die aus Sicht von.NET »unsicheren« Win32-Einsprungpunkte erzeugt, müssen Sie jedoch unsicherenCode explizit erlauben. Die für Win32-Anwendungen aufrufbaren Prozeduren müssenaußerdem in einer exports-Klausel aufgelistet werden:

{$UNSAFECODE ON}library Library1;...procedure F1; begin end;

exports F1; // F1 kann von Win32 aufgerufen werden

end.

Ohne zwingende Gründe sollte man jedoch die Compilerdirektive $UnsafeCode nichtbenutzen, denn eine damit erzeugte Assembly kann beispielsweise vom im .NET-SDKenthaltenen Kommandozeilen-Tool PEVerify nicht mehr als typsicher verifiziert wer-den. Im Normalfall gilt daher, dass Sie eine .NET-DLL als Delphi-Package implemen-tieren sollten.

Page 18: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

303

Das Assembly-ManifestJede .NET-Assembly enthält ein so genanntes Manifest, in dem unter anderem folgendeDaten gespeichert sind:

�  die von dieser Assembly vorausgesetzten Assemblies (die Assemblies, die in Del-phis Projektmanager unter den Knoten Requires bzw. Referenzen angezeigt werden),wobei für jede dieser Assemblies eine Versionsnummer angegeben wird und gege-benenfalls ein Schlüssel, der sicherstellt, dass beim späteren Starten des Programmskeine modifizierten oder verfälschten Assemblies eingebunden werden

�  einige Attribute der Assembly selbst, wie z.B. der Produktname, der Hersteller-name, das Copyright und die Versionsnummer

Während der erste Teil von Delphi automatisch aus den Daten der Projektverwaltungerzeugt wird, können Sie den zweiten Teil im Quelltext beeinflussen, indem Sie diestandardmäßig ausgeblendeten Attribute editieren. Zeigen Sie dazu den Quelltext desProjekts an (PROJEKT | QUELLTEXT ANZEIGEN), expandieren Sie die ausgeblendeteRegion Programm/Assemblierungs-Informationen und ersetzen Sie die vorgegebenen lee-ren Strings durch passende Daten:

{$REGION 'Programm/Assemblierungs-Informationen'} ... [assembly: AssemblyDescription('Komponenten zum Delphi-Buch')] [assembly: AssemblyConfiguration('')] [assembly: AssemblyCompany('EWLAB')] [assembly: AssemblyProduct('')] [assembly: AssemblyCopyright('Copyright (c) 2006 Elmar Warken')] [assembly: AssemblyTrademark('')] [assembly: AssemblyCulture('')]

[assembly: AssemblyTitle('Mein Assembly-Titel')] // überschreibt etwaige // Einstellungen in PROJEKT | OPTIONEN | LINKER | EXE-BESCHREIBUNG

[assembly: AssemblyVersion('1.0.0.0')] ...{$ENDREGION}

Zur Versionsnummer ist zu sagen, dass sie in .NET aus bis zu vier Teilen besteht. Dieersten beiden stellen Haupt- und Nebenversionsnummer dar (oben Version 1.0), demfolgen Build- und Revisionsnummer. Wie unten im Abschnitt Installation von Assemb-lies gezeigt werden wird, kann das .NET-Framework im globalen Assembly-Cache(GAC) mehrere Versionen der gleichen Assembly nebeneinander verwalten. ÜberKonfigurationsdateien ist es möglich, festzulegen, welche von mehreren Versionen ausdem GAC mit der Anwendung verbunden werden sollen.

Page 19: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

304

Standardmäßig erhält ein neues Delphi-Projekt die Versionsnummer 1.0.*. Der »*«-Platzhalter wird vom Compiler beim Übersetzen ersetzt. Dabei erhält die Build-Num-mer einen Wert, der jeden Tag um eins heraufgezählt wird, während die Revisions-nummer sich ständig ändert. Zwei aufeinander folgende Kompilierungen einerAssembly sind dann zumindest durch ihre Versionsnummer zu unterscheiden. WennSie – wie im obigen Beispiellisting – den Platzhalter durch konkrete Zahlen ersetzen,findet eine solche automatische Nummerierung nicht mehr statt.

Signierte AssembliesEine Assembly kann gegen nachträgliche Veränderungen und Manipulationengeschützt werden, indem sie mit einem starken Namen (strong name) versehen wird.Hierzu wird ein Schlüsselpaar erzeugt, bestehend aus einem langen nichtöffentlichenSchlüssel, der auf dem Rechner der Herstellerfirma oder des Autors der Assemblybleiben kann und beim Kompilieren der Assembly nur indirekt in die in der Assemblyeingebetteten Prüfdaten einfließt, sowie aus einem kürzeren öffentlichen Schlüssel, derim Klartext in der Assembly abgelegt wird.

Mit Hilfe dieses öffentlichen Schlüssels kann die CLR beim Laden der Assembly fest-stellen, dass sie sich noch im Originalzustand befindet. Eine Manipulation der Assem-bly könnte vor der CLR nur dann verheimlicht werden, wenn auch der öffentlicheSchlüssel manipuliert werden würde. Dies ist jedoch zwecklos, denn jede Anwendung,die eine Assembly mit einem starken Namen einbindet, nennt dabei (innerhalb ihresManifests, siehe oben) den ihr bekannten öffentlichen Schlüssel der eingebundenenAssembly (bzw. eine Kurzform davon – das öffentliche Schlüsseltoken), so dass dieManipulation auffallen und die CLR das Laden der Assembly mit einer Exceptionabbrechen würde.

Wir versehen als Beispiel die oben erzeugte DLL FormTestPackage mit einem starkenNamen. Zunächst wird mit Hilfe des Tools sn.exe aus dem .NET-SDK (Programme\Microsoft.NET\SDK\[Versionsnummer]\Bin) ein Schlüssel erzeugt und in der Datei Form-TestPackage.snk abgelegt:

sn -k FormTestPackage.snk

Dann wird diese Schlüsseldatei in die Assembly eingebunden, indem im Projektquell-text von FormTestPackage eines der drei mit der Signatur zusammenhängenden Assem-bly-Attribute angepasst wird:

[assembly: AssemblyDelaySign(false)] // unverändert[assembly: AssemblyKeyFile('FormTestPackage.snk')][assembly: AssemblyKeyName('')] // unverändert

Page 20: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

305

Die DLL kann nun neu kompiliert werden und ist durch den starken Namen vor nach-träglicher Veränderung geschützt. Sie können dies durch einen erneuten Aufruf von snüberprüfen (siehe Abbildung 3.8):

sn -Tp FormTestPackage.dll

Eine DLL mit starkem Namen wird in Delphi genauso in andere Projekte eingebundenwie eine normale DLL (REFERENZ HINZUFÜGEN im Projektmanager, uses-Anweisung fürdie in der DLL enthaltenen Units/Namespaces). Die Ablage des öffentlichen Schlüssel-Tokens im Assembly-Manifest wird automatisch vom Compiler erledigt. Das erzeugteManifest kann mit ILDasm.exe überprüft werden (siehe Abbildung 3.9).

Abbildung 3.8: Sn.exe bestätigt, dass der öffentliche Schlüssel von Delphi in der DLL abgelegt wurde, und nennt auch das zugehörige Schlüssel-Token.

Abbildung 3.9: Das Manifest der die signierte DLL einbindenden EXE-Datei nennt das Public Key-Token der DLL.

Page 21: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

306

Installation von AssembliesBisher haben wir entweder die Assemblies von Microsoft und Borland verwendet odereigene Assemblies, die im gleichen Verzeichnis gespeichert wurden wie die ausführ-bare Datei der Anwendung. Diese beiden Varianten zeigen auch die beiden grundle-genden Arten, eine .NET-DLL zu installieren:

Für Assemblies, die von vielen Anwendungen verwendet werden, steht der globaleAssembly-Cache (GAC) zur Verfügung. Hier befinden sich die Microsoft- und Bor-land-Assemblies, aber auch eigene Assemblies können installiert werden, vorausge-setzt, sie verfügen über einen starken Namen. Die oben angelegte signierte Beispiel-DLL kann etwa wie folgt im GAC installiert werden:

gacutil /i FormTestPackage.dll

Um den Inhalt des GAC zu überprüfen, können Sie im Explorer das Verzeichnis [WIN-DOWS-SYSTEMVERZEICHNIS]\ASSEMBLY einsehen. Dank einer vom .NET-Frameworkinstallierten Shell-Erweiterung zeigt der Explorer dieses Verzeichnis nicht in seiner

Sie finden die signierte Version der DLL zusammen mit einem Delphi- und einem C#-Projekt, wel-che diese DLL benutzen, im Verzeichnis Kapitel3\Signierte Assemblies der Buch-CD. Dieses Beispiel funktioniert nur mit Delphi 2006 richtig. In Delphi 2005 gab es einen Bug, wegen dem das Public Key-Token der DLL nicht korrekt in das EXE-Manifest geschrieben wurde, so dass die .NET-CLR die DLL beim Laden der EXE für gefälscht hielt und das Laden der EXE abbrach.

Abbildung 3.10: Nach der Installation des .NET Framework 2.0 gaben die .NET-Assemblies eine gute Demonstration der Tatsache, dass .NET mehrere Versionen der gleichen Assembly nebeneinander existieren lässt.

> > > HINWEIS

Page 22: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

307

kompliziert verschachtelten physikalischen Struktur, sondern listet die Assembliesübersichtlich untereinander auf (siehe Abbildung 3.10).

Die Installation im GAC ist aber nicht erforderlich, denn grundsätzlich sucht die CLReine eingebundene Assembly zuerst im Verzeichnis der Anwendung. Wenn Sie in einDelphi-Projekt daher per REFERENZ HINZUFÜGEN eine Assembly einbinden, die nichtim GAC gespeichert ist und sich auch nicht im Projektverzeichnis befindet, kopiertDelphi die Assembly in das Projektverzeichnis. Dieser Kopiervorgang wird bei jedemKompilieren des Projekts wiederholt, falls sich die Original-Assembly mittlerweilegeändert hat.

3.1.4 Delphi-UnitsIn Kapitel 3.1.2 wurde auf die Entsprechung von .NET-Namespaces und Delphi-Unitshingewiesen. An dieser Stelle geht es um den Inhalt und den Aufbau einer Delphi-Unit, wobei auch wieder überprüft werden soll, inwieweit sich die Delphi-Unit mitdem Begriff des .NET-Namespaces in Einklang befindet.

Der Inhalt von NamespacesAuf .NET-Ebene kann ein Namespace nur eine Art von Elementen enthalten: Typen.Ein Typ ist in .NET immer ein Objekttyp, also eine Klasse. Folgende Arten von Typenkönnen definiert werden:

�  Werttypen (C#: struct, Delphi: record)

�  Aufzählungstypen (C#: enum, Delphi: Aufzählungstyp)

�  Schnittstellen (C# und Delphi: interface)

�  Delegaten (C#: delegate, Delphi: Methodentyp, procedure ... of object)

�  Klassen (C# und Delphi: class)

Sie können dieses automatische Kopieren auch unterbinden, indem Sie die Referenz im Projekt-manager markieren und im Objektinspektor die Eigenschaft Lokal kopieren auf False setzen.

Die CLR ist sehr flexibel, was den Speicherort einer Assembly betrifft. So können Sie etwa mit dem <codebase>-Attribut in einer Konfigurationsdatei für Ihre Anwendung (Datei mit der Endung .config im Anwendungsverzeichnis) ein anderes Standardverzeichnis für eingebundene DLLs fest-legen. Falls Sie die Assembly im GAC installieren, gilt es möglicherweise noch zwischen verschiede-nen Versionen zu unterscheiden. Das Thema der Konfiguration von Anwendungen würde jedoch den Rahmen dieses Kapitels sprengen und hat darüber hinaus überhaupt nichts mit Delphi zu tun. Die Online-Dokumentation des Frameworks zeigt unter Microsoft .Net Framework SDK | Konfigurie-ren von .NET Framework-Anwendungen die Komplexität der bestehenden Möglichkeiten auf.

> > > HINWEIS

Page 23: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

308

Alle fünf Arten von Typen können auch in einem Delphi-Programm definiert werden,wobei teilweise Delphi-spezifische Schlüsselwörter zum Einsatz kommen, die obengenannt wurden. In jedem Fall wird ein Typ in Delphi in einem Programmabschnittmit der Überschrift type definiert.

Der Inhalt von UnitsEine Delphi-Unit besteht aus folgenden Elementen, die in beliebiger Reihenfolge auf-treten können, sofern keine Abhängigkeiten zwischen den einzelnen Elementen dage-gen sprechen:

�  Typen (Programmabschnitte mit der Überschrift type)

�  Konstanten (Überschrift const)

�  Variablen (Überschrift var)

�  Prozeduren und Funktionen (keine Überschrift, jede einzelne Prozedur wird mitdem Schlüsselwort procedure, jede Funktion mit function eingeleitet)

Alle Typen von Object Pascal lassen sich in die entsprechenden .NET-Typen umsetzen,folglich bleiben als problematische Elemente lediglich die Konstanten, Variablen undMethoden, die in .NET nur als Teil einer Klasse vorkommen, in Object Pascal aber auchohne umgebende Klasse als globale Konstanten, Variablen und Methoden deklariertwerden dürfen.

Damit diese Regeln der Sprache Object Pascal nicht geändert zu werden brauchen,greift der Delphi-Compiler zu dem Trick, alle globalen Konstanten, Variablen undMethoden für den Programmierer transparent in einer .NET-Klasse namens Unit zuverpacken. Während diese globalen Symbole innerhalb eines Delphi-Programms wiegewohnt verwendet werden können, würden externe Assemblies sie als Elemente der.NET-Klasse Unit sehen.

Aufbau von UnitsEine Delphi-Unit ist wie folgt aufgebaut:

unit ModulName; { Modulname muss angegeben werden }

interface[uses-Klausel][Deklarationen]

implementation[uses-Klausel][Deklarationen und Definitionen]

Unit-Hauptprogramm, abkürzbar als "end."

Page 24: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

309

Eine Unit kann alle Arten von Object-Pascal-Elementen enthalten: Prozeduren, Funk-tionen, Variablen, Konstanten, Typen, insbesondere Klassen. Sinn der Unit ist es, dieseObjekte teilweise oder ganz anderen Modulen zur Verfügung zu stellen.

Alle Objekte, die in anderen Modulen benutzt werden dürfen, müssen im interface dekla-riert werden. Dadurch werden die entsprechenden Bezeichner öffentlich (public), alsonach außen hin sichtbar. Bei Variablen, Konstanten und einfachen Typen genügt diese ein-malige Deklaration, alle Prozeduren und Funktionen bzw. Methoden von Klassen müssenaußerdem noch definiert werden, und zwar im Abschnitt implementation. Alle weiterenDeklarationen, die innerhalb des Implementation-Abschnitts stehen, sind privat.

Zirkuläre Unit-EinbindungDer Sinn zweier getrennter uses-Anweisungen in Interface- und Implementation-Abschnitt liegt darin, zirkuläre Nutzungsverhältnisse festzulegen – wenn sich bei-spielsweise zwei Units gegenseitig benutzen wollen. Das folgende Beispiel würde denCompiler in eine Endlosschleife stürzen, wenn dieser den Fehler nicht erkennen würde(bei uses Unit2 würde er versuchen, die Unit2 einzulesen, wo ihn die Anweisung usesUnit1 wieder zurück zu Unit1 schicken würde):

{ Dateibeginn der ersten Unit: }unit Unit1; interface uses Unit2;{ Dateibeginn der zweiten Unit: }unit Unit2; interface uses Unit1;

In diesem Fall muss eine der uses-Klauseln auf den Implementationsteil der Unit ver-schoben werden, beispielsweise:

{ Beginn der ersten Unit: }unit Unit1; uses Unit2;---{ Beginn der zweiten Unit: }unit Unit2;interfaceimplementationuses Unit1;

Wenn der Compiler nun Unit1 übersetzt, kann er das Interface der Unit2 einlesen, ohnewieder zurück zur Unit1 verwiesen zu werden. Der Implementationsteil von Unit2 inter-essiert den Compiler bei der Übersetzung von Unit1 noch nicht. Während das Interfacevon Unit1 also von Unit2 abhängt, ist das Interface von Unit2 unabhängig von Unit1.Diese Ausweichlösung bringt natürlich als Einschränkung mit sich, dass Sie im Interfacevon Unit2 keine Bezeichner aus Unit1 mehr verwenden können; Sie müssen zwei Units,die sich gegenseitig referenzieren, also so entwerfen, dass zumindest in einem der beidenInterface-Teile nicht Klassen oder Typen der anderen Unit verwendet werden.

Page 25: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

310

Units initialisieren und verlassenDer im oben gezeigten Unit-Aufbau als »Unit-Hauptprogramm« ausgewieseneAbschnitt wird beim Programmstart aufgerufen, um der Unit die Gelegenheit zugeben, sich selbst zu initialisieren. Sie können diesen Abschnitt mit dem traditionellenSchlüsselwort begin oder mit dem etwas neueren Wort initialization einleiten.

Passend dazu gibt es noch das Schlüsselwort finalization, das als Überschrift für denCode dient, der bei der Beendigung des Programms auf jeden Fall aufgerufen werdensoll. Das Ende einer Unit kann damit wie folgt aussehen:

initialization { Code zum Initialisieren der Unit }finalization { Code zum Aufräumen nach der Programmausführung }end.

3.1.5 Delphi-Units für UmsteigerUnits sind die Module von Object Pascal. Sie unterscheiden sich von den Modulen man-cher anderer Sprachen wie C++ durch ihre besondere Eigenständigkeit und Abgeschlos-senheit. Während z.B. in C++ Dateien auf die verschiedensten Arten zusammengebundenwerden können und üblicherweise für jedes Modul eine Implementierungs- und eineHeader-Datei (.cpp und .h) gewartet werden muss, ist eine Unit in Object Pascal eine ein-zige, in sich abgeschlossene Textdatei, die sowohl einen Interface-Teil (entsprechend derHeader-Datei von C++) als auch einen Implementierungs-Teil enthält.

Java- und C#-Programmierer sind den Umgang mit solchen in sich abgeschlossenenDateien ja gewöhnt – sie werden sich in Delphi daran gewöhnen müssen, dass Klassenin Deklaration und Implementierung aufgespalten werden, wobei die Deklaration füröffentliche Klassen im Interface-Teil der Unit stattfindet und die Implementierung injedem Fall im Implementierungs-Teil.

Die Trennung in Interface- und Implementierungsteil wirkt sich jedoch nicht in so gro-ßer Zusatzarbeit aus, wie es vielleicht auf den ersten Blick erscheinen mag: Delphibringt mit der automatischen Klassenvervollständigung eine Funktion mit, mit der dieSynchronisierung zwischen Interface- und Implementierungsteil teilweise automati-siert werden kann. Und schließlich ist es durchaus auch von Vorteil, dass die Klassen-deklaration nur die Methodenköpfe enthält und nicht den gesamten Code derMethoden: So kann das gesamte Interface einer Klasse in jedem Texteditor ohne wei-tere Hilfsmittel (wie etwa Code-Folding oder Strukturfenster) schnell überblickt wer-den.

Page 26: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

311

3.2 Objekte und KlassenDas zentrale Konzept der objektorientierten Programmierung ist die Zusammenfas-sung von Daten und Code, die in der prozeduralen Programmierung noch striktgetrennt waren, zu abgeschlossenen Strukturen namens Objekten. Die Objekte, mitdenen Sie im Formular-Designer in Kontakt kommen, sind in erster Linie Steuerele-mentkomponenten und Formulare, aber auch hinter den Properties, die Sie im Objekt-inspektor aufgelistet finden, stecken oft wieder Objekte, beispielsweise die Font-Objekte zur Definition der Schriftart von Controls oder Kollektionsobjekte zur Auflis-tung von ListBox-Einträgen, von ListView-Spalten oder von TabControl-Seiten.

Auf der .NET-Plattform können sogar einfache Werte wie Zahlen und Strings alsObjekte behandelt werden, so dass Sie jetzt z.B. zur Umwandlung einer Zahl in eineZeichenkette anstelle eines prozeduralen Aufrufs wie ...

str := IntToStr(zahl);// IntToStr als Funktion der Unit SysUtils, // kann auch geschrieben werden als:str := SysUtils.IntToStr(zahl);

... nun die Zahl als Objekt behandeln und eine Methode aufrufen können:

str := zahl.ToString;

Der KlassenbegriffSo, wie Pascal schon immer eine streng typisierte Programmiersprache war, in der manfür jede Variable einen Typ festlegen musste, haben auch alle Objekte in Object Pascal(und auf der .NET-Plattform) einen bestimmten Typ. Im Falle von Objekten wird die-ser Typ auch als Klasse bezeichnet. Das Objekt, welches eine bestimmte Klasse als Typhat, wird als Exemplar dieser Klasse, oder auch als Instanz dieser Klasse bezeichnet.

Ein Beispiel für die Unterscheidung zwischen der Klasse und ihren Exemplaren/Instanzen sind die Formulare im Formular-Designer (Fensterklasse) und die von ihnenzur Laufzeit erzeugten tatsächlichen Fenster.

In diesem Buch wird der Ausdruck Exemplar vorgezogen. Der ebenfalls häufig anzutreffende Begriff der Instanz muss wohl auf eine anfänglich unüberlegte und nun gewohnheitsmäßig ver-wendete Übersetzung des englischen Wortes instance zurückzuführen sein. Das Wort »Instanz« der deutschen Sprache entspricht jedoch eigentlich nicht dem englischen Wort instance, sondern dem Wort authority.

> > > HINWEIS

Page 27: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

312

3.2.1 Die KlassendeklarationAls Beispiel für eine Klassendeklaration sei hier eine vereinfachte Version der KlasseTimerEvent aus Kapitel 2.2.3 wiederholt:

type TimerEvent = class public // Variablen: Aktivierungszeit: DateTime; // Methoden: function ToString: String; override; // Ereignisdaten // in Text umwandeln procedure Trigger; virtual; // Ereignis auslösen end;

Klassendeklarationen befinden sich immer in einem Quelltextabschnitt, der durch dasSchlüsselwort type eingeleitet wird. Das wichtigste Schlüsselwort bei der Deklarationist das Wort class. Es unterscheidet die ihm folgende Struktur von den Records, diestattdessen mit record eingeleitet werden.

Darauf folgen die Bestandteile der Klasse, und zwar unterteilt in Abschnitte mit Über-schriften wie private und public, wobei im obigen Beispiel lediglich ein public-Abschnittvorhanden ist.

Innerhalb eines solchen Abschnitts müssen Sie zuerst die Variablen und dann dieMethoden und Properties aufführen. Sobald die erste Methode erscheint, dürfen Vari-ablen erst wieder nach einer neuen Überschrift wie z.B. public folgen. Für die einzelnenDeklarationen gelten folgende Regeln:

�  Variablen werden deklariert wie alle Variablen von Object Pascal, Details dazu fin-den Sie in Kapitel 3.5.3, fürs Erste genügt es, dass Sie die einfache Syntax Name: Typkennen, mit der eine Variable mit Namen Name und Typ Typ deklariert wird.

�  Auch die Methodendeklarationen richten sich nach der Syntax von Object Pascal,die für alle Funktionen und Prozeduren gilt: Hat die Methode einen Rückgabetyp,wird sie als function deklariert und der Rückgabetyp wird hinter einem Doppel-punkt angegeben; ansonsten handelt es sich um eine procedure. Die Parameter derMethode werden in Klammern hinter dem Namen aufgelistet. Mehr Details zur all-gemeinen Syntax finden Sie in Kapitel 3.8. Speziell für Methoden kommen nocheinige Direktiven hinzu wie im obigen Beispiel override und virtual, zu ihnen wer-den wir in Kürze kommen.

�  Properties sind eine Spezialität, die nur innerhalb von Klassen vorkommt, ihr istdas in Kürze folgende Kapitel 3.2.4 gewidmet.

Page 28: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

313

KlassenvervollständigungNachdem innerhalb der Klassendeklaration lediglich Name, Parameter und Ergebnis-typ der Methoden festgelegt wurden, muss die Implementation der Methoden (alsoder Methodenrumpf mit dem auszuführenden Programmcode) weiter unten im Quell-text – getrennt von der Klassendeklaration – erfolgen. Das Implementieren der Metho-den direkt in der Klassendeklaration (wie z.B. in Java und C#) ist in Object Pascal nichtmöglich, hierfür sind die Implementationsteile der Units vorgesehen.

Am einfachsten erhalten Sie ein Gerüst zur Implementation Ihrer Methoden durch dieautomatische Klassenvervollständigung von Delphi. Drücken Sie etwa (Strg)+ (ª)+(C), während sich die Eingabemarkierung innerhalb der oben gezeigten Klassendekla-ration befindet, erzeugt Delphi im Implementationsteil der Unit folgende Methoden-rümpfe:

implementation

{ TimerEvent }

constructor TimerEvent.Create;begin

end;

function TimerEvent.ToString: String;begin

end;

procedure TimerEvent.Trigger;begin

end;

NamenskonventionenDa Delphi eine Brücke zwischen mehreren Welten schlägt, werden Sie keine allgemein-gültige Benennungskonvention finden, die für alle in Delphi programmierten oder vonDelphi ansprechbaren Klassen eingehalten wird. In früheren Delphi-Versionen war dieSituation noch überschaubar, denn Delphi-Programmierer folgten im Allgemeinendem von Borland vorgeschlagenen Prinzip, alle Klassennamen mit einem »T« begin-nen zu lassen (wie auch alle anderen Namen von Typen in Object Pascal): Darauswurde dann in Borlands Bibliothek VCL z.B. TColor, TForm, TButton.

Auf der .NET-Plattform gibt es diese Tradition nicht, hier heißen die Klassen einfachColor, Form oder Button. Da es natürlich eine unglaubliche Vielzahl von Klassen im.NET-Framework gibt, sehen sich die T-Klassen in Delphi urplötzlich in der Minder-

Page 29: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

314

heit. Dafür können Sie den Klassennamen als ersten Hinweis auf die Herkunft einerKlasse deuten: Solange Sie noch keine Klassen von Drittanbietern hinzugenommenhaben, wird eine mit T-Präfix ausgestattete Klasse in Delphi von Borland stammen,eine andere Klasse eher von Microsoft.

Die Beispielprogramme dieses Buchs wenden mal das eine, mal das andere Benennungs-schema an, je nachdem, ob das Beispielprogramm eher der VCL oder der .NET-Plattformnahe steht (so haben die Klassen in Kapitel 1 beispielsweise kein Extra-T, die Klassen derVCL.NET-Anwendung in Kapitel 4 beginnen dagegen grundsätzlich damit).

3.2.2 SichtbarkeitsattributeDurch die Angabe von Abschnittsüberschriften in der Klassendeklaration, analog zurVerwendung von public im obigen Beispiel TimerEvent, können Sie bestimmte Ele-mente der Klasse vor dem Zugriff von außen schützen. Die folgenden Schutzebenenstehen zur Wahl:

�  public (öffentlich) lässt die Elemente eines Objekts vollkommen ungeschützt underlaubt es, dass von außen darauf zugegriffen wird. Den Idealen der objektorien-tierten Programmierung entspricht es, wenn nur Methoden und Properties, aberkeine Daten öffentlich sind.

�  strict protected schützt die Elemente vor dem Zugriff von außen, was bedeutet, dassbeispielsweise eine Variable, die im protected-Bereich der Button-Klasse deklariertist, nicht von einer Ereignisbearbeitungsmethode des Formulars aus angesprochenwerden kann. Sie dürfen die mit protected geschützten Elemente einer Klasse jedochin abgeleiteten Klassen ohne Einschränkung verwenden.

�  strict private versagt sogar abgeleiteten Klassen die Verwendung der so gekenn-zeichneten Elemente, so dass die besitzende Klasse die alleinige Kontrolle darüberhat, was mit diesen Elementen geschieht.

�  protected und private gibt es auch ohne den Zusatz strict, dann sind zusätzlichZugriffe aus beliebigen Methoden innerhalb der Unit erlaubt, in der sich die Klas-sendeklaration befindet – also auch aus anderen Klassen derselben Unit. DieseErweiterung des erlaubten Zugriffs entspricht der CLR-Sichtbarkeitsebene Assem-bly, während strict private dem CLR-Sichtbarkeitsattribut private und strict protecteddem CLR-Attribut family entspricht.

�  Schließlich gibt es noch die Sichtbarkeitsebene published (veröffentlicht), die nur fürKomponenten eine Rolle spielt, die im Formular-Designer verwendet werden sol-len. Hier müssen die Properties als published deklariert werden, die im Objekt-inspektor angezeigt werden sollen.

Page 30: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

315

Standardmäßig, also solange Sie keine andere Angabe machen, haben alle Elementeeiner Klasse die Sichtbarkeit public (bzw. im Kontext der VCL.NET: published).

3.2.3 Das Selbst einer MethodeDie Vereinigung von Code und Daten in der OOP wird besonders gut erkennbar beider Art, wie ein Objekt auf seine eigenen Daten zugreift. Sehen wir uns als Beispieleine Formularmethode an, in der die Hintergrundfarbe des Fensters angepasst wird:

procedure TWinForm.ChangeBackgroundColor;begin BackColor := Color.Red;end;

Das Formular greift auf sein BackColor-Element zu, indem es dieses einfach beimNamen nennt – es scheint zunächst nichts Natürlicheres zu geben als dies. Doch dasFormular ist eigentlich nur eine Klasse, von der erst zur Laufzeit ein konkretes Objekterzeugt wird. Und nicht nur das – es können auch mehrere Objekte der gleichen Klasseerzeugt werden. Wenn nun mehrere Fenster des Typs TWinForm existieren – wessenHintergrundfarbe wird dann mit der obigen Anweisung verändert?

Der implizite self-ParameterDie Antwort liegt im unsichtbaren self-Parameter, den jede Methode automatisch zuge-ordnet bekommt. Self gibt das Objekt an, auf das die Methode angewendet wurde. DerCompiler stellt jedem Objektelement, das Sie innerhalb der Methode verwenden, auto-matisch diesen Self-Parameter voran. Jede Methode ist also intern so organisiert:

procedure TWinForm. Methode(self: TForm1; ... hier folgen explizit deklarierte Parameter);begin with self do begin ... hier kommen die explizit aufgeschriebenen Anweisungen end;end;

Durch die with self do-Anweisung werden alle im nachfolgenden Block vorkommendenNamen, die auch Namen von Elementen von self sind, auf dieses self bezogen. Die frü-her gezeigte Änderung der Hintergrundfarbe des Formulars sieht also intern so aus:

self.BackColor := Color.Red;

Page 31: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

316

self zur LaufzeitNehmen wir nun zur Veranschaulichung einen Codeausschnitt an, in dem zwischenzwei Formularobjekten unterschieden wird:

var Form1, Form2: TWinForm;begin ... Form2.ChangeBackgroundColor;

In diesem Fall wird der self-Parameter der aufgerufenen Methode auf Form2 und ent-sprechend im Methodenrumpf das BackColor-Property von Form2 gesetzt:

procedure TWinForm.ChangeBackgroundColor(self = Form2);begin Form2.BackColor := Color.Red;end;

Die Unterscheidung zwischen mehreren Objekten zur Laufzeit geschieht also imGrunde automatisch. Bei der Programmierung von Methoden einer Klasse kann mansich die Situation im Normalfall so vorstellen, als gäbe es nur ein einziges Objekt die-ser Klasse und man programmiere nicht die Methoden der Klasse, sondern die Metho-den dieses einen Objekts. Es ist das Objekt mit dem Namen self. Im Hinterkopf solltenSie allerdings immer behalten, dass die gleiche Methode zur Laufzeit mit verschiede-nen Werten von self aufgerufen wird (es sei denn, Ihre Klasse ist bewusst daraufbeschränkt, dass es von ihr nur genau ein Objekt geben kann).

Solange Sie nicht selber eine andere Variable deklarieren, die auch self heißt, könnenSie den Bezeichner self auch explizit verwenden, um die Wirkung einer anderen with-Anweisung aufzuheben. Im Folgenden bezieht der Compiler den Bezeichner BackColoraufgrund der with-Anweisung nicht auf self, sondern auf Button1. Um die BackColor desFormulars anzugeben, muss daher das self explizit angegeben werden:

with Button1 do BackColor := self.BackColor;{ ist das Gleiche wie ohne with-Anweisung: }Button1.Color := Color;

3.2.4 PropertiesProperties heben einen Widerspruch zwischen der OOP-Philosophie und dem Wunschnach einer einfachen Programmierung auf: Wenn man – ohne Properties – ein Datene-lement eines Objekts möglichst einfach verändern wollte, müsste man schreiben:

objekt.Datenelement := NeuerWert;

Page 32: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

317

Hierzu müsste das Datenelement jedoch mit dem Sichtbarkeitsattribut public versehensein, was jedoch der OOP-Philosophie widersprechen würde, die einen Methodenauf-ruf wie den folgenden verlangt:

objekt.SetDatenelement(NeuerWert);

Dies hätte den Vorteil, dass der aufrufende Code nicht von der internen Datenstrukturder Klasse abhängig wäre (wenn das Datenelement später umbenannt, entfernt oder ineine ganz andere Struktur verlagert würde, müsste nur die Methode SetDatenelementumgeschrieben werden, die Aufrufe dieser Methode könnten dann gleich bleiben).Außerdem könnte SetDatenelement noch weitere Aktionen durchführen, die bei jederÄnderung von Datenelement automatisch vorgenommen werden sollen (z.B. dieAktualisierung der Bildschirmanzeige).

Ist nun Datenelement keine Variable, sondern ein Property, können Sie schreiben:

objekt.Datenelement := NeuerWert;

... und trotzdem wird intern eine Set...-Methode ausgeführt.

Eigene Properties deklarierenProperties werden an derselben Stelle einer Klassendeklaration deklariert wie Metho-den, dürfen also erst nach allen Variablen desselben public/private/...-Abschnittsgenannt werden. Die Deklaration eines einfachen Beispiel-Properties Width sieht wiefolgt aus:

property Width: Integer read GetWidth write SetWidth;

Die hinter dem Property-Typ Integer stehenden read- und write-Direktiven geben dieMethoden an, die beim Lese- bzw. beim Schreibzugriff aufgerufen werden. Sie könneneine der beiden Direktiven auch weglassen, um ein Property zu erhalten, das nur gele-sen bzw. nur beschrieben werden kann.

Die Lesemethode eines Properties muss eine Funktion ohne Parameter sein, derenErgebnistyp mit dem Typ des Properties übereinstimmt, während die Schreibmethodeeine Prozedur ist, die eine Variable des Property-Typs als Parameter hat, beispiels-weise:

function TEineKlasse.GetWidth: Integer;begin Result := FWidth;end;

procedure TEineKlasse.SetWidth(NewWidth: Integer);

Page 33: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

318

begin FWidth := NewWidth; Invalidate;end;

Wie in diesem Beispiel liegen einem Property oft Variablen desselben Typs zugrundeund es ist die Aufgabe der Property-Methoden, besondere Aktionen mit dem Lesenund Schreiben dieser Variablen zu verknüpfen. Meistens wird diese Variable auseinem vorangestellten »F« und dem Namen des Properties gebildet (in diesem BeispielFWidth).

Variablen in Property-VerkleidungAlternativ zu den Methoden können Sie in den read- und write-Direktiven auch Variab-len angeben, die direkt gelesen oder geschrieben werden können. Im obigen Beispiel,in dem die Methode GetWidth nur die Variable FWidth ausliest, könnte derselbe Effekterzielt werden, wenn das Property wie folgt deklariert würde:

{ alle für das Property notwendigen Deklarationen: } FWidth: Integer; procedure SetWidth(NewWidth: Integer); property Width: Integer; read FWidth write SetWidth;

Property-BeispieleFür praktische Property-Beispiele sei an dieser Stelle auf andere Teile dieses Buchs ver-wiesen, z. B.

�  verfügt die Klasse RTFAttributes auf Seite PropertyBeispiel1 über zahlreiche Proper-ties für die in einer RichTextBox verwendbaren Textattribute,

�  wird in der Klasse DesktopChangeEvent auf Seite PropertyBeispiel2 das PropertyColorARGB verwendet, um den XML-Serialisierungsprozess anzupassen.

Darüber hinaus definieren natürlich die in Kapitel 6 entwickelten Komponenten Pro-perties, die Sie auch zur Entwurfszeit im Objektinspektor editieren können.

Array-PropertiesEin Array-Property funktioniert nach außen hin wie ein normales Array, Sie greifenalso beispielsweise mit

Colors[0] := System.Drawing.Color.White;

auf das nullte Element des Properties Colors zu. Um ein solches Property zu deklarie-ren, deklarieren Sie nach dem Property-Namen in eckigen Klammern für jede Dimen-

Page 34: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

319

sion des Arrays eine Indexvariable (z.B. [I: Byte]). Der Typ der einzelnen Array-Elemente wird nach der schließenden eckigen Klammer bestimmt:

// Ein Array von TColor-Objekten, indiziert über einen Integer-Wert:property Colors[i: Integer]: TColor read GetColor write SetColor;// Ein dreidimensionales Array von Objekten, indiziert über// drei verschiedene Integer-Werte:property DreiD[x, y, z: Integer]: TObject; read Read3D write Set3D;

Bei den Schreib- und Lesemethoden der Array-Properties müssen die Indexvariablenaus der Array-Deklaration wieder als Parameter auftauchen, für die obigen Propertieskönnten sie folgendermaßen deklariert werden:

function GetColor(i: Intger): TColor;procedure SetColor(i: Integer; val: TColor);function Get3D(x, y, z: Integer): TObject;procedure Set3D(x, y, z: Integer; val: TObject);

Das Standard-Array-PropertyWenn Sie die Deklaration eines Array-Properties mit der Direktive default abschließen,wird dieses Property zum Standard-Property der Klasse:

type TList = class property Items[Index: Integer]: TObject read GetItem write SetItem; default;

Für die Klasse List bedeutet das beispielsweise, dass Sie ein ganzes Objekt dieserKlasse behandeln können wie dieses Array, ohne das Property extra nennen zu müs-sen:

var List: TList; ErstesObjekt: TObject;begin List := TList.Create(...); // Liste initialisieren ErstesObjekt := List[0];

Der Ausdruck List[0] hat dieselbe Wirkung wie List.Items[0]. Es versteht sich vonselbst, dass jede Klasse höchstens ein derartiges Standard-Property haben kann.

Eine Methode für mehrere PropertiesIndizierte Properties sind vergleichbar mit Array-Properties, bei denen Sie nicht übereinen Index, sondern über unterschiedliche Namen auf die einzelnen Elemente zugrei-

Page 35: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

320

fen. Die Properties Left, Right, Top und Bottom der Klasse TGraphicElement des Tree-Designers beispielsweise sind wie folgt deklariert:

property Left: Integer index 1 read GetCoord;property Right: Integer index 2 read GetCoord;property Top: Integer index 3 read GetCoord;property Bottom: Integer index 4 read GetCoord;

Alle vier Properties werden mit derselben Methode gelesen, wobei diese die Propertiesüber den Index unterscheidet. Der Compiler fügt automatisch den passenden Index inden Methodenaufruf ein, wenn Sie eines dieser Properties ansprechen:

function TGraphicElement.GetCoord(Index: Integer): Integer;begin with Points do case Index of 1: Result := Left; 2: Result := Right; 3: Result := Top; 4: Result := Bottom; end;end;

Gleichermaßen funktionieren die Schreibprozeduren für ein solches Property, die alsozwei Parameter benötigen: den Index und einen Parameter des neu zu setzendenWerts. Das obige Beispiel definiert keine Schreibmethoden, folglich können die viergezeigten Properties nur gelesen werden.

3.2.5 Klassenmethoden und KlassenvariablenDelphi für .NET unterstützt als Klassenelemente sowohl statische Methoden als auchstatische Variablen und Properties. Noch in Delphi 7 gab es nur die statischen Metho-den, die seither auch unter dem Namen »Klassenmethoden« geführt werden (entspre-chend der Deklarationssyntax als class function/procedure). Diese Klassenmethodenentsprechen – wenn auch mit einer später noch zu erläuternden Einschränkung – denstatischen Methoden auf der Ebene der CLR.

Die Bezeichnung Klassenmethode meint so viel wie »Methoden für die Klasse«, alsoMethoden, die nicht für spezifische Objekte, sondern für die Klasse selbst aufgerufenwerden. Ein rein fiktives Beispiel ist:

type TDemoClass = class class function GetSpeedEstimate: Integer; { mit dieser Funktion kann die Klasse versprechen, besonders

Page 36: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

321

schnell zu arbeiten (falls Rückgabewert > 100) }

// Die Klassenfunktion wird wie folgt aufgerufen:if TDemoClass.GetSpeedEstimate < 100 then ShowMessage('Zu langsam!');

Statische Methoden nach CLR-ArtIm oben gezeigten Beispiel entspricht die Klassenmethode GetSpeedEstimate von derFunktion her einer statischen Methode auf der .NET-Plattform und wird vom Delphi-Compiler auch in eine solche statische Methode übersetzt.

In der .NET-Klassenbibliothek dienen statische Methoden vor allem dazu, Utility-Funktionen zur Verfügung zu stellen, die unabhängig von konkreten Objekten arbei-ten sollen. Solche Utility-Funktionalität hätte man früher in C++ (oder in Delphi) inglobalen Funktionen unterbringen können, doch in der CLR gibt es keine globalenFunktionen mehr. Statische Klassenmethoden dienen hier als Ausweg, um Methodendefinieren zu können, die so verwendet werden können wie früher die globalen Routi-nen: Es ist nicht notwendig, zuerst ein Objekt zu erzeugen, für das die Methode aufge-rufen werden kann, sondern die Klasse selbst ist das Objekt, für das die Methodeaufgerufen wird (also: TDemoClass.GetSpeedEstimate).

Viele Klassen des .NET-Frameworks bieten statische Methoden an, doch besonderstypisch für das beschriebene Konzept (»Ersatz globaler Methoden«) sind bestimmteKlassen, die nur aus statischen Methoden bestehen, wie etwa File, Directory und Path(zu diesen siehe etwa Kapitel 2.6.2).

Konsequenterweise haben statische Methoden in der .NET-Welt keinen self-Parameter.Und genau hier beginnen die Differenzen zu Delphis Klassenmethoden ...

Klassenmethoden nach Delphi-ArtKlassenmethoden erhalten in Delphi als self-Parameter eine Klassenreferenz, die besagt,für welche Klasse die Methode aufgerufen wurde (bei einem Aufruf TDemoClass.GetSpee-dEstimate würde der Parameter auf TDemoClass weisen). Im Delphi-Quelltext ist dieserself-Parameter implizit, wenn Sie eine in Delphi geschriebene Klassenmethode aber auseiner anderen Sprache heraus aufrufen oder mit einem Reflection-Tool betrachten, wer-den Sie dieses self als zusätzlichen ersten Parameter vorfinden. Dieser Parameter dientzunächst nur der Abwärtskompatibilität zu früheren Delphi-Versionen, denn in denKlassenmethoden, von denen bisher die Rede war, ist er einfach unnötig: In jeder Klas-senmethode ist unmittelbar klar, auf welche Klasse sie sich bezieht.

Dies ändert sich jedoch, wenn wir die zweite Eigenheit von Delphi betrachten: Klas-senmethoden können hier auch virtuell sein und in abgeleiteten Klassen überschriebenwerden (virtuelle Methoden werden in Kapitel 3.3.3 ausführlich erläutert). Der self-

Page 37: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

322

Parameter einer Klassenmethode kann dann zur Laufzeit tatsächlich einen anderenWert haben als die Klasse, in der die Methode enthalten ist.

Klassenstatische MethodenWie erläutert besteht der einzige technische Unterschied zwischen Delphis Klassenme-thoden und den .NET-Klassenmethoden im zusätzlichen self-Parameter von DelphisMethoden. Um nun ganz konform mit der .NET-Linie zu gehen, können Sie in Delphifür .NET eine Klassenmethode zusätzlich noch mit der Direktive static deklarieren.Dies bewirkt, dass Delphi eine für .NET ganz normale Klassenmethode erzeugt, dienicht über einen self-Parameter verfügt:

class function MyClassFunction: Integer; static;

Dies ist z.B. wichtig, wenn Sie Klassen-Properties deklarieren (siehe Seite U4Klassen-Properties), die von anderen .NET-Sprachen aus angesprochen werden sollen. Andere.NET-Sprachen erwarten hier nämlich, dass die Get-/Set-Methoden keinen self-Parameterhaben.

KlassenvariablenVariablen und Properties können wie Methoden so deklariert werden, dass sie nur ein-mal für die ganze Klasse existieren und nicht Teil jedes einzelnen Objekts sind. BeiVariablen erfolgt die Deklaration einfach, indem eine normale Variablendeklarationdurch die Wörter class var erweitert wird.

Als Beispiel soll eine Klasse Color dienen, die an die .NET-Klasse Color angelehnt ist,welche fest vordefinierte Farbobjekte mit Namen wie White und Red als Klassenvariab-len zur Verfügung stellt:

type Color = class class var White: Color;

Bei den Klassenmethoden ist das Sprachkonzept von Delphi mächtiger als etwa das von C#, wo es keine virtuellen Klassenmethoden gibt. Allerdings zeigt die Umsetzung dieses Konzepts durch den Delphi-Compiler genau, dass sich dieses Konzept auch in C# »simulieren« ließe: Delphi übersetzt eine virtuelle Klassenmethode nicht in eine im Sinne der CLR statische Methode der Klasse, son-dern in eine im Sinne der CLR virtuelle Methode einer Metaklasse. Eine solche Metaklasse erzeugt Delphi automatisch für jede im Quelltext definierte Klasse, auch um andere Spezialitäten der Del-phi-Welt zu implementieren. Für die Klasse TDemoClass würde die Metaklasse beispielsweise @MetaTDemoClass heißen und im Reflection-Tool als Unterknoten (geschachtelter Typ) von TDe-moClass erscheinen (siehe auch @MetaTWinForm in Abbildung 3.1).

> > > HINWEIS

Page 38: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

323

class var Red: Color; ...

// Benutzung dieser Klassenvariablen:Color.Red; // Dieser Ausdruck liest die Variable Red der Klasse aus.

Klassenvariablen initialisierenKlassenvariablen müssen initialisiert werden, sofern sie nicht die von der CLR vorein-gestellten Null/nil-Werte enthalten sollen. Da ja äußerer Code jederzeit auf die Variab-len einer Klasse zugreifen kann, noch bevor er ein Objekt dieser Klasse erzeugt hat,stellt sich die Frage, wann der frühestmögliche Zeitpunkt ist, die Variablen zu initiali-sieren.

Die Antwort findet sich in den Klassenkonstruktoren. Klassenkonstruktoren sind Klas-senmethoden, die mit class constructor deklariert und von der CLR automatisch aufge-rufen werden, bevor die Klasse das allererste Mal benutzt wird. Hier kann dieBeispielklasse also ihre Objekte White, Red usw. initialisieren:

type Color = class class var White: Color; class var Red: Color; ... class constructor Create;...

class constructor Color.Create;begin White := Color.Create; Red := Color.Create; ...

Klassen-PropertiesEntsprechend können nun auch Properties als Klassen-Properties deklariert werden.Hierzu müssen Sie die Properties in einem Abschnitt der Klassendeklaration auffüh-ren, der durch class var eingeleitet wird. Diese beiden Wörter wurden ja oben bereitszur Deklaration von Klassenvariablen benutzt und in der Tat hätte es im obigen Bei-spiel genügt, nur einmal class var zu schreiben. Das folgende Beispiel zeigt, wie zweiVariablen und zwei (Nur-Lese-)Properties als Klassenfelder deklariert werden können:

type Color = class class function GetWhite: Color; static; class var FRed: Color;

Page 39: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

324

FWhite: Color; property Red read FRed; // Zugriff über Klassenvariable ... property White read GetWhite; // oder über klassenstatische Methode ... public // Ende des "class var"-Abschnitts ... end; // Ende der Klassendeklaration

Der class var-Abschnitt endet in diesem Beispiel beim Wort public, ebenfalls beendetwerden kann ein solcher Abschnitt natürlich durch andere Sichtbarkeitsattribute wieprivate und protected sowie außerdem durch die Deklaration einer Methode.

Die in den read- und write-Direktiven eines Klassen-Properties angegebenen Variablenmüssen Klassenvariablen und die Methoden klassenstatische Methoden sein, alsoMethoden, die sowohl als class procedure/function als auch als static deklariert wurden.

3.2.6 VererbungEines der grundlegenden Merkmale der objektorientierten Programmierung ist dieVererbung, bei der eine Basisklasse alle ihre Elemente an eine abgeleitete Klasse ver-erbt. Die abgeleitete Klasse erweitert dadurch die Basisklasse. Auf der .NET-Plattformund in Object Pascal kann jede Klasse nur eine einzige Basisklasse haben.

Objekte der abgeleiteten Klasse verhalten sich zunächst so, wie Objekte der Basis-klasse. Durch neue Methodendeklarationen oder durch Überschreiben von Methodenkann die abgeleitete Klasse neue Funktionen hinzufügen oder geerbte Funktionenändern, so dass auch das Verhalten ihrer Objekte angepasst wird.

In Object Pascal und auf der .NET-Plattform ist die Vererbung so fest eingebaut, dassSie gar keine Klasse ohne Vererbung schreiben können, denn auch wenn Sie keineBasisklasse angeben, erbt eine Klasse automatisch von der Klasse System.Object, die inObject Pascal auch unter dem Aliasnamen TObject (für die Abwärtskompatibilität)angesprochen werden kann.

Ein Beispiel für die Vererbung ist jede im Formular-Designer bearbeitete Formular-klasse:

type TWinForm = class(System.Windows.Forms.Form)

Eine Neuerung von Delphi 2006 besteht darin, dass die ursprünglich anlässlich der .NET-Unter-stützung eingeführten Sprachmerkmale der Klassenvariablen und -Properties jetzt auch im Win32-Compiler zur Verfügung stehen.

> > > HINWEIS

Page 40: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

325

Durch die in Klammern hinter class folgende Angabe erbt die im Designer entworfeneKlasse TWinForm von der FCL-Klasse System.Windows.Forms.Form. Erst aufgrund die-ser Vererbung sind alle in Form definierten Properties wie das schon so häufig alsBeispiel strapazierte Property BackColor auch in den Methoden von TWinFormansprechbar, als wenn sie in der Klassendeklaration von TWinForm aufgeführt wordenwären.

Der Prozess des Ableitens ist auch in mehreren Ebenen wiederholbar, so dass sichKlassenhierarchien aufbauen lassen, in denen es allgemeine Klassen gibt, die haupt-sächlich als Basisklassen für speziellere Klassen dienen. Alle davon abgeleiteten Klas-sen müssen nur noch den ihnen eigenen Funktionsanteil realisieren und können sichfür den Funktionsbereich, in dem Übereinstimmung herrscht, auf die gemeinsameBasisklasse berufen. Die Klassenhierarchien der FCL und der VCL.NET für die visuel-len Steuerelemente sind das beste Beispiel dafür (siehe z.B. Abbildung 2.1).

Vererbung und ZuweisungskompatibilitätEine Konsequenz aus der Vererbung besteht darin, dass ein Objekt nicht nur mit denObjekten derselben Klasse kompatibel ist, sondern dass es auch überall dort verwen-det werden kann, wo ein Objekt einer seiner Vorfahrklassen erwartet wird:

var Objekt: System.Object; { Object ist die oberste aller Klassen } aComponent: Component; { Component ist abgeleitet von Object. } aForm: Form; { Form ist indirekt abgeleitet von Component. } aButton: Button; { Button stammt ebenfalls von Component ab. }...aComponent := aForm;aComponent := aButton;Objekt := aComponent;

Da Formulare und Schalter alle Fähigkeiten von Component erben, können sie anstelleeines Component-Objekts (hier die Variable aComponent) verwendet werden. Da außer-dem Component eine Nachfahrklasse von Object ist, kann sie alles tun, was ein Objectauch tun kann, daher ist auch die letzte Zuweisung des obigen Beispiels erlaubt. Derumgekehrte Weg ist nicht möglich:

Form := Object; { Typ-Fehler }

Von einem Formular werden sehr viele spezielle Dinge erwartet, eines davon ist z. B.,dass es auf dem Bildschirm sichtbar ist. Da Object aber irgendein Objekt sein kann, istes sehr unwahrscheinlich, dass es allen Anforderungen der Klasse Form genügt. Fürdie Zuweisung an eine Form-Variable kommen also nur Objekte von Form oder vondavon abgeleiteten Klassen in Frage.

Page 41: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

326

Das Ableiten einer Klasse von einer anderen führt also nicht nur dazu, dass die abge-leitete Klasse die Elemente der Basisklasse erbt, sondern es führt zu Verwandtschafts-verhältnissen, die bei der Polymorphie eine große Rolle spielen, wie wir gleich sehenwerden.

Versiegelte KlassenDelphi für .NET unterstützt auch das .NET-Konzept der versiegelten Klassen undführt dafür das neue Schlüsselwort sealed ein. Von einer mit sealed deklarierten Klassekönnen keine anderen Klassen abgeleitet werden. Ein Beispiel für eine solche versie-gelte Klasse aus dem .NET-Framework ist Thread. Sie hätte in Object Pascal wie folgtdeklariert werden können:

type Thread = class sealed procedure Start; ... end; MyThread = class(Thread) // Fehler: "Sealed-Klasse 'Thread' kann // nicht erweitert werden" end;

Die Deklaration als sealed könnte sich etwa anbieten, wenn eine Klasse in der sie umge-benden Bibliothek als unveränderbar angesehen wird – wenn es also nicht möglichsein soll, ihr Verhalten durch Überschreiben von Methoden zu ändern. Natürlichkönnte dies auch erreicht werden, wenn die Klasse einfach keine Methoden als virtuelldeklariert. Die Deklaration als sealed gibt dem Benutzer jedoch eine zusätzliche Infor-mation: Sie teilt ihm mit, dass es zwecklos ist, die Klasse per Vererbung zu erweitern,sondern dass er die gewünschte Erweiterung auf anderem Wege – z.B. per Aggrega-tion – erreichen muss:

type MyThread = class // MyThread erweitert Thread, indem es ein T: Thread; // Thread-Objekt enthält und ggf. Methodenaufrufe // an das enthaltene Objekt weiterleitet: procedure Start; { ruft T.Start auf } end;

Ein vollständiges Beispiel zu dieser Art der Erweiterung der Klasse Thread finden Siein Kapitel 2.8.2.

In diesem Zusammenhang ist es wichtig, dass Objektvariablen nur Verweise auf Objekte enthal-ten. Bei den Zuweisungen im obigen Beispiel werden nur diese Verweise kopiert, die Objekte selbst werden nicht verändert.

> > > HINWEIS

Page 42: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

327

3.2.7 Vorwärtsdeklaration von KlassenIn Object Pascal können Bezeichner grundsätzlich erst benutzt werden, nachdem derBezeichner dem Compiler bekannt gemacht wurde. Dabei ist davon auszugehen, dassder Compiler den Quelltext nur einmal von vorne nach hinten abarbeitet und an jederStelle nur die Bezeichner kennt, die bis dahin schon deklariert wurden.

Bei Klassen kommt es aber häufig vor, dass zwei Klassen sich gegenseitig benutzen.Wie soll nun die erste Klasse vor der zweiten Klasse deklariert werden, wenn gleich-zeitig die zweite Klasse vor der ersten deklariert werden muss?

Hier ist die Vorwärtsdeklaration von Klassen gefragt. Um dem Compiler beispiels-weise die Klasse TDocument bekannt zu machen, genügt die folgende Deklaration:

type TDocument = class;

Von da an können Sie Objekte dieser Klasse deklarieren. Die gegenseitige Benutzungzweier Klassen sähe dann so aus:

type TDocument = class; { Klasse für ein Dokument } TItem = class { Klasse für einen Bestandteil des Dokuments } { Der Bestandteil weiß, in welchem Dokument er sich befindet: } ParentDocument: TDocument; end; TDocument = class { Das Dokument kennt z. B. seinen ersten Bestandteil: } FirstItem: TItem; end;

3.2.8 Verschachtelte TypdeklarationenIn der FCL kommen Sie leicht mit geschachtelten Typen in Kontakt, wie etwa den inKapitel 2.2.5 beschriebenen Kollektionsklassen. Als .NET-Compiler erkennt Delphidiese geschachtelten Typen der FCL natürlich, doch nicht nur das: Delphi erlaubt nunauch in eigenen Object-Pascal-Programmen die Deklaration geschachtelter Typen. DieKlasse ListViewItemCollection könnte in Delphi etwa so nachprogrammiert werden:

type ListView = class(System.Windows.Forms.Control) // geschachtelte Klasse: type ListViewItemCollection = class(IList, ICollection, IEnumerable) ... Elemente von ListViewItemCollection ... end; ... weitere Elemente von ListView ... end;

Page 43: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

328

Bei der Benutzung des Typs ListViewItemCollection müsste dann der Name der äußerenKlasse immer mit genannt werden:

var KollektionsVerweis: ListView.ListViewItemCollection;

3.3 Objekte zur LaufzeitWir untersuchen nun die Vorgänge, die zur Laufzeit des Programms mit einem Objektpassieren können. Dazu gehören Initialisierung und Löschung mit Konstruktoren undDestruktoren sowie ein zentrales Thema der OOP: die Polymorphie, bei der sich erstzur Programmlaufzeit entscheidet, welche Methode aufgerufen wird (»späte Bin-dung«).

3.3.1 Initialisierung von Objekten: KonstruktorenDurch die Deklaration einer Variablen mit dem Typ einer Klasse erhalten Sie eine Vari-able, die zur Programmlaufzeit auf ein Objekt weisen kann, aber zunächst von der CLRmit nil initialisiert wird (was so viel bedeutet wie »diese Variable weist auf keinObjekt«):

var MeinButton: Button; // von der CLR automatisch mit nil initialisiert.

Wenn Sie versuchen, eine solche Variable zu benutzen, rufen Sie einen Laufzeitfehlerhervor. Sie können diesen Umstand ändern, indem Sie der Variablen ein bereits existie-rendes Button-Objekt zuweisen:

MeinButton := EinFormular.Button2;

Dann weisen MeinButton und EinFormular.Button2 auf dasselbe Button-Objekt.

Häufig müssen Sie ein Objekt jedoch selbst neu erzeugen, und hierzu benötigen Sieeinen Konstruktor. Das Mindeste, was bei dem Aufruf eines Konstruktors geschieht, istin jedem Fall die Reservierung von Speicher für das Objekt, denn alle Objekte einer mitclass deklarierten Klasse werden von der CLR dynamisch auf dem Heap angelegt.Außerdem erhält die Klasse im Konstruktor Gelegenheit, die Variablen des Objekts mitangemessenen Startwerten zu belegen und Ressourcen zu reservieren, die bei der spä-teren Benutzung des Objekts benötigt werden. Der übliche Name für einen Konstruk-tor ist Create.

Page 44: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

329

Konstruktoren aufrufenKonstruktoren haben nicht nur die Aufgabe, das Objekt zu initialisieren, sondern auch,das Objekt überhaupt zu erzeugen (Speicher für es zu reservieren). Um beispielsweisedas Objekt DynamicForm zu erzeugen, können Sie daher nicht die folgende Anweisungverwenden:

var DynamicForm: Form;begin DynamicForm.Create; { keine Objekterzeugung! }

Diese Anweisung ruft den Konstruktor Create mit einem Objekt (DynamicForm) auf,das noch gar nicht existiert. Um ein Objekt neu zu erzeugen, müssen Sie der Objektva-riablen (hier DynamicForm) ein neues Objekt zuweisen. Das neue Objekt erhalten Sie,indem Sie den Konstruktor nicht auf die Objektvariable, sondern auf die Klasseanwenden. Form.Create reserviert den Speicher für ein neues dynamisches Objekt, initi-alisiert das Objekt und liefert es als Ergebnis zurück, so dass Sie es der Objektvariablenzuweisen können:

DynamicForm := Form.Create;

Eigene Konstruktoren schreibenIn eigenen Klassen deklarieren Sie einen Konstruktor wie eine Methode, verwendenallerdings das Schlüsselwort constructor:

type TGraphicElement = class(TPersistent) constructor Create(InitRect: TRect); { der Name "Create" ist nicht vorgeschrieben }

In diesem Beispiel verlangt der Konstruktor als Parameter ein Rechteck, dessen Koor-dinaten zur Initialisierung der (im Listing nicht gezeigten) Objektdaten verwendetwerden sollen.

Steuerelemente und Formulare der Windows-Forms-Bibliothek haben grundsätzlich parameterlose Konstruktoren. VCL.NET-Formulare und -Komponenten erwarten hingegen als Parameter die Angabe einer Besitzerkomponente.

Um Formulare zu initialisieren, erweitern Sie bei Windows-Forms-Formularen einfach den von Delphi bereits erzeugten Konstruktorrumpf, bei VCL.NET-Formularen genügt es meistens, wenn Sie das OnCreate-Ereignis bearbeiten.

> > > HINWEIS

> > > HINWEIS

Page 45: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

330

Innerhalb eines Konstruktors müssen Sie den Konstruktor der Basisklasse aufrufen,damit auch diese sich richtig initialisieren kann:

constructor TGraphicElement.Create(InitRect: TRect);begin inherited Create; // keine Parameter für den geerbten Konstruktor ...

Falls der Konstruktor der Basisklasse Parameter hat, ist es meist sinnvoll, diese auchim eigenen Konstruktor wieder als Parameter zu verlangen und dann an den geerbtenKonstruktor weiterzugeben.

3.3.2 Ressourcenfreigabe und Garbage CollectionDas Gegenstück zu den Konstruktoren bilden die Destruktoren. Grundsätzlich solltejedes Objekt, für das in irgendeinem Teil des Programms Speicher reserviert wurde, aneiner anderen Stelle wieder freigegeben werden.

Die Arbeitsweise der Garbage CollectionAuf der .NET-Plattform stellt die im Hintergrund wirkende Garbage Collection sicher,dass alle Objekte, die nicht mehr benutzt werden, automatisch freigegeben werden. ImIdealfall erzeugen Sie daher einfach nach Belieben Objekte und kümmern sich nichtum deren Freigabe. So wurde etwa in Kapitel 2.2.4 ein Formular dynamisch erzeugtund mit ShowDialog angezeigt:

procedure TWinForm.Button3_Click(sender: System.Object; e: System.EventArgs); var F: Form;begin F := Form.Create; ... Einstellung der Formular-Properties ... ... Erzeugen und Hinzufügen der Steuerelemente zum Formular ... F.ShowDialog; // Anzeige des Formulars als Dialog.end;

Warnung für Borland-Pascal-Kenner: Im früheren Borland Pascal hätten Sie statt inherited Create auch TPersistent.Create schreiben können, um den geerbten Konstruktor aufzurufen, ohne dass dadurch ein weiteres neues Objekt konstruiert worden wäre. In Delphi würde der Aufruf TPersistent.Create ein neues TPersistent-Objekt erzeugen und als Funktionsergebnis zurücklie-fern (wobei dieses Ergebnis verloren gehen würde, weil Sie es keiner Variablen zugewiesen haben). In Delphi müssen Sie also mit dem Schlüsselwort inherited arbeiten.

Page 46: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

331

Die CLR erkennt nun das Ende einer Methode und weiß, dass beim end alle lokalen Vari-ablen der Methode ungültig werden. Sie verwaltet für jedes erzeugte Objekt einen Zähler,der angibt, wie viele Verweise auf das Objekt noch existieren. Im obigen Beispiel wird derZähler mit 1 initialisiert, wenn eine Referenz auf das Objekt in der Variablen Formulargespeichert wird. Wird die Variable Formular ungültig, weil die Methode bei end ange-kommen ist, zählt die CLR den Zähler um eins herunter. Ein Zählerstand von nullbewirkt, dass das Objekt für die automatische Freigabe vorgemerkt wird.

Nun könnte es sein, dass im Verlauf der obigen Prozedur die Variable Formular an einanderes Objekt weitergegeben wird:

var FP: FormularPool;begin ... FormularPool.MerkeFormular(Formular);

Nehmen wir an, dass MerkeFormular das übergebene Formular in den Daten von FPspeichert (z.B. in einer Kollektion), dann erhöht die CLR den Benutzungszähler desFormulars während der Ausführung von MerkeFormular automatisch auf 2. Wenn nundie Variable FP ungültig wird, wird der Zähler wieder um 1 verringert werden, doch ersteht noch nicht auf 0, daher wird das Objekt nicht zur Freigabe vorgemerkt.

Ähnlich wie am Ende einer Methode die lokal reservierten Objekte freigegeben wer-den (genauer gesagt: ihr Benutzungszähler heruntergezählt wird), geschieht es auchbei der Freigabe eines Objekts mit den Objekten, auf die das freigegebene Objekt ver-weist. Wird also im obigen Beispiel das dynamisch erzeugte Formular freigegeben,wandern auch die in ihm enthaltenen Steuerelemente in die Garbage Collection (es seidenn, es existieren außerhalb des Objekts noch Referenzen auf sie).

Da die CLR innerhalb des verwalteten Codes die absolute Kontrolle über alle Zuwei-sungen von Objektreferenzen hat, können Sie sich darauf verlassen, dass sie immergenau weiß, welche Objekte schon freigegeben werden können und welche nicht2.

Wozu trotzdem Destruktoren?So nützlich die automatische Speicherfreigabe ist, um die zunehmende Blockierungdes Speichers durch nicht freigegebene Objekte einer fehlerhaft programmiertenAnwendung (memory leaks) zu vermeiden, so wenig kann sie alle Wünsche erfüllen, dieman als Programmierer an die Freigabe von Objekten stellen kann:

2 Kommt allerdings unverwalteter Code ins Spiel, der Zugriff auf ein verwaltetes Objekt erhält, istdies nicht mehr garantiert.

Page 47: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

332

�  Einerseits kann es sein, dass Ressourcen früher freigegeben werden sollen, als esdie Garbage Collection veranlassen würde. Ein Beispiel sind Dateien, die geschlos-sen werden sollten, sobald sie nicht mehr benötigt werden. Zwar erfolgt etwa dasSchließen einer neu beschriebenen Datei automatisch, wenn das zugehörige File-Stream-Objekt freigegeben wird, doch kann bis dahin eine ganze Weile vergehen, inder eventuell andere Anwendungen nicht auf die Datei zugreifen können. Hier isteine manuelle Freigabe von Ressourcen erforderlich, die im Falle des FileStream-Objekts mit der Methode Close veranlasst werden kann.

�  Andererseits kann es sein, dass bei der automatischen Freigabe eines Objekts nichtnur der Speicher freigegeben, sondern auch noch andere Aktionen ausgeführt wer-den sollen, z.B. eine während der Aktivität des Objekts angesammelte Statistik ineine Datei schreiben oder veränderte Einstellungen in der Registry oder in einerKonfigurationsdatei zu speichern. Hier ist es erforderlich, dass man in den automa-tischen Ablauf der Garbage Collection noch einmal eingreifen kann, bevor derSpeicher des Objekts freigegeben wird.

Beides ist auf der .NET-Plattform möglich. Der erste Fall – die manuelle Freigabemög-lichkeit – ist selbstverständlich, da eine Methode wie Close jederzeit selbst definiertwerden kann. Diese Freiheit wird durch ein vom .NET-Framework vorgeschlagenesIDisposable-Muster ergänzt, mit dem solche manuelle Ressourcenfreigaben auf standar-disiertem Weg vollzogen werden können. Bei der automatischen Freigabe ruft die CLRdie Finalize-Methode des zur Freigabe bestimmten Objekts auf, bevor die Freigabe desSpeichers endgültig erfolgt. Finalize entspricht gewissermaßen den Destruktoren man-cher Sprachen, doch auf der Ebene der CLR ist der Begriff des Destruktors zunächsteinmal nicht definiert.

Beide Merkmale – Finalize-Methode und IDispose-Muster – stehen in Delphi zur Verfü-gung, wobei Borland auch das Sprachmerkmal des Destruktors in diese Welt übertragenhat. Falls Sie auch C# kennen oder sich in Zukunft damit beschäftigen werden, sei vor-weg vor einer Verwechslungsmöglichkeit gewarnt: Die Destruktoren von C# entspre-chen der Finalize-Methode auf CLR-Ebene. C#-Destruktoren können daher in Delphi nurdurch eine Finalize-Methode nachgebildet werden. Was in Delphi ein Destruktor bewirkt,kann in C# nur durch eine manuelle Implementierung des IDispose-Musters nachgebil-det werden. Die folgende Tabelle fasst diese Unterschiede zusammen und deutet schonan, dass es in Delphi zwei Varianten gibt, die nachfolgend genauer erläutert werden.

CLR-Ebene ... entspricht in C#:

... Destruktor-variante in Delphi:

... manuelle Variante in Delphi:

Automatisch aufgerufen

Finalize Destruktor Finalize Finalize

Manuell aufgerufen

IDispose-Muster

IDispose-Muster Free à Dispose à Des-truktor

IDispose-Muster

Page 48: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

333

Destruktoren in DelphiUm einen Destruktor selbst zu schreiben, deklarieren Sie eine Methode mit demSchlüsselwort destructor. In Delphi für .NET sind Destruktoren nur noch dann erlaubt,wenn sie Destroy heißen, keinen Parameter haben und in der Klassendeklaration mitder Direktive override markiert werden. In jedem Fall sollte ein Destruktor am Schlussauch den geerbten Destruktor aufrufen:

// Deklaration in der Klasse: destructor TGraphicElement.Destroy; override;

// Implementierung:destructor TGraphicElement.Destroy;begin ... inherited Destroy;end;

Borland hat diese Syntax in Delphi für .NET erlaubt, weil bestehender Delphi-Codehäufig mit solchen Destruktoren arbeitet. Der Delphi-Compiler setzt dieses Destruk-tormuster automatisch in das moderne IDisposable-Muster des .NET-Frameworks um:

�  In der kompilierten Assembly erscheint zwar eine Methode Destroy, die jedochdazu benutzt wird, die von der Schnittstelle IDisposable geforderte Methode Disposezu implementieren. Wenn Sie Ihr Objekt an ein fremdes Objekt übergeben, welchesdie IDispose-Schnittstelle erwartet, kann das fremde Objekt Dispose für Ihr Objektaufrufen, was in der Ausführung des von Ihnen geschriebenen Destruktors resul-tiert.

�  Um die Freigabe der Ressourcen zu bewirken, sollten Sie Destroy nicht direkt aufru-fen, sondern die automatisch vom Compiler generierte Methode Free. Diese über-prüft auch eine automatisch vom Compiler erzeugte Variable Disposed, mit derenHilfe sie einen mehrfachen Aufruf von Destroy verhindert.

Die Benutzung eines solchen Objekts sieht also wie folgt aus:

var objekt: TGraphicElement;begin objekt := TGraphicElement.Create; // Konstruktion ... Benutzung ... objekt.Free; // Freigabe der Ressourcen // Garbage Collection und Speicherfreigabe erfolgen später automatisch // oder können auch manuell erzwungen werden: // GC.Collect; // GC.WaitForPendingFinalizers;

Page 49: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

334

Manuelle Implementierung der IDispose-SchnittstelleSie können die IDispose-Schnittstelle auch manuell implementieren und den Code, denSie sonst im Destruktor geschrieben hätten, nun in der Methode Dispose unterbringen.Der Compiler erlaubt in diesem Fall keinen Destruktor mehr.

Die .NET-Online-Dokumentation beschreibt ein mögliches Muster zur Anwendungder IDispose-Schnittstelle, bei dem sowohl bei der manuellen Freigabe des Objekts alsauch bei der automatischen Finalisierung teilweise der gleiche Code ausgeführt wird.Hierzu wird die Methode Finalize überschrieben – die einzige Methode, die bei derFinalisierung automatisch von der CLR aufgerufen wird – und von dort die Kontrollemanuell an Dispose weitergegeben.

Dispose wird ferner auf zwei Fälle vorbereitet: den expliziten manuellen Aufruf undden automatischen Aufruf bei der Finalisierung. Beide Fälle werden durch einen boo-leschen Parameter unterschieden, wofür eine zweite Version von Dispose angelegt wer-den muss. Die standardmäßige, automatisch aufgerufene Dispose-Methode hat keinenParameter und ruft die überladene Version mit einem Parameter von True auf, Finalizehingegen verwendet den Parameter False.

Eine Übertragung dieses Musters auf Object Pascal sieht wie folgt aus:

// Deklaration im Interface-Teil der Unit:

TestClass = class (TInterfacedObject, IDisposable) private Disposed: Boolean; public procedure Dispose; overload; procedure Dispose(ExplicitCall: Boolean); overload; strict protected procedure Finalize; override;end;

// Implementierung im Implementierungsteil:

procedure TestClass.Finalize;begin inherited; Dispose(False);end;

Auch wenn IDispose vom .NET-Framework definiert ist, wird die Dispose-Methode nicht automa-tisch bei der Freigabe des Objekts aufgerufen.

> > > HINWEIS

Page 50: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

335

procedure TestClass.Dispose;begin inherited; Dispose(true); // Dispose wurde gerade manuell aufgerufen, // die Finalize-Methode braucht bei Freigabe des // Objekts nicht mehr ausgeführt zu werden: GC.SuppressFinalize(self);end;

procedure TestClass.Dispose(ExplicitCall: Boolean);begin if not Disposed then begin if ExplicitCall then begin // bei einem expliziten Aufruf wünscht der Aufrufer, dass // alle verwendeten Ressourcen vor der automatischen Finalisierung // freigegeben werden. Dies geht nur dann, wenn hier auch andere // von dieser Klasse benutzte verwaltete Ressourcen explizit // freigegeben werden, indem ihre Dispose-Methode manuell // aufgerufen wird. // Beispiel: // BenutzteKomponente.Dispose; end; // In jedem Fall (auch bei der Finalisierung) können hier // nicht verwaltete Ressourcen freigegeben werden. // Beispiel: BenutzteDatei.Close; end; Disposed := True; // doppelten Aufruf vermeiden // in von TestClass abgeleiteten Klassen müsste // statt Disposed:=True die geerbte Methode mit // inherited aufgerufen werden.end;

Die Benutzung dieser Klasse sieht dann wie folgt aus:

var objekt: TestClass;begin objekt := TestClass.Create; ... Benutzung der Klasse ... // Ressourcenfreigabe (ohne Speicherfreigabe) objekt.Dispose; // manuelle Freigabe

Die Speicherfreigabe erfolgt irgendwann nach dem Aufruf von Dispose automatisch,wenn die Garbage Collection Zeit dafür hat oder wenn Sie sie manuell erzwingen,indem Sie GC.Collect aufrufen. Wichtig ist, dass aufgrund des Dispose-Aufrufs keineautomatische Finalisierung des Objekts mehr zu erfolgen braucht (siehe den Suppress-Finalize-Aufruf in TestClass.Dispose), was der GC einigen Verwaltungsaufwand erspart.

Page 51: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

336

Dispose für FormulareDas oben gezeigte Muster wird auch in dem von Delphi automatisch erzeugten Win-Forms-Formular-Code teilweise implementiert: Jedes Formular erhält eine MethodeDispose mit einem Parameter Disposing, der dem oben gezeigten Parameter ExplicitCallentspricht:

procedure TWinForm1.Dispose(Disposing: Boolean);begin if Disposing then begin if Components <> nil then Components.Dispose(); end; inherited Dispose(Disposing);end;

Im Falle Disposing = True ruft die Dispose-Methode des Formulars die Dispose-Metho-den aller in ihm enthaltenen Komponenten auf (was jedoch wiederum nur dann eineRolle spielt, wenn das Formular zur Entwurfszeit im Formular-Designer aktiv seinsollte, denn zur Laufzeit wird Components, die Liste der Komponenten, gar nichtbenutzt und bleibt leer3).

Die Finalize-Methode in DelphiWie erwähnt, ist die Finalize-Methode die einzige Methode, die von der Garbage Coll-ection standardmäßig automatisch aufgerufen wird. Das obige Codebeispiel enthältein Beispiel für das Überschreiben der Finalize-Methode und zeigt ein Muster dafür,wie bei automatischem Finalize und manuellem Free-Aufruf der gleiche Freigabecodeausgeführt werden kann.

Sie finden das obige Codebeispiel zusammen mit einer anderen Beispielklasse, die dieselbe Funk-tionsweise mit Hilfe von Delphis Destruktorsyntax bewerkstelligt, auf der CD im Projekt GCDisposeVarianten.

Wenn Sie einen Destruktor schreiben und die IDispose-Schnittstelle automatisch vom Compiler generieren lassen, können Sie Dispose nicht, wie im letzten Codebeispiel gezeigt, manuell aufrufen, sondern nur, wenn Sie eine zusätzliche Typumwandlung vornehmen: (objekt as IDisposable).Dis-pose. (Dies nur zur Theorie – wenn Sie schon nach alter Delphi-Tradition einen Destruktor schrei-ben, werden Sie ihn wahrscheinlich eher über die traditionelle Delphi-Methode Free aufrufen wollen.)

3 Wann ist der Code einer Formular-Unit schon zur Entwurfszeit aktiv? Erst wenn ein anderes For-mular von diesem Formular erbt, siehe Kapitel 2.1.4.

> > > HINWEIS

Page 52: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Die Sprache Delphi in der .NET-Umgebung

337

Sie können Finalize jedoch auch unabhängig von der Implementierung einer IDisposable-Schnittstelle oder eines Destruktors verwenden, wenn Ihre Klasse keine manuelle Freiga-bemöglichkeit benötigt und Sie stets nur eine automatische Aktion programmieren wol-len, die bei der Finalisierung des Objekts ausgeführt wird. Es ist jedoch zu beachten, dasseine Finalize-Methode selbst keine neuen Objekte mehr erzeugen darf.

Interne Funktionsweise der FinalisierungFinalize-Methoden wirken sich verzögernd auf den Ablauf der Garbage Collection aus.Zunächst verpflichten sie die Garbage Collection zu einem zusätzlichen Durchlaufdurch ihre internen Objektlisten: Wenn ein Objekt, das eine Finalize-Methode besitzt,nicht mehr referenziert wird (sein Benutzungszähler auf null fällt), wird es zunächst indie Liste der finalisierbaren Objekte eingereiht. Die von Zeit zu Zeit ausgeführte Gar-bage Collection ruft für die in dieser Liste vorgefundenen Objekte die Finalize-Metho-den auf und reiht die Objekte danach in die Liste der freigebbaren Objekte ein. Erst beieinem Durchlauf dieser zweiten Liste kann dann der Speicher des Objekts freigegebenwerden.

Hinzu kommt, dass die Garbage Collection bis zum Abschluss der Finalize-Methodeauch die Objekte, auf die das zu finalisierende Objekt verweist, nicht freigeben kann,da es ja möglich ist, dass die Finalize-Methode noch auf diese Objekte zugreift.

Freigabe von Variablen des ObjektsIn den bisher als Beispiel gezeigten Aufrufen von Free und Dispose wurden dieseimmer auf Objekte angewendet, die durch lokale Variablen referenziert wurden.Lokale Variablen werden nach Ablauf der Methode sowieso ungültig, so dass es kaumvorkommen sollte, dass Sie das gleiche Objekt mehrfach freigeben.

Bei Objekten, die in Variablen einer Klasse gespeichert sind, kann es unter Umständenvorkommen, dass Sie sie freigeben wollen, ohne dass die Variablen irgendwann ungül-tig werden. Die Variable, deren Objekt freigegeben wird, sollte dann auf nil gesetztwerden, was so viel bedeutet wie »nicht zugewiesen«:

Sie können also in einer Finalisierungsmethode eines Objekts auch die von diesem Objekt verwen-deten anderen Objekte ansprechen, da die Freigabe der anderen Objekte frühestens nach der Fina-lize-Methode erfolgt. Sie müssen aber damit rechnen, dass für diese noch nicht freigegebenen Objekte bereits die Finalize-Methode aufgerufen wurde und die Objekte daher evtl. nicht mehr wie gewohnt benutzt werden können. Die Reihenfolge des Finalize-Aufrufs mehrerer Objekte ist nicht definiert und kann sich von Programmlauf zu Programmlauf (oder von Version zu Version der CLR) ändern, Sie müssen eine Finalize-Methode daher schreiben, ohne eine bestimmte Reihenfolge vorauszusetzen.

> > > WICHTIG

Page 53: Delphi 2006  - *ISBN 3-8273-2388-6* - © 2006 by ... · Kapitel 3 288 Einführung Object Pascal bzw. die Sprache Delphi, wie Borland sie neuerdings bezeichnet, ist

Kapitel 3

338

// Falls Destruktorsyntax verwendet wird:DynamicObject.Free;// oder: falls IDisposable verwendet wird:// DynamicObject.DisposeDynamicObject := nil;

Sie können dann mit der Funktion Assigned abfragen, ob noch ein Objekt zugewiesenist (Assigned gibt False zurück, wenn die angegebene Variable nil ist), und vermeiden,dass das bereits freigegebene Objekt versehentlich benutzt oder noch einmal freigege-ben wird:

if Assigned(DynamicObject) then DynamicObject.TueEtwas;

Freigabe von teilinitialisierten ObjektenWenn innerhalb des Konstruktors eine Exception auftritt, wird die Ausführung desKonstruktors automatisch abgebrochen. Sollten Sie diese Exception abfangen und dasProgramm weiterlaufen lassen, wird es irgendwann dazu kommen, dass das teilinitia-lisierte Objekt freigegeben wird. Finalize- und Dispose-Methoden (sowie Destruktoren)sollten daher auf den Fall vorbereitet sein, dass der Konstruktor nicht vollständig aus-geführt, das Objekt also nur zum Teil initialisiert wurde. Im folgenden Beispiel könntees Probleme geben:

constructor DemoObject.Create;begin inherited.Create; File1 := OpenFile1; // hier könnte z. B. eine Exception auftreten, File2 := OpenFile2; // File2 würde dann nicht initialisiert werden.end;

procedure DemoObject.Finalize;begin File1.Close; File2.Close; // hier kann File2 = nil sein! inherited Destroy;end;

Falls in Create während der Operation OpenFile1 (oder vorher!) eine Exception auftritt,sind die Variablen File1 und File2 im Destruktor noch nicht initialisiert. Der Finalisie-rungscode wäre also nur dann sicher, wenn er wie folgt erweitert würde:

if Assigned(File2) then File2.Close;if Assigned(File1) then File1.Close;