Windows Programmierung
Kapitel 1: Windows API

1.1 Hintergrund der Windows-Programmierung

1.1.1 Prozesse, Threads und Fenster

Wie der Name schon sagt, dreht sich in Windows alles um Fenster. Oder? Nicht ganz. Der Kern einer Windows-Anwendung ist ein Prozess. Der Prozess ist das, was später auch im Task-Manager aufgeführt wird. Jedem Prozess können mehrere Threads zugeordnet werden, die parallel verschiedene Aufgaben übernehmen. Zum Beispiel kann Thread A ein Dokument ausdrucken während Thread B auf Benutzereingaben reagiert. Jeder Thread wiederum kann nun Besitzer von ein oder mehreren Fenstern sein, muss es aber nicht.

1.1.2 Kommunikation mit dem Benutzer

In Windows findet die Kommunikation zwischen dem System und dem Anwender über eine sogenannte Nachrichtenwarteschlange statt. Wird von Benutzer eine Taste gedrückt oder die Maus bewegt, wird eine Nachricht von System in die Warteschlange gelegt. Für Windows-Nachrichten steht eine Struktur zur Verfügung, die wie folgt definiert ist:

typedef struct tagMSG {
   HWND hwnd; // Handler des Nachrichtenempfängers
   UINT message; // Die Nachricht als Nummer
   WPARAM wParam; // 1. Parameter der Nachricht
   LPARAM lParam; // 2. Parameter der Nachricht
   DWORD time; // Zeitpunkt der Nachrichtenerzeugung
   POINT pt; // Cursorposition zum Zeitpunkt der Nachrichtenerzeugung
}

In der Variablen hwnd ist der Handler des Fensters gespeichert, an das die Nachricht gesendet wird. Ein Fenster kann jedoch seine Nachrichten nicht selbst verwalten. Diese Aufgabe kommt dem Thread zu, der das Fenster öffnet. Dazu kann der Thread über die Funktion GetMessage auf alle Nachrichten zugreifen, die an seine Fenster geschickt wurden. Nachrichten werden für gewöhnlich nicht als numerische Werte gesendet sondern über symbolische Konstanten, die alle in windows.h definiert sind. Beispiele für Nachrichten sind WM_KILLFOCUS (Fenster verliert Focus), WM_PAINT (Fenster neu zeichnen) und WM_LBUTTONUP (Linke Maustaste losgelassen).

1.1.3 Ein einfaches Fensterprogramm

Wir wollen nun einmal ein Fenster erzeugen, dass nur aus einer Schaltfläche besteht. Windows hält bereits ein solches Fenster bereit, wir müssen uns also nur um die Erzeugung kümmern und sehen, wie eine einfache Nachrichtenschleife funktioniert.

#include <windows.h>

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE d2, LPSTR d3, int d4 )
{
    MSG msg;   // Handler für die Nachrichten
    HWND hwnd; // Handler für die Fensteradresse

    hwnd = CreateWindow( "BUTTON", "Hallo Welt!", WS_VISIBLE | BS_CENTER, 100, 100, 50, NULL, NULL, hInstance, NULL );

    while ( GetMessage( &msg, NULL, 0, 0 ) )
    {
      if( msg.message == WM_LBUTTONUP )
      { DestroyWindow( hwnd ); // Fenster schließen
        PostQuitMessage(0); // Und Anwendung schließen
      }
      DispatchMessage(&msg);
    }
    return msg.wParam;
}

Funktionsweise: Zunächst definieren wir die Funktion WinMain, die die Einsprungadresse für Windows-Anwendungen darstellt. Mit CreateWindow erzeugen wir ein Fenster, das wir in diesem Fall so aussehen lassen wollen, wie es in der vordefinierten Klasse BUTTON beschrieben ist. Die Nachrichtenschleife besteht aus einer while-Schleife, die solange läuft, bis die Funktion GetMessage den Wert FALSE zurückgibt. Dies ist der Fall, wenn die Nachricht WM_QUIT in der Warteschlange liegt. Haben wir die Nachricht WM_LBUTTONUP erhalten ( die linke Maustaste wurde losgelassen), so "zerstören" wir zunächst unser Fenster mit der Funktion DestroyWindow und senden dann mit PostQuitMessage die Nachricht WM_QUIT.
Auf jeden Fall müssen wir über DispatchMessage die Nachricht wieder ans System zurücksenden, damit die Windows-Standartnachrichtenhandler sich um den Refresh des Fensters kümmern können. Was das bedeutet, sieht man, wenn man die Anweisung aus dem Code weglässt.


1.2 Fensterklasse mit eigenem Nachrichtenhandler

Als nächstes wollen wir ein "echtes" (Windows-Typisches) Fenster erstellen. Dazu müssen wir zunächst eine eigene Fensterklasse ("WNDCLASS") erstellen. Die Struktur wird bereits vom System vorgegeben, wir brauchen nur noch die einzelnen Member mit Werten füllen:

typedef struct _WNDCLASS {
  UINT style;  // Aussehen des Fensters mit CS_xxxxx
  WNDPROC lpfnWndProc; // Die Funktion zur Nachrichtenbehandlung, z.B. WndProc
  int cbClsExtra; // Zusätzlich zu reservierender Speicher (meist 0)
  int cbWndExtra; // Für das einbinden von Dialogen über WNDCLASS
  HANDLE hInstance;  // Das Instanzenhandle der Applikation
  HICON hIcon; // Eine Icon-Resource für die Klasse
  HCURSOR hCursor; // Eine Cursor-Resource für die Klasse
  HBRUSH hbrBackground; // Pinsel für die Hintergrundfarbe
  LPCTSTR lpszMenuName;  // Eine Resource für das Menü
  LPCTSTR lpszClassName; // Der Name, mit dem die Klasse registriert wird
} WNDCLASS;

Uns interessiert vor allem der Member lpfnWndProc. Hierbei handelt sich um einen "Pointer to a Funktion", also einen Zeiger auf eine Funktion. Diese Funktion muss unser Programm selbst definieren. Sie ist für das abarbeiten der Nachrichten zuständig, die unsere Applikation empfängt. Sehen wir uns ein einfaches Beispiel an, um zu verstehen, was es mit Fenstern auf sich hat. Die Funktion DrawText soll im wesentlichen unkommentiert bleiben. Auf das ausgeben von Grafik und Text wird in einem späteren Kapitel genauer eingegangen. Wir müssen nur wissen, das die Funktion sich zunächst beim System über den Zeichenbereich des Fensters informiert um dort dann zentriert einen Text auszugeben.

[ Beispiel ]

Unser Hauptprogramm registriert zunächst eine Fensterklasse und erstellt daraus ein Fenster. Dieses wird dann auch gleich angezeigt und die Daten aktuallisiert. Was folgt, ist die gewohnte Nachrichtenbehandlung, nur das wir die Nachrichten nichtmehr direkt sondern über WndProc abfangen. Wann immer das Fenster neu gezeichnet werden muss, schickt uns das System ein WM_PAINT, woraufhin wird unseren Text wieder abschicken. Nicht behandelte nachrichten schicken wir an den Standart-Handler DefWindowProc weiter.

Die C-Funktion memset hilft uns ein wenig weiter, um nicht alle Member der Klasse WNDCLASS setzen zu müssen. Wir definieren einfach, dass wir zunächst den gesamten Speicher, in dem das Objekt liegt, nullen wollen. Damit haben wir alle Member auf Standart NULL gesetzt und sparen uns eine Menge arbeit.



© Gerhard Zapf
Kapitelübersicht
Sitemap
www.tutorialpage.de
API-Referenz
Diese Seite wurde seit dem 12.07.01 genau 1807 mal abgerufen.
Letzte Änderung: 11.09.2003