www.codeworx.org/directx_tuts/Einführung in DirectDraw, Teil 2: Erstellen eines einfachen Windows-Programms

Einführung in DirectDraw Teil 2: Erstellen eines einfachen Windows-Programms
Datum: 27.1.00
Autor: Tim-Oliver Husser

Themen für Teil 2 dieses Tutorials:
Der Programmkopf
Die WinMain
Die Fensterprozedur

Der Programmkopf
Nachdem Sie in Teil 1 dieses Tutorials gelernt haben, wie man die IDE konfiguriert, fangen wir hier in Teil 2 mit der Programmierung an.
Jetzt geht es um den Teil, der eigentlich nur für die Verwaltung zuständig ist. Hier erfahren Sie, wie man ein neues Fenster erstellt und anzeigt, und wie man die Windows-Nachrichten abfängt und verarbeitet.
Fangen wir also an. Zunächst brauchen wir nur eine einzige Include-Datei. Somit wäre die erste Zeile in der main.cpp:

#include <windows.h>

Jetzt folgen zwei Definitionen. Die eine ist für den Fenstertitel, die andere für die Fensterklasse. Den Namen für die Fensterklasse brauchen wir im ganzen Programm zwar nur zweimal; die Arbeit mit #define macht die Sache aber etwas übersichtlicher. Die Definitionen sind:

#define WindowTitle "DirectDraw-Tutorial"
#define WindowClassName "DDWinClass"

Jetzt folgt die Definition der Fensterprozedur. Dessen Bedeutung erläutere ich weiter unten.

LONG FAR WINAPI WndProc (HWND , UINT , UINT , LONG) ;


Die WinMain
Als nächstes kommt das Herzstück eines jeden Windows-Programmes: die WinMain-Prozedur. Sie ist das, was unter DOS die main-Prozedur war. Hier wird die Fensterklasse mit Daten gefüllt, bei Windows registriert und schließlich wird damit das neue Fenster aufgerufen. Der Funktionskopf zusammen mit den Variablendeklarationen:

int WINAPI WinMain (HINSTANCE hInstance, 
					HINSTANCE hPrevInstance, 
					LPSTR lpCmdLine, 
					int nCmdShow)
{
   MSG msg ;
   HWND hwnd ;
   WNDCLASS wndclass ;


Jetzt prüfen wir, ob schon eine Instanz des Programms vorhanden ist, also ob es schon läuft. Falls nicht, füllen wir die Fensterklasse mit Daten und registrieren sie.

   if (!hPrevInstance)
   {
      wndclass.style = CS_HREDRAW | CS_VREDRAW ;
      wndclass.lpfnWndProc = WndProc ;
      wndclass.cbClsExtra = 0 ;
      wndclass.cbWndExtra = 0 ;
      wndclass.hInstance = hInstance ;
      wndclass.hIcon = LoadIcon (NULL , IDI_APPLICATION) ;
      wndclass.hCursor = LoadCursor (NULL , IDC_ARROW) ;
      wndclass.hbrBackground = (HBRUSH)GetStockObject (LTGRAY_BRUSH) ;
      wndclass.lpszMenuName = NULL ;
      wndclass.lpszClassName = WindowClassName ;
      RegisterClass (&wndclass) ;
   }

Jetzt erstellen wir das Fenster und zeigen es an:

   hwnd = CreateWindow (WindowClassName ,
   WindowTitle ,
   WS_OVERLAPPEDWINDOW ,
   CW_USEDEFAULT ,
   CW_USEDEFAULT ,
   CW_USEDEFAULT ,
   CW_USEDEFAULT ,
   NULL , NULL ,
   hInstance , NULL) ;
   ShowWindow (hwnd , nCmdShow) ;
   UpdateWindow (hwnd) ;

Was jetzt kommt, ist der wichtigste Teil eines Windows-Programmes: der "Message-Loop".
Hier werden die von Windows kommenden Fensternachrichten abgefangen und verarbeitet.

   while(1)
   {
      if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
      {
         if (msg.message == WM_QUIT) break;
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
   }

Wenn Sie noch nie ein Windows-Programm geschrieben haben, werden Sie jetzt wohl denken: "Ja, spinnt der denn? Ist ja 'ne schöne Endlosschleife!"
So ganz endlos ist diese Schleife aber nicht, trotz der eigenartigen Abbruchbedingung der While-Schleife, die gar keine ist. Wenn Sie genau hinsehen, erkennen Sie die Ausgang der Schleife: Ist msg.message - was auch immer das ist - gleich WM_QUIT - was auch immer das ist - wird die Schleife verlassen.
Dann will ich Sie mal nicht so im Regen stehen lassen. Mit der Anweisung PeekMessage wird die nächste Nachricht aus der Nachrichtenschleife abgerufen. Alle Nachrichten, die Windows an ein Programm sendet, werden hier gespeichert und können dann der Reihe nach abgearbeitet werden. Der erste Parameter an PeekMessage ist ein Zeiger auf die Variable msg vom Typ MSG. Hier wird die zu verarbeitende Nachricht gespeichert.
Jetzt wird Ihnen wahrscheinlich gerade ein Licht aufgehen. Ja, WM_QUIT ist eine bestimmte Fensternachricht (WM = window message) , und wie der Name schon sagt, ist sie für den Programmabbruch bestimmt.
TranslateMessage übersetzt die Nachricht und DispatchMessage leitet sie an die Fensterprozedur, die ich gleich besprechen werde, weiter.
So, noch zwei Zeilen, dann ist die WinMain komplett:

   return msg.wParam ;
} // WinMain


Die Fensterprozedur
Jetzt kommt, wie angekündigt, die Fensterprozedur. Hier ist sie erst komplett, besprechen werde ich sie hinterher:

LONG FAR WINAPI WndProc (HWND hwnd , UINT message ,
                         UINT wParam , LONG lParam)
{
   switch (message)
   {
      case WM_DESTROY:
      {
         PostQuitMessage (0) ;
         return 0 ;
      } break;
   } // switch (message)
   return DefWindowProc (hwnd , message , wParam , lParam) ;
} // WndProc

Fällt Ihnen was auf? Hier kommen wieder Konstanten, die mit WM_ anfangen, vor und die Funktion übernimmt einen Parameter mit der Bezeichnung message. Es geht also wieder um Fensternachrichten.
Die Prozedur ist eigentlich ganz einfach. Ist message gleich WM_DESTROY, wird das Programm durch den Aufruf von PostQuitMessage beendet.
Etwas trickreich ist die Art, wie Windows Nachrichten verarbeitet. Wird das Programm z.B. durch einen Klick auf das Kreuz oben rechts beendet, schickt Windows zuerst eine Nachricht an das entsprechende Programm, daß es sich zerstören soll (WM_DESTROY). Ist das Programm damit einverstanden, schickt es Windows eine Nachricht (PostQuitMessage), daß es beendet werden will. Windows schickt dann eine neue Nachrichgt zum Beenden (WM_QUIT).
Verwirrt? Ist nicht schlimm, mit den Windows-Nachrichten haben wir von jetzt an nicht mehr viel am Hut.
Damit ist Ihr erstes Windows-Programm fertig. Sollten Sie Probleme mit den Code oder mit etwas anderem haben, schreiben Sie mir einfach eine eMail.

Das Projekt können Sie hier herunterladen.

In Teil 3 des Tutorials lernen Sie, wie man ein einfaches DirectDraw-Programm erstellt.