Programme und Units (Delphi)

Aus RAD Studio
Wechseln zu: Navigation, Suche

Nach oben zu Programme und Units - Index


Dieses Thema enthält Informationen zur Struktur einer Delphi-Anwendung und beschreibt den program-Kopf, die unit-Deklarationssyntax und die uses-Klausel.

  • Aufteilung großer Programme in Module, die separat bearbeitet werden können.
  • Erstellung von Bibliotheken, die von mehreren Programmen genutzt werden können.
  • Weitergabe von Bibliotheken an andere Entwickler, ohne den Quelltext verfügbar zu machen.

Programmstruktur und -syntax

Eine vollständige, ausführbare Delphi-Anwendung besteht aus mehreren Unit-Modulen, die über ein Quelltextmodul, die Projektdatei, miteinander verbunden sind. Bei der herkömmlichen Pascal-Programmierung wird der gesamte Quelltext - einschließlich des Hauptprogramms - in .pas-Dateien gespeichert. Embarcadero-Tools verwenden die Namenserweiterung .dpr, um das Quellmodul des Hauptprogramms zu kennzeichnen. Der weitere Quelltext befindet sich zum größten Teil in sogenannten Unit-Dateien mit der Namenserweiterung .pas. Damit ein Projekt vollständig compiliert werden kann, benötigt der Compiler neben der Projektquelldatei entweder eine Quelltextdatei oder eine compilierte Unit-Datei für jede Unit.

Hinweis: Es müssen keine Units in ein Projekt aufgenommen werden, alle Programme verwenden aber automatisch die Unit System sowie die Unit SysInit.

Die Quelltextdatei für eine ausführbare Delphi-Anwendung enthält folgende Elemente:

  • Einen program-Kopf
  • Eine uses-Klausel (optional)
  • Einen Block mit Deklarationen und ausführbaren Anweisungen


Der Compiler und somit auch die IDE setzt voraus, dass sich diese drei Elemente in einer einzelnen Projektdatei (.dpr) befinden.

Der Programmkopf

Der program-Kopf enthält den Namen des ausführbaren Programms. Er besteht aus dem reservierten Wort program, einem nachgestellten gültigen Bezeichner und einem abschließenden Strichpunkt. In Anwendungen, die mit Embarcadero-Tools entwickelt werden, muss der Bezeichner dem Namen der Projektquelldatei entsprechen.

Das folgende Beispiel zeigt die Projektquelldatei für ein Programm namens Editor. Der Name der Projektdatei lautet demnach Editor.dpr.

program Editor;

  uses Forms, REAbout, // Ein Info-Dialogfeld 
              REMain;         // Hauptformular

  {$R *.res}

  begin 
   Application.Title := 'Text Editor'; 
   Application.CreateForm(TMainForm, MainForm); 
   Application.Run; 
  end.

Die erste Zeile enthält den Programmkopf mit dem reservierten Wort program. Über die uses-Klausel wird eine Abhängigkeit von drei zusätzlichen Units definiert: Forms, REAbout und REMain. Die Compiler-Direktive $R linkt die Ressourcendatei des Projekts zum Programm. Der Block mit den Anweisungen zwischen den Schlüsselwörtern begin und end wird beim Starten des Programms ausgeführt. Die Projektdatei wird wie alle Delphi-Quelltextdateien mit einem Punkt (nicht mit einem Strichpunkt) beendet.

Delphi-Projektdateien sind normalerweise kurz, da die Programmlogik üblicherweise in Unit-Dateien erstellt wird. Eine Delphi-Projektdatei enthält in der Regel nur den Code, der zum Starten des Hauptfensters der Anwendung und der Schleife für die Ereignisverarbeitung erforderlich ist. Projektdateien werden automatisch von der IDE generiert und verwaltet und nur in seltenen Fällen manuell bearbeitet.

In Standard-Pascal können im Programmkopf hinter dem Programmnamen auch Parameter angegeben werden:

program Calc(input, output);

Der Delphi-Compiler von Embarcadero ignoriert diese Parameter.

In RAD Studio wird mittels des Programmkopfs ein eigener Namespace, der so genannte Standardprojekt-Namespace, eingefügt.

Die uses-Klausel

Die uses-Klausel gibt alle Units an, die in das Programm aufgenommen werden. Diese Units können eigene uses-Klauseln enthalten. Ausführliche Informationen zu uses-Klauseln in Unit-Quelldateien finden Sie weiter unten im Abschnitt Unit-Referenzen und die uses-Klausel.

Eine uses-Klausel besteht aus dem reservierten Wort uses und einer durch Kommas getrennten Liste der Units, von denen die Projektdatei direkt abhängig ist.

Der Block

Ein Block enthält eine einfache oder strukturierte Anweisung, die beim Starten des Programms ausgeführt wird. In den meisten program-Dateien besteht der Block aus einer zusammengesetzten Anweisung zwischen den reservierten Wörtern begin und end. Die einzelnen Anweisungen sind einfache Aufrufe von Methoden des Application-Objekts des Projekts. Die meisten Projekte verfügen über eine globale Application-Variable, die z. B. eine Instanz von Vcl.Forms.TApplication, Web.WebBroker.TWebApplication, oder Vcl.SvcMgr.TServiceApplication enthält. Ein Block kann außerdem Deklarationen von Konstanten, Typen, Variablen, Prozeduren und Funktionen enthalten. Die Deklarationen müssen im Block vor den Anweisungen stehen. Beachten Sie bitte, dass nach dem end, das das Ende des Quelltextes kennzeichnet, ein Punkt (.) stehen muss:

begin 
  . 
  . 
  . 
end.

Unit-Struktur und -Syntax

Eine Unit besteht aus Typen (einschließlich Klassen), Konstanten, Variablen und Routinen (Prozeduren und Funktionen). Jede Unit wird in einer separaten Quelldatei (.pas) definiert.

Eine Unit-Datei beginnt mit dem unit-Kopf, auf den das Schlüsselwort interface folgt. Nach dem Schlüsselwort interface wird in der uses-Klausel eine Liste mit Unit-Abhängigkeiten angegeben. Darauf folgen der implementation-Abschnitt und die optionalen initialization- und finalization-Abschnitte. Die Struktur einer Unit-Quelldatei sieht also folgendermaßen aus:

unit Unit1;

interface

uses // Liste der Unit-Abhängigkeiten... 
  // Interface-Abschnitt

implementation

uses // Liste der Unit-Abhängigkeiten...

// Implementierung der Klassenmethoden, -prozeduren und -funktionen...

initialization

// Unit-Initialisierungscode...

finalization

// Unit-Finalisierungscode...

end.

Eine Unit muss mit dem reservierten Wort end und einem Punkt abgeschlossen werden.

Der Unit-Kopf

Der Unit-Kopf gibt den Namen der Unit an. Er besteht aus dem reservierten Wort unit, einem nachgestellten gültigen Bezeichner und einem abschließenden Strichpunkt. In Anwendungen, die mit Embarcadero-Tools entwickelt werden, muss der Bezeichner dem Namen der Unit-Datei entsprechen. Beispiel:

unit MainForm;

Dieser Unit-Kopf kann in einer Quelltextdatei namens MainForm.pas verwendet werden. Die Datei mit der compilierten Unit trägt dann den Namen MainForm.dcu. Unit-Namen müssen in einem Projekt eindeutig sein. Auch wenn die Unit-Dateien in unterschiedlichen Verzeichnissen gespeichert werden, dürfen in einem Programm keine Units mit identischen Namen verwendet werden.

Der interface-Abschnitt

Der interface-Abschnitt einer Unit beginnt mit dem reservierten Wort interface. Er endet mit dem Beginn des implementation-Abschnitts. Der interface-Abschnitt deklariert Konstanten, Typen, Variablen, Prozeduren und Funktionen, die für Clients verfügbar sind. Clients sind andere Units oder Programme, die auf Elemente aus dieser Unit zugreifen möchten. Solche Entitäten werden als public bezeichnet, da Code in anderen Units auf sie wie auf Entitäten zugreifen kann, die in der Unit selbst deklariert sind.

Die interface-Deklaration einer Prozedur oder Funktion enthält nur die Signatur der Routine. Die Signatur umfasst den Namen der Routine, Parameter und den Rückgabetyp (bei Funktionen). Der Block, der den ausführbaren Code für die Prozedur bzw. Funktion enthält, wird im implementation-Abschnitt definiert. Prozedur- und Funktionsdeklarationen im interface-Abschnitt entsprechen also forward-Deklarationen.

Die interface-Deklaration einer Klasse muss die Deklarationen aller Klassen-Member enthalten: Felder, Eigenschaften, Prozeduren und Funktionen.

Der interface-Abschnitt kann eine eigene uses-Klausel enthalten, die unmittelbar auf das Schlüsselwort interface folgen muss.

Der implementation-Abschnitt

Der implementation-Abschnitt einer Unit beginnt mit dem reservierten Wort implementation und endet mit dem Beginn des initialization-Abschnitts oder, wenn kein initialization-Abschnitt vorhanden ist, mit dem Ende der Unit. Der implementation-Abschnitt definiert Prozeduren und Funktionen, die im interface-Abschnitt deklariert wurden. Im implementation-Abschnitt können diese Prozeduren und Funktionen in beliebiger Reihenfolge definiert und aufgerufen werden. Sie brauchen in den Prozedur- und Funktionsköpfen keine Parameterlisten anzugeben, wenn diese im implementation-Abschnitt definiert werden. Geben Sie jedoch eine Parameterliste an, muss diese der Deklaration im interface-Abschnitt exakt entsprechen.

Außer den Definitionen der public Prozeduren und Funktionen kann der implementation-Abschnitt Deklarationen von Konstanten, Typen (einschließlich Klassen), Variablen, Prozeduren und Funktionen enthalten, die für die Unit private sind. Das heißt, dass andere Units nicht auf Entitäten zugreifen können, die im implementation-Abschnitt deklariert sind (im Gegensatz zu Entitäten im interface-Abschnitt).

Der implementation-Abschnitt kann eine eigene uses-Klausel enthalten, die unmittelbar auf das Schlüsselwort implementation folgen muss. Bezeichner, die in den im implementation-Abschnitt angegebenen Units deklariert sind, stehen nur innerhalb des implementation-Abschnitts zur Verfügung. Auf diese Bezeichner kann im interface-Abschnitt nicht Bezug genommen werden.

Der initialization-Abschnitt

Der initialization-Abschnitt ist optional. Er beginnt mit dem reservierten Wort initialization und endet mit dem Beginn des finalization-Abschnitts oder - wenn kein finalization-Abschnitt vorhanden ist - mit dem Ende der Unit. Der initialization-Abschnitt enthält Anweisungen, die beim Programmstart in der angegebenen Reihenfolge ausgeführt werden. Arbeiten Sie beispielsweise mit definierten Datenstrukturen, können Sie diese im initialization-Abschnitt initialisieren.

Für Units in der uses-Liste des interface-Abschnitts werden die von einem Client verwendeten initialization-Abschnitte von Units in der Reihenfolge ausgeführt, in der die Units in der uses-Klausel des Clients aufgeführt sind.

Die ältere "begin ... end."-Syntax kann weiterhin verwendet werden. Im Grunde kann das reservierte Wort "begin" gefolgt von keiner oder mehreren Ausführungsanweisungen anstelle von initialization verwendet werden. Code, der die "begin ... end."-Syntax verwendet, darf keinen finalization-Abschnitt haben. In diesem Fall wird die Finalisierung durch Bereitstellen einer Prozedur für die Variable ExitProc vorgenommen. Diese Methode wird für künftige Entwicklungen nicht empfohlen, kommt aber in älterem Quelltext vor.

Der finalization-Abschnitt

Der finalization-Abschnitt ist optional und kann nur in Units verwendet werden, die auch einen initialization-Abschnitt enthalten. Dieser Abschnitt beginnt mit dem reservierten Wort finalization und endet mit dem Ende der Unit. Er enthält Anweisungen, die beim Beenden des Hauptprogramms ausgeführt werden (es sei denn, die Halt-Prozedur wird zum Beenden des Programms verwendet). Im finalization-Abschnitt sollten Sie die Ressourcen freigeben, die im initialization-Abschnitt zugewiesen wurden.

finalization-Abschnitte werden in der umgekehrten Reihenfolge der initialization-Abschnitte ausgeführt. Initialisiert eine Anwendung beispielsweise die Units A, B und C in dieser Reihenfolge, werden die finalization-Abschnitte dieser Units in der Reihenfolge C, B und A ausgeführt.

Mit dem Beginn der Ausführung des initialization-Codes einer Unit ist sichergestellt, dass der zugehörige finalization-Abschnitt beim Beenden der Anwendung ausgeführt wird. Der finalization-Abschnitt muss deshalb auch unvollständig initialisierte Daten verarbeiten können, da der initialization-Code bei Auftreten eines Laufzeitfehlers möglicherweise nicht vollständig ausgeführt wird.

Unit-Referenzen und die uses-Klausel

Eine uses-Klausel in einem Programm, einer Bibliothek oder einer Unit gibt die von diesem Modul verwendeten Units an. Die uses-Klausel kann an folgenden Positionen verwendet werden:

  • Projektdatei eines Programms program oder einer Bibliothek library
  • interface-Abschnitt einer Unit
  • implementation-Abschnitt einer Unit

Die meisten Projektdateien enthalten, wie die interface-Abschnitte der meisten Units, eine uses-Klausel. Der implementation-Abschnitt einer Unit kann eine eigene uses-Klausel enthalten.

Die Units System und SysInit werden automatisch von jeder Anwendung verwendet und dürfen nicht in der uses-Klausel angegeben werden. (System implementiert Routinen für die Datei-E/A, String-Verarbeitung, Gleitkommaoperationen, dynamische Speicherzuweisung usw.). Andere Standard-Units (Bibliotheken) wie SysUtils müssen dagegen in der uses-Klausel angegeben werden. Normalerweise werden alle erforderlichen Units automatisch in der uses-Klausel platziert, wenn Sie in der IDE Units in ein Projekt einfügen oder daraus entfernen.

Berücksichtigung der Schreibweise: In unit-Deklarationen und uses-Klauseln müssen Unit-Namen mit den Dateinamen hinsichtlich der Groß-/Kleinschreibung übereinstimmen. In einem anderen Kontext (beispielsweise bei qualifizierten Bezeichnern) spielt bei Unit-Namen die Groß-/Kleinschreibung keine Rolle. Um Probleme mit Unit-Referenzen zu vermeiden, verweisen Sie auf die Unit-Quelldatei explizit:

uses MyUnit in "myunit.pas";

Enthält die Projektdatei eine solche explizite Referenz, können andere Quelldateien die Unit mit einer einfachen uses-Klausel referenzieren:

uses Myunit;

Die Syntax der uses-Klausel

Eine uses-Klausel besteht aus dem reservierten Wort uses, einem oder mehreren durch Kommas getrennter Unit-Namen und einem abschließenden Strichpunkt. Beispiele:

uses Forms, Main;

uses 
  Forms, 
  Main;

uses Windows, Messages, SysUtils, Strings, Classes, Unit2, MyUnit;

In der uses-Klausel eines Programms bzw. einer Bibliothek kann auf den Namen jeder Unit das reservierte Wort in mit dem Namen einer Quelltextdatei folgen. Der Name wird mit oder ohne Pfad in Hochkommas angegeben. Die Pfadangabe kann absolut oder relativ sein. Beispiele:

uses 
  Windows, Messages, SysUtils, 
  Strings in 'C:\Classes\Strings.pas', Classes;

Fügen Sie das Schlüsselwort in nach dem Unit-Namen ein, wenn Sie die Quelltextdatei einer Unit angeben müssen. Da in der IDE vorausgesetzt wird, dass die Unit-Namen den Namen der Quelltextdateien entsprechen, in denen sie gespeichert sind, ist die Angabe des Namens der Quelltextdatei normalerweise nicht erforderlich. Das reservierte Wort in wird nur benötigt, wenn die Position der Quelltextdatei aus folgenden Gründen nicht eindeutig ist:

  • Die Quelltextdatei befindet sich in einem anderen Verzeichnis als die Projektdatei, und dieses Verzeichnis ist nicht im Suchpfad des Compilers aufgeführt.
  • Mehrere Verzeichnisse im Suchpfad des Compilers enthalten Units mit identischen Namen.
  • Sie compilieren in der Befehlszeile eine Konsolenanwendung und haben einer Unit einen Bezeichner zugeordnet, der nicht dem Namen der Quelltextdatei entspricht.

Der Compiler stellt mit der Konstruktion in ... auch fest, welche Units Teil eines Projekts sind. Nur Units, auf die in der uses-Klausel einer Projektdatei (.dpr) das reservierte Wort in und ein Dateiname folgt, werden als Teil des Projekts betrachtet. Alle anderen Units in der uses-Klausel werden zwar vom Projekt genutzt, gehören aber nicht zu ihm. Dieser Unterschied ist zwar für die Compilierung ohne Bedeutung, wird aber in IDE-Tools wie der Projektverwaltung sichtbar.

In der uses-Klausel einer Unit können Sie in nicht verwenden, um für den Compiler die Position einer Quelltextdatei anzugeben. Jede Unit muss sich im Suchpfad des Compilers befinden. Außerdem müssen die Namen der Units mit den Namen der Quelltextdateien identisch sein.

Mehrere und indirekte Unit-Referenzen

Die Reihenfolge der Units in der uses-Klausel bestimmt die Reihenfolge der Initialisierung dieser Units und wirkt sich auf die Suche des Compilers nach den Bezeichnern aus. Wenn zwei Units eine Variable, eine Konstante, einen Typ, eine Prozedur oder eine Funktion mit identischem Namen deklarieren, verwendet der Compiler die Deklaration der in der uses-Klausel zuletzt angegebenen Unit. Wollen Sie auf den Bezeichner in einer anderen Unit zugreifen, müssen Sie den vollständigen Bezeichnernamen angeben: UnitName.Bezeichner.

Eine uses-Klausel muss nur die Units enthalten, die direkt vom Programm bzw. von der Unit verwendet werden, in dem bzw. der die Klausel steht. Referenziert beispielsweise Unit A Konstanten, Typen, Variablen, Prozeduren oder Funktionen, die in Unit B deklariert sind, muss die Unit B explizit von Unit A verwendet werden. Referenziert B wiederum Bezeichner aus Unit C, ist Unit A indirekt von Unit C abhängig. In diesem Fall muss Unit C nicht in einer uses-Klausel in Unit A angegeben werden. Der Compiler benötigt jedoch Zugriff auf die Units B und C, während Unit A verarbeitet wird.

Das folgende Beispiel illustriert diese indirekte Abhängigkeit:

program Prog; 
uses Unit2; 
const a = b; 
// ...

unit Unit2; 
interface 
uses Unit1; 
const b = c; 
// ...

unit Unit1; 
interface 
const c = 1; 
// ...

Hier hängt das Programm Prog direkt von Unit2 ab, die wiederum direkt von Unit1 abhängig ist. Prog ist also indirekt von Unit1 abhängig. Da Unit1 nicht in der uses-Klausel von Prog angegeben ist, sind die in Unit1 deklarierten Bezeichner für Prog nicht verfügbar.

Damit ein Client-Modul compiliert werden kann, muss der Compiler Zugriff auf alle Units haben, von denen der Client direkt oder indirekt abhängt. Wenn sich der Quelltext für diese Units nicht geändert hat, benötigt der Compiler aber nur deren .dcu-Dateien, nicht die entsprechenden Quelltextdateien (.pas).

Werden im interface-Abschnitt einer Unit Änderungen vorgenommen, müssen die von dieser Unit abhängigen Units neu compiliert werden. Werden die Änderungen dagegen nur im implementation- oder einem anderen Abschnitt einer Unit vorgenommen, müssen die abhängigen Units nicht neu compiliert werden. Der Compiler überwacht diese Abhängigkeiten und nimmt Neucompilierungen nur vor, wenn dies erforderlich ist.

Zirkuläre Unit-Referenzen

Wenn sich Units direkt oder indirekt gegenseitig referenzieren, werden sie als gegenseitig voneinander abhängig bezeichnet. Gegenseitige Abhängigkeiten sind zulässig, wenn keine zirkulären Pfade auftreten, die eine uses-Klausel im interface-Abschnitt einer Unit mit der uses-Klausel im interface-Abschnitt einer anderen Unit verbinden. Ausgehend vom interface-Abschnitt einer Unit darf es also nicht möglich sein, über die Referenzen in den uses-Klauseln in interface-Abschnitten anderer Units wieder zu dieser Ausgangsklausel zu gelangen. Damit eine Gruppe gegenseitiger Abhängigkeiten gültig ist, muss der Pfad jeder zirkulären Referenz über die uses-Klausel mindestens eines implementation-Abschnitts führen.

Im einfachsten Fall mit zwei gegenseitig voneinander abhängigen Units bedeutet dies, dass die Units sich nicht gegenseitig in den uses-Klauseln der jeweiligen interface-Abschnitte referenzieren dürfen. Das folgende Beispiel führt also bei der Compilierung zu einem Fehler:

unit Unit1; 
interface 
uses Unit2; 
// ...

unit Unit2; 
interface 
uses Unit1; 
// ...

Eine gültige gegenseitige Referenzierung ist jedoch möglich, indem eine der Referenzen in den implementation-Abschnitt verschoben wird:

unit Unit1; 
interface 
uses Unit2; 
// ...

unit Unit2; 
interface 
//...

implementation 
uses Unit1; 
// ...

Um unzulässige zirkuläre Referenzen zu vermeiden, sollten Sie die Units möglichst in der uses-Klausel des implementation-Abschnitts angeben. Werden jedoch Bezeichner einer anderen Unit im interface-Abschnitt verwendet, muss die betreffende Unit in der uses-Klausel des interface-Abschnitts angegeben werden.

Siehe auch