Logo

Marco Cantù's
Essential Pascal

Kapitel 9
Windows-Programmierung

Delphi bietet eine vollständige Kapselung des zugrundeliegenden Windows-API (Programmierinterface für Anwendungen) durch Objekt Pascal und die Visuelle Komponentenbibliothek (VCL), so dass es nur selten erforderlich ist, Windows-Applikationen zu entwickeln, in denen reines Pascal benutzt wird und die Windows API-Funktionen direkt aufgerufen werden. Dennoch haben Programmierer die Möglichkeit, bestimmte Spezialtechniken anzuwenden, die nicht von der VCL unterstützt werden. Sie werden diese Möglichkeit nur in ganz speziellen Fällen benötigen, wie z.B. bei der Entwicklung von neuen Delphi-Komponenten, die auf der Verwendung von selten benötigten API-Aufrufen arbeiten, und ich möchte daher hier nicht die Details dazu erläutern. Statt dessen werden wir einen Blick auf Delphi's Interaktion mit dem Betriebssysstem werfen und Techniken vorstellen, von denen Sie als Delphi-Programmierer profitieren können.

Windows Handles

Unter den Datentypen, die in Delphi für den Umgang mit Windows eingeführt wurden, bilden Handles (zu deutsch etwa Griff oder Referenz) die wichtigste Gruppe. Der Name des Datentyps ist THandle und er wird in der Unit Windows wie folgt definiert:

type
  THandle = LongWord;

Der Datentyp Handle ist als Nummer implementiert, aber er wird nicht als solcher verwendet. Für Windows bildet ein Handle eine Referenz auf eine interne Datenstruktur des Systems. Wenn Sie z.B. mit einem Fenster arbeiten (oder einem Delphi-Formular) so gibt Ihnen das System ein Handle für dieses Fenster. Das System informiert Sie darüber, dass das Fenster mit dem Sie arbeiten z.B. die Nummer 142 trägt. Von dieser Stelle an kann Ihre Anwendung das System auffordern, das Fenster Nummer 142 zu bearbeiten - es zu verschieben, die Größe zu verändern, es als Icon zu verkleinern usw. Viele Windows API-Funktionen benötigen daher ein Handle als ersten Parameter. Das gilt nicht nur für Funktionen, die Fenster manipulieren; andere Windows API-Funktionen besitzen als ersten Prameter ein GDI-Handle, ein Menü-Handle, ein Instanz-Handle, ein Bitmap-Handle oder eines der vielen anderen Handle-Typen.

Mit anderen Worten, ein Handle ist ein interner Code, mit dem Sie auf ein bestimmtes Element verweisen können, welches durch das System verwaltet wird, einschließlich einem Fenster, einem Bitmap, einem Icon, einem Speicherblock, einem Cursor, einem Font, einem Menü u.s.w.. In Delphi müssen Sie diese Handle nur selten verwenden, da sie innerhalb der Formulare, Bitmaps und anderen Delphi-Objekten verborgen sind. Sie werden jedoch nützlich, wenn Sie Windows API-Funktionen aufrufen möchten, die nicht von Delphi unterstützt werden.

Um diese Beschreibung zu vervollständigen, ist hier ein einfaches Beispiel, das Windows-Handle demonstriert. Das Programm WHandle besitzt ein einfaches Formular, welches nur einen Schalter beinhaltet. Im Code behandle ich das OnCreate-Ereignis des Formulars und das OnClick-Ereignis des Schalters, wie es die folgende Textdarstellung des Hauptformulars darstellt:

object FormWHandle: TFormWHandle
  Caption = 'Window Handle'
  OnCreate = FormCreate
  object BtnCallAPI: TButton
    Caption = 'Das API aufrufen'
    OnClick = BtnCallAPIClick
  end
end

Sobald das Formular erzeugt wird empfängt das Programm ein Handle des Fensters, welches mit dem Formular verbunden ist, durch Zugriff auf die Handle-Eigenschaft des Formulars selbst. Wir benutzen IntToStr, um den Zahlenwert des Handles in einen String umzuwandeln und hängen ihn anschließend an die Beschriftung des Formulars an, wie Sie in Abbildung 9.1 sehen können:

procedure TFormWHandle.FormCreate(Sender: TObject);
begin
  Caption := Caption + ' ' + IntToStr (Handle);
end;

Da FormCreate eine Methode der Formularklasse ist, kann sie direkt auf andere Eigenschaften und Methoden dieser Klasse zugreifen. Daher können wir einfach auf die Caption-Eigenschaft des Formulars und seiner Handle-Eigenschaft direkt zugreifen.

Abbildung 9.1: Das Beispiel WHandle zeigt das Handle des Formular-Fensters. Jedes mal, wenn Sie das Programm starten, werden Sie einen anderen Wert erhalten.

Jedes mal, wenn Sie dieses Programm starten, werden sie auch jedesmal einen unterschiedlichen Wert für das Handle erhalten. Der Wert als solcher wird durch Windows bestimmt und dann an die Applikation zurückgeliefert. (Handles werden niemals durch das Programm bestimmt und haben auch keinen vordefinierten Wert; sie werden durch das System vergeben, welches jedesmal, wenn Sie das Programm starten, einen neuen Wert erzeugt.)

Wenn der Anwender den Schalter betätigt, ruft das Programm einfach eine Windows API-Funktion auf, welche den Text bzw. die Beschriftung des Fenster verändert, welches als erster Parameter übergeben wurde. Um genauer zu sein, der erste Parameter dieser API-Funktion ist das Handle desjenigen Fensters, welches wir verändern möchten:

procedure TFormWHandle.BtnCallAPIClick(Sender: TObject);
begin
  SetWindowText (Handle, 'Hallo');
end;

Dieser Code hat den gleichen Effekt wie die vorherige Ereignisbehandlungsroutine, welche die Beschriftung des Fensters durch Übergabe eines neuen Wertes an die Caption-Eigenschaft des Formulars verändert hat. In diesem Fall hat die Verwendung einer API-Funktion keinen Sinn, da hierfür eine einfachere Delphi-Technik existiert. Einige API-Funktionen besitzen keine Entsprechung in Delphi, wie wir noch in einigen komplizierteren Beispielen später in diesem Kapitel sehen werden.

Externe Deklarationen

Ein weiteres wichtiges Element für die Windows-Programmierung stellten externe Deklarationen dar. Ursprünglich wurden diese verwendet, um Pascal-Code mit externen Funktionen zu verbinden, die in Assembler geschrieben wurden. Für die Windows-Programmierung wird die externe Deklaration verwendet, um Funktionen in einer DLL aufzurufen (eine dynamisch eingebundene Bibliothek). In Delphi existieren eine Reihe solcher Deklarationen in der Unit Windows:

// Vorwärtsdeklaration
function LineTo (DC: HDC; X, Y: Integer): BOOL; stdcall;

// externe Deklaration (anstatt des tatsächlichen Codes)
function LineTo; external 'gdi32.dll' name 'LineTo';

Diese Deklaration bedeutet, dass der Code der Funktion LineTo in der dynamischen Bibliothek GDI32.DLL (einer der wichtigsten Windows-Systembibliotheken) unter dem selben Namen, den wir in unserem Code verwenden, gespeichert ist. Innerhalb einer externen Deklaration können wir jedoch auch angeben, dass unsere Funktion auf eine Funktion der DLL verweist, die ursprünglich einen anderen Namen hatte.

Sie werden nur selten Deklarationen wie die gerade dargestellte schreiben müssen, da sie bereits in der Unit Windows und vielen anderen Delphi System-Units enthalten sind. Den einzigen Grund wo es erforderlich wird, derartigen externen Deklarationscode zu schreiben, ist der Aufruf von Funktionen aus einer zusätzlich erworbenen DLL oder um undokumentierte Windows-Funktionen aufzurufen.

Beachte: In der 16-Bit Version von Delphi benutzte die externe Deklaration den Namen der Bibliothek ohne die Erweiterung und wurde gefolgt durch die Name-Direktive (wie in obigem Code) oder durch eine alternative Index-Direktive, gefolgt von der ordinalen Nummer der Funktion innerhalb der DLL. Diese Änderung spiegelt eine Änderung des Systems in der Art des Zugriffs auf Bibliotheken wieder: obwohl Win32 immer noch den Zugriff auf DLL-Funktionen über Nummern zulässt, hat Microsoft erklärt, dass dies in der Zukunft nicht mehr unterstützt wird. Beachten Sie bitte auch, dass die Unit Windows die Units WinProcs und WinTypes aus der alten 16-Bit Version von Delphi ersetzt.

Eine Windows Callback-Funktion

In Kapitel 6 haben wird gesehen, dass Objekt Pascal prozedurale Typen unterstützt. Eine häufige Anwendung dieser prozeduralen Typen bildet die Bereitstellung von Callback-Funktionen an eine Windows API-Funktion dar.

Zunächst jedoch, was ist eine Callback-Funktion? Die Idee dabei ist, dass einige API-Funktionen eine bestimmte Aktion über eine Anzahl von internen Elementen des Systems durchführen, wie z.B. alle Fenster einer bestimmten Art. Solch eine Funktion, auch als Aufzählungsfunktion bezeichnet, erfordert als einen Parameter die Aktion, die mit jedem Element ausgeführt werden soll und dazu als Funktion oder Prozedur übergeben wird, die kompatibel mit einem bestimmten prozeduralen Typ ist. Windows benutzt gewöhnlich Callback-Funktionen unter anderen Umstanden, aber wir werden unsere Untersuchung auf diesen einfachen Fall beschränken.

Betrachten Sie nun die API-Funktion EnumWindows, welche den folgenden Prototyp besitzt (kopiert aus der Win32 Hilfedatei):

BOOL EnumWindows(
  WNDENUMPROC lpEnumFunc,  // Adresse der Callback-Funktion
  LPARAM lParam // anwendungsspezifischer Wert
  );

Selbstverständlich ist dies eine C-Deklaration. Wir können in die Unit Windows sehen, um die entsprechende Deklaration in Pascal zu erhalten:

function EnumWindows (
  lpEnumFunc: TFNWndEnumProc;
  lParam: LPARAM): BOOL; stdcall;

Konsultieren wir wieder die Hilfedatei, so finden wir , dass die Funktion, die als Parameter übergeben werden muss, folgenden Typ besitzt (wiederum in C):

BOOL CALLBACK EnumWindowsProc (
  HWND hwnd, // Handle des Elternfensters
  LPARAM lParam // anwendungsspezifischer Wert
  );

Das entspricht der folgenden Delphi-Definition eines prozeduralen Typs:

type
  EnumWindowsProc = function (Hwnd: THandle;
    Param: Pointer): Boolean; stdcall;

Der erste Parameter ist das Handle von jedem ermittelten Hauptfensters, während der zweite Parameter der Wert ist, den wir beim Aufruf der Funktion EnumWindows übergeben haben. Allerdings ist in Pascal der Typ TFNWndEnumProc nicht korrekt definiert, er ist einfach ein Zeiger. Das bedeutet, dass wir eine Funktion mit den richtigen Parametern zur Verfügung stellen müssen, die wir dann als Zeiger benutzen indem wir die Adresse der Funktion angeben anstatt sie aufzurufen. Leider bedeutet das auch, dass der Compiler keine Hilfe bereitstellt, für den Fall eines Fehlers im Typ eines der Parameter.

Windows fordert vom Programmierer, jedes mal die Aufrufkonvention stdcall zu verwenden, wenn eine Windows API-Funktion aufgerufen werden soll oder eine Callback-Funktion an das System übergeben wird. Delphi verwendet standardmäßig eine andere, effektivere Aufrufkonvention, die durch das Schlüsselwort register angezeigt wird.

Hier folgt die Definition einer korrekten kompatiblen Funktion, welche den Titel eines Fensters in einen String einliest und anschließend an eine ListBox des aktuellen Formulars anfügt:

function GetTitle (Hwnd: THandle; Param: Pointer): Boolean; stdcall;
var
  Text: string;
begin
  SetLength (Text, 100);
  GetWindowText (Hwnd, PChar (Text), 100);
  FormCallBack.ListBox1.Items.Add (
    IntToStr (Hwnd) + ': ' + Text);
  Result := True;
end;

Das Formular besitzt eine ListBox, die fast die gesamte Fläche belegt, zusammen mit einem schmalen Panel am oberen Rand, die einen Schalter trägt. Wenn dieser Schalter betätigt wird, so wird die API-Funktion EnumWindows aufgerufen, und die Funktion GetTitle als Parameter übergeben:

procedure TFormCallback.BtnTitlesClick(Sender: TObject);
var
  EWProc: EnumWindowsProc;
begin
  ListBox1.Items.Clear;
  EWProc := GetTitle;
  EnumWindows (@EWProc, 0);
end;

Ich hätte die Funktion auch aufrufen können, ohne den Wert zunächst in einer temporären prozeduralen Typvariablen zu speichern, aber ich wollte deutlich machen was in diesem Beispiel vor sich geht. Der Effekt dieses Programms ist tatsächlich recht interessant, wie sie in Abbildung 9.2 sehen können. Das Callback-Beispiel zeigt eine Liste aller existierender Hauptfenster, die auf dem System ausgeführt werden. Die meisten von ihnen sind verborgene Fenster, die Sie gewöhnlich niemals sehen (und viele davon haben keine Beschriftung).

Abbildung 9.2: Die Ausgabe des Callback-Beispiels, listet die momentanen Hauptfenster (sichtbar und verborgen) auf.

Ein minimales Windows-Programm

Um den Exkurs zur Windows-Programmierung in der Sprache Pascal zu vervollständigen, möchte ich Ihnen ein sehr einfache, aber vollständige Applikation zeigen, die ohne Nutzung der VCL erstellt wurde. Das Programm übernimmt einfach den Kommandozeilenparameter (der durch das System in der globalen Variable cmdLine gespeichert wird) und ermittelt daraus Informationen mit den Pascal-Funktionen ParamCount und ParamStr. Die erste dieser Funktionen liefert die Anzahl der Parameter; die zweite ermittelt den Parameter an einer bestimmten Position.

Obwohl Anwender nur selten Kommandozeilenparameter in einer graphischen Bedienoberfläche spezifizieren, sind Kommandozeilenparameter wichtig für das System. Wenn Sie z.B. eine Zuordnung zwischen einer Dateierweiterung und einer Applikation definiert haben, so können Sie ein Programm einfach dadurch starten, dass Sie eine zugeordnete Datei auswählen. Praktisch startet Windows das zugeordnete Programm, wenn Sie auf eine Datei doppelklicken, und übergibt die selektierte Datei als Kommandozeilenparameter.

Hier ist der komplette Quellcode des Projekts (eine DPR-Datei, keine PAS-Datei):

program Strparam;

uses
  Windows;

begin
  // den ganzen String anzeigen
  MessageBox (0, cmdLine, 
    'StrParam Kommandozeile', MB_OK);

  // den ersten Parameter anzeigen
  if ParamCount > 0 then
    MessageBox (0, PChar (ParamStr (1)), 
      'Erster StrParam Parameter', MB_OK)
  else
    MessageBox (0, PChar ('Keine Parameter'), 
      'Erster StrParam Parameter', MB_OK);
end.

Der Code zur Ausgabe verwendet die API-Funktion MessageBox, einfach um zu vermeiden, dass die gesamte VCL in das Projekt aufgenommen wird. Ein reines Windows-Programm wie das oben gezeigte hat den Vorteil eines sehr geringen Speicherverbrauchs: Die ausführbare Datei des Programms ist nur ca. 16kByte groß.

Um einen Kommandozeilenparameter an das Programm zu übergeben, können Sie Delphi's Menükommando Run > Parameter verwenden. Eine andere Methode ist es, den Windows-Explorer zu öffen und die Datei, die sie starten möchten über die ausführbare Datei zu ziehen. Der Windows-Explorer wird das Programm unter Benutzung des Namens der abgelegten Datei als Kommandozeilenparameter starten. Abbildung 9.3 zeigt beides, den Explorer und die dazugehörige Ausgabe.

Abbildung 9.3. Sie können einen Kommandozeilenparameter an das Beispiel StrParam übergeben, indem Sie im Windows-Explorer eine Datei auf der ausführbaren Datei ablegen.

Zusammenfassung

In diesem Kapitel haben wir eine Einführung in die Grundlagen der Windows-Programmierung gesehen, wir haben Handles diskutiert und ein sehr einfaches Windows-Programm kennen gelernt. Für normale Windows-Programmieraufgaben werden Sie im allgemeinen die visuelle Entwicklungsunterstützung verwenden, die Delphi gestützt auf die VCL bereitstellt. Aber das geht bereits über das Thema dieses Buches hinaus, welches die Sprache Pascal ist.

Das nächste Kapitel beinhaltet Varianten, eine sehr ungewöhnliche Zugabe zum System der Datentypen von Pascal, dazu eingeführt, um eine volle OLE-Unterstützung bereitzustellen.

Nächstes Kapitel: Varianten

© Copyright Marco Cantù, Wintech Italia Srl 1995-2000
© Copyright der deutschen Übersetzung Immo Wache, 2000