Um zu verstehen, warum man in C einen Standard eingeführt hat bedarf es ein wenig mehr der Geschichte zu dieser Sprache.C C++ C/C++ Programmieren C Kurs Programmierung Systemprogrammierung Win32 api Win-Api Windows Systemprogrammierung C Programmierung Win32 api Win-Api Windows Systemprogrammierung Kapitel 1: Einstieg in die Win32-API

Ein Tutorial zur Win32-Programmierung? Sicherlich werden Sie sich fragen, ob es überhaupt möglich ist ein Tutorial dazu zu schreiben. Dann mit seinen über 10.000 Funktionen ist die WIN32-API (Application Interface == Programierschnittstelle) nicht unbedingt schwach am Umfang. Es ist also nicht einfach mal so möglich ein Tutorial bzw. ein Buch darüber zu schreiben. Der gewaltige Umfang ist wohl auch einer der Gründe, warum es im deutschsprachigen Raum immer noch recht wenig Bücher und Tutorials dazu gibt. Folglich können Sie von diesem Tutorial eben nur eine Einführung in die Win32-Programmierung erwarten. Aber vielleicht suchen Sie auch nur mal nach einer Einführung in die Win32-Programmierung. Vom Umfang her gesehen, entspricht dieses Tutorial einem Buch mit etwa 250 Seiten.

Hilfe
 

Da man mit der Win32-API mit C und C++ Anwendungen entwickeln kann, passiert es häufiger das bspw. eine Warnmeldung bezüglich eines fehlenden Typecasting kommen kann. Speziell bei C++, da hierbei beim Datentyp void ein Casting nötig ist. Über Hinweise zu Warnmeldungen der diversen Compiler würde ich mich sehr freuen.



Sollte dieses Tutorial regen Zuspruch finden, wird dieser Umfang natürlich erweitert.

1.1. Vorraussetzungen            zurück  zum Inhaltsverzeichnis

Um es mit dieser API aufzunehmen, sollten Sie bereits Erfahrung in der C-Programmierung gesammelt haben. Als Buch könnte ich Ihnen "C von A bis Z" aus meiner Feder empfehlen. Außerdem währe es recht hilfreich, wenn Sie Wissen, wie man einen Compiler verwendet. Diese Programmbeispiele wurden alle mit der kostenlosen Entwicklungsumgebung des Bloodshed-Dev-C++ (Version 4) und dem Microsoft-Compiler Visual C++ 6.0 getestet. Das Sie als Betriebssystem MS-Windows zur Programmierung der Win32-API verwenden sollten, muss ich glaube ich nicht mehr erwähnen.

Hinweis
 

Da es bis Dato mit der neuesten Version der Bloodshed-Dev-C++ Umgebung immer noch Probleme gibt, empfehle ich Ihnen, sofern sie Probleme mit diesem haben sollten, eine ältere Version einzusetzen.

1.2 Was ist die Win32-API            zurück  zum Inhaltsverzeichnis

Die Win32-API ist vereinfacht ausgedrückt, eine Schnittstelle zur Erstellung von Windows-Fensterprogrammen. Es ist zwar heutzutage nicht mehr die Nonplusultra-Schnittstelle aber dennoch ist das Erlernen dieser API immer noch die Grundlage für die weitere Windowsprogrammierung. Mit erweiterter Windowsprogrammierung währe bspw. die MFC-Bibliothek zu nennen. MFC (Foundation Class Library) ist vereinfacht ausgedrückt die objektorientierte WIN32-API in C++. Wobei mit der MFC-Bibliothek schon, im Gegensatz zur Win32-API, ein gewisser Overhead vermieden wird. Borland stellt mit OWL (Object Windows Library) ein konkurenzfähige Alternative zum Microsoft-Compiler und der MFC-Bibliothek dar. Ob aber jetzt MFC, OWL und sonstige grafische Bibliotheken, alle haben eines gemeinsam, sie bauen alle auf der Win32-API auf.

Und auch wenn Sie sich für die Spieleprogrammierung wie bspw. mit OpenGL oder DirectX interessieren, (gerade) dann sollten Sie sich ebenfalls ein wenig mit der Win32-Programmierung auseinander setzen.

Sie sehen also, es gibt genügend Gründe sich die Win32-API mal etwas näher zu betrachten.

1.3 Win32-API und der (C) Syntax            zurück  zum Inhaltsverzeichnis

Es wird wohl eine Weile dauern, bis Sie sich an der Windowstypischen C-schreibweise gewöhnt haben. Auf den ersten Blick erscheint einem alles ein wenig anders als man dies C-typisch kennt. Bspw. wird anstatt unsigned char der Name BYTE oder anstatt unsigned long der Name DWORD für den Datentypen verwendet. Aber ein Blick in die Win32-Bibliothek zeigt, das sich dies durch eine einfache Typendefinition ergibt:

typedef unsigned char       BYTE;
typedef unsigned long       DWORD;

Ebenso wird die mit Strukturen und anderen Datentypen gemacht.

Außerdem wird bei den Variablennamen von vielen Programmierern eine besondere Schreibweise, die so genannte ungarische Notation, verwendet. Hierbei fängt jeder Variablenname mit einer Vorsilbe (Präfix) an, die etwas über den Variablentyp aussagt, gefolgt vom eigentlichen Namen, der mit einem Großbuchstaben beginnt. Ein Beispiel ist der Name szPauseName. Der Präfix sz steht hier für "string-zero terminated" = "Zeichenkette, mit dem Stringendezeichen".

1.4 Ein Fenster erstellen            zurück  zum Inhaltsverzeichnis

Das grundlegende Element bei der WIN32-Programmierung und bei der GUI-Programmierung überhaupt, dürfte die Erstellung eines Fensters sein. Im Prinzip kann alles in einer WIN32-Anwendung als Fenster bezeichnet werden. Ob dies nun einzelne Schaltflächen, Dialogfelder oder Bearbeitungselemente sind, alles stellt wieder nichts anderes als eine Art Fenster im Fenster da. Allerdings hat jede Anwendung nur ein Hauptfenster oder auch Stammfenster genannt.

Man würde annehmen, dass Erstellen eines einfachen Fensters sollte das Einfachste in WIN32-Programmierung sein. Leider ist das Gegenteil der Fall. Der Grund ist rechte einfach. Für andere Elemente wie Dialogfelder, Bildlaufleisten usw. gibt vordefinierte Vorlagen. Für die Erstellung eines Hauptfensters hingegen nicht. Also liegt damit ein wenig mehr (Tipp) Arbeit vor Ihnen.

Folgende grundlegende Elemente besitzt ein einfaches Fenster:

Elemente eines einfachen Fensters

Elemente eines einfachen Fensters

Im nächsten Schritt, will ich Ihnen den Quellcode zeigen, womit Sie ein einfaches Fenster erstellen können. Im Anschluss des Quellcodes, wird das Listing analysiert und erklärt.

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

LPCSTR lpszAppName = "AppName";
LPCSTR lpszTitle   = "Meine erste Applikation";

int APIENTRY WinMain(HINSTANCE hInstance,
           HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{

   HWND       hWnd;
   MSG        msg;
   WNDCLASSEX   wc;

   wc.cbSize        =  sizeof(WNDCLASSEX);
   wc.style         =  CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc   =  WndProc;
   wc.cbClsExtra    =  0;
   wc.cbWndExtra    =  0;
   wc.hInstance     =  hInstance;
   wc.hCursor       =  LoadCursor(NULL,IDC_ARROW);
   wc.hIcon         =  LoadIcon(NULL, IDI_APPLICATION);
   wc.hbrBackground =  (HBRUSH)GetStockObject(WHITE_BRUSH);
   wc.lpszClassName =  lpszAppName;
   wc.lpszMenuName  =  lpszAppName;
   wc.hIconSm       =  LoadIcon(NULL, IDI_APPLICATION);

   if( RegisterClassEx(&wc) == 0)
      return 0;

   hWnd = CreateWindowEx(NULL,
                         lpszAppName,
                         lpszTitle,
                         WS_OVERLAPPEDWINDOW,
                         0,
                         0,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         NULL,
                         NULL,
                         hInstance,
                         NULL);

   if( hWnd == NULL)
      return 0;

   ShowWindow(hWnd, iCmdShow);
   UpdateWindow(hWnd);

   while (GetMessage(&msg, NULL, 0, 0) > 0)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT umsg, WPARAM wParam, LPARAM lParam)
{
   switch (umsg)
   {
   case WM_DESTROY:
      {
         PostQuitMessage(0);
         return 0;
      }
   }
   return DefWindowProc(hWnd, umsg, wParam, lParam);
}

Nach dem Compilieren und Linken des Listings sollte sich bei Ihnen in etwa folgendes Fenster öffnen:

Ein leeres Fenster

Ein leeres Fenster

1.4.2 Den Quellcode analysieren
Wenn Sie gerade die Programmiersprache C gelernt haben, werden Sie sich beim Anblick dieses Quellcodes fragen, was daran noch C sein soll? In der Tat ließt sich der Code auf dem ersten Blick recht schwer und erinnert wohl eher an die hebräische Schrift anstatt an C. Einerseits dürften Sie verwirrt sein über Datentypen wie LPCSTR oder UINT und andererseits über die Bezeichner, die hier verwendet wurden wie bspw. lpszAppName. Aber darauf habe ich Sie ja zu Beginn hingewiesen.

Windows-Datentypen
Damit Sie in diesem Beispiel und auch im laufe des Tutorials, wissen, um was für Datentypen es sich handelt erfolgt hierzu eine Tabelle und deren Bedeutungen. Sie werden dabei bemerken, dass diese Datentypen doch nicht so fremd erscheinen. Ihnen sind die meisten nur unter einem anderen Namen bekannt. Alle zu erwähnen, währe hier wohl ein wenig zu viel des Guten. Dafür sei die Dokumentation des Visual C++ empfohlen (siehe Win32 Simple Data Types). Hier die geläufigsten Datentypen:
Datentyp Bedeutung< /td>
BOOL boolscher Wert (32 Bit)
BYTE vorzeichenloser 8-Bit-Wert
COLORREF Farbinformationen (32 Bit)
DWORD wie unsigned int in C (32 Bit)
LONG wie int in C (32 Bit)
LPARAM Funktionsparameter (32 Bit)
LPCSTR Zeiger auf konstanten String
LPSTR Zeiger auf Sring
LRESULT Rückgabewert von Funktionen (32 Bit)
UINT unsigned int
WNDPROC Zeiger auf eine Windowsfunktion
WORD unsigned short
WPARAM Funktionsparameter (16 Bit)


Tabelle 1.1: Datentypen der Win32-API

Wie erwähnt ist dies nur ein Bruchteil der Datentypen in der WIN32-API. Einige die hier nicht erwähnt wurden, werden bei Bedarf in diesem Tutorial behandelt.

Jetzt soll das Listing analysiert werden.

#include <windows.h> 

Als erstes müssen Sie die <windows.h> Headerdatei einbinden, damit Sie alle Funktionsprototypen und Konstanten für das Programm zur Verfügung haben.

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 

Auf diesen Funktionsprototypen (WndProc) wird etwas später noch genauer eingegangen.

LPCSTR lpszAppName = "AppName";
LPCSTR lpszTitle   = "Meine erste Applikation";

Hier haben Sie zwei Zeiger auf konstante Strings. Sie hätten dies natürlich auch so (C-like) schreiben können:

const char *lpszAppName = "AppName";
const char *lpszTitle   = "Meine erste Applikation";

Diese beiden Strings benötigen Sie noch bei der Erstellung des Fensters.

int APIENTRY WinMain(HINSTANCE hInstance,
           HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)

Wie auch in C/C++-Programmen benötigen Sie bei WIN32-Programmen eine Hauptfunktion, hier WinMain(). Die Funktion WinMain() empfängt vier Parameter: hInstance, hPrevInstance, lpCmdLine und nCmdShow. Der erste Parameter (hInstance) ist eine Programminstanz (ein ID==Nummer), womit ein Programm identifiziert wird. Der zweite Parameter (hPrevInstance) ist ein Überbleibsel alter Zeiten und wird nicht mehr verwendet.

Instanz
 

Windows ist ein Multitasking-System, womit quasi zu jedem Zeitpunkt mehrere Programme Gleichzeit in den Speicher geladen werden können, die nacheinander den Prozessor benutzen und so scheinbar parallel ablaufen. Wenn Sie jetzt bspw. ein und dasselbe Programm mehrmals gestartet haben (bspw. zweimal Word geöffnet) wird nicht der gesamte Programmcode nochmals geladen, sondern es wird nur eine weitere Instanz für die privaten Daten angelegt. Damit ist sichergestellt, dass der Speicher möglichst wirtschaftlich verwendet wird. Wurde also Word zweimal geöffnet, so bedeutet dies, dass der Programmcode nur einmal geladen wurde aber jede Instanz dafür einen eigenen Datenbereich und eine eigene Nummer besitzt.

Der dritte Parameter ist dass, was in C int argc und char **argv sind. Ob das Fenster minimiert, maximiert usw. angezeigt werden soll, können Sie mit dem vierten Parameter angeben. Wie dem auch sei, die einzelnen Parameter der WinMain()-Funktion können Sie vorerst mal außen vor lassen.

HWND hWnd; 

hWnd ist der Handle des Fensters.

Handle
 

Bei einem Handle handelt es sich um eine vorzeichenlose Integerzahl, mit der Sie ein Objekt in Windows kennzeichnen. Ein solches Objekt kann ein Fenster, ein Zeichenfeld, ein Gerätekontex und noch viel mehr sein. Die meisten Handle werden unter Windows durch typdef mit einem entsprechenden Namen versehen. Bspw. steht HWND (Handle WiNDow) für ein Fenster, HDC (Handle Device Contex) für einen Gerätekontex, HBITMAP für eine Bitmap, HICON für Symbole, HCURSOR für den Mauszeiger und HINSTANCE für ein Handle auf eine Instanz. Es gibt natürlich noch eine Menge mehr solcher Handle mit entsprechenden Namen, welche in der Regel mit H… anfangen. Zwar handelt es sich bei all den Bezeichnungen weiterhin um eine vorzeichenlose Integerzahl, nur dienen diese Namen auch zur besseren Lesbarkeit des Programms.

MSG msg; 

Mit der Variable msg, holen Sie die Nachrichten (Ereignisse) ab und leiten weitere Schritte ein. Auf die Struktur MSG wird noch eingegangen.

WNDCLASSEX   wc; 

WNDCLASSEX ist eine Struktur, womit Sie den Bezug des Fensters angeben. Damit legen Sie erstmal fest, wie das Fenster auszusehen hat.

wc.cbSize        =  sizeof(WNDCLASSEX);
wc.style         =  CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc   =  WndProc;
wc.cbClsExtra    =  0;
wc.cbWndExtra    =  0;
wc.hInstance     =  hInstance;
wc.hCursor       =  LoadCursor(NULL,IDC_ARROW);
wc.hIcon         =  LoadIcon(NULL,IDI_APPLICATION);
wc.hbrBackground =  (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszClassName =  lpszAppName;
wc.lpszMenuName  =  lpszAppName;

Fensterklasse beschreiben
Hiermit geben Sie die Beschreibung des Hauptfensters an. Hierzu die einzelnen Bedeutungen der Struktur WNDCLASSEX. Zuerst die Zeile:

wc.cbSize        =  sizeof(WNDCLASSEX);  

Mit dieser Variablen geben Sie Größe der Struktur in Bytes an. Dies ist nötig, damit Windows feststellen kann, um welche Version der Struktur es sich handelt. Bspw. existiert für die Struktur WNDCLASSEX auch eine ältere Version mit dem Namen WNDCLASS. Die Erweiterung …EX steht für extendend (erweitert) und bedeutet, dass es für diese Funktion oder Struktur eine ältere Version mit dem gleichen Namen gibt.

Hinweis
 

In der Win32-API gibt es viele solche erweiterte Funktionen mit der Endung …EX.

wc.style = CS_HREDRAW | CS_VREDRAW;

Hiermit geben Sie den Stil der Fensterklasse an. In diesem Beispiel geben Sie mit CS_HREDRAW und CS_VREDRAW an, dass das Fenster Neue gezeichnet werden soll, sobald die horizontale (CS_HREDRAW) und/oder die vertikale (CS_VREDRAW) Größe des Fensters verändert wird. Wollen Sie bspw., dass man das Fenster nicht mehr schließen kann, können Sie den Stil CS_NOCLOSE hinzufügen:

wc.style = CS_HREDRAW | CS_VREDRAW | CS_NOCLOSE; 

Es gibt natürlich noch einige weitere Fensterklassenstile, worauf bei Bedarf eingegangen wird. Die Gängigsten sind auf jeden Fall CS_HREDRAW und CS_VREDRAW.

Die nächste Zeile:

wc.lpfnWndProc = WndProc;

Ein Zeiger auf eine Callback-Funktion zur Verarbeitung von Nachrichten, die für das Fenster bestimmt sind. Mehr zur Callbackfunktion folgt etwas später.

wc.cbClsExtra    =  0;
wc.cbWndExtra    =  0;

Damit können Sie Speicher für Informationen (cbClsExtra) des Fensters und Fensterklasse (cbWndExtra) reservieren. Diese Variablen werden selten benötigt und mit 0 initialisiert.

wc.hInstance = hInstance; 

Der Instanz-Handle, worin sich die Fensterprozedur für diese Klasse befindet. Die Instanz ist einer der Parameter, welcher der WinMain()-Funktion übergeben wird.

wc.hCursor  =  LoadCursor(NULL,IDC_ARROW);
wc.hIcon    =  LoadIcon(NULL,IDI_APPLICATION);

Mit den Handle hCursor und hIcon können Sie einen speziellen Cursor oder ein Icon angeben, welches für das Fenster verwendet werden soll. In beiden Fällen verwenden Sie erstmal die voreingestellten Icons. Wollen Sie das Icon oder den Cursor verändern, sollten Sie einen Blick auf die Funktionen LoadCurosor() und LoadIcon() werfen.

wc.hbrBackground =  (HBRUSH)GetStockObject(WHITE_BRUSH);   

Hierbei können Sie mit einem Pinsel den Fensterhintergrund zeichnen (GetStockObject()). Hier haben Sie eine weiße Farbe (WHITE_BRUSH) verwendet. Anstatt Pinsel können aber auch Systemfarben verwendet werden welchem mit dem Präfix COLOR_… beginnen. Dafür sei wieder die MSDN-Dokumentation empfohlen.

wc.lpszClassName =  lpszAppName;
wc.lpszMenuName  =  lpszAppName;

Mit lpszClassName geben Sie, sofern benötigt den Namen des Menü an. Wird kein Menü benötigt, wird hier NULL übergeben.

lpszMenuName ist eine Variable, welcher Sie einen Namen übergeben müssen. Mit diesem Namen wird die Klasse identifiziert. Bei beiden Variablen handelt es sich um einen Nullterminierten String. wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

Bei diesem Icon handelt es sich um das Icon links neben dem Fenstertitel. Auch hierbei wird ein Handle übergeben. Hinweis: Bei den Klassen in der WIN32-API handelt es sich nicht um C++-typischen Klassen. Man spricht hierbei von einer Klasse, wenn Sie bspw. einer Struktur für die Fensterklasse mit bestimmten Eigenschaften ausstatten.

Hierzu nochmals die komplette Struktur WNDCLASSEX im Überblick:

typedef struct _WNDCLASSEX {
    UINT    cbSize;
    UINT    style;
    WNDPROC lpfnWndProc;
    int     cbClsExtra;
    int     cbWndExtra;
    HANDLE  hInstance;
    HICON   hIcon;
    HCURSOR hCursor;
    HBRUSH  hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
    HICON   hIconSm;
} WNDCLASSEX;

Fenster registrieren
Als Nächstes müssen Sie diese Eigenschaften, welche Sie gerade an die Fensterklasse vergeben haben registrieren oder genauer erzeugen. Damit melden Sie quasi die Fensterklasse beim Fenstermanager von Windows an. Dies können Sie mit der Funktion RegisterClassEx() erledigen. Als Parameter erhält die Funktion lediglich die Anfangsadresse auf die WNDCLASSEX-Struktur. Bei Fehler gibt die Funktion RegisterClassEx() den NULL-Zeiger zurück.

   if( RegisterClassEx(&wc) == NULL)
      return 0;

Fenster erzeugen
Nachdem Sie die Eigenschaften des Fensters festgelegt und registriert haben, wird es Zeit das Fenster zu erstellen. Dies können Sie mit der Funktion CreateWindowEx() machen. Bei einem Fehler liefert diese Funktion den Rückgabewert NULL oder ansonsten den Handle zum Fenster, HWND.

   hWnd = CreateWindowEx(NULL,
                         lpszAppName,
                         lpszTitle,
                         WS_OVERLAPPEDWINDOW,
                         0,
                         0,
                         CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         NULL,
                         NULL,
                         hInstance,
                         NULL);

   if( hWnd == NULL)
      return 0;

Um Ihnen die einzelnen Parameter dieser Funktion zu erläutern, sollte hierzu die Syntax dieser Funktion hergenommen werden:

HWND CreateWindowEx(
  DWORD dwExStyle,
  LPCTSTR lpClassName,
  LPCTSTR lpWindowName,
  DWORD dwStyle,
  int x,
  int y,
  int nWidth,
  int nHeight,
  HWND hWndParent,
  HMENU hMenu,
  HINSTANCE hInstance,
  LPVOID lpParam
);

dwExStyle: Mit diese Parameter können einen erweiterten Fensterstil angeben, der für das Fenster verwendet werden soll. Wollen Sie diesen Parameter nicht verwenden, können Sie auch den NULL-Zeiger übergeben. Da es davon nicht wenige gibt, sei hierfür auf die MSDN-Dokumentation verwiesen. Suchen Sie nach den Konstanten, welchem mit dem Präfix WS_EX_… beginnen.
lpClassName: Hier müssen Sie denselben Klassennamen verwenden, welchen Sie bereits WNDCLASSEX-Struktur verwendet haben. Dabei handelt es sich um einen Nullterminierten String. Es können aber auch vordefinierte Fensterklassen verwendet werden (BUTTON, EDIT, COMBOBOX, MDICLIENT, RICHEDIT, RICHEDIT_CLASS, SCROLLBAR, STATIC).
lpWindowName: Hier befindet sich ein nullterminierter String, der in der Titelleiste des Fensters ausgegeben wird.
dwStyle: Hier können Sie angeben, welche Art von Fenster erzeugt werden soll. Hierfür gibt es auch wieder eine Menge Konstanten zum Angeben. Die Angabe im Beispiel WS_OVERLAPPEDWINDOW ist eine Kombination aus mehreren Attributen und bedeutet, dass unser Fenster über ein Systemmenü und je eine Schaltfläche zum Minimiere, maximieren und zum Schließen besitzt. Wollen Sie auf jeden Fall, dass Ihr Fenster am Anfang sichtbar ist, können Sie mit dem bitweisen ODER-Operator die Konstante WS_VISBLE verknüpfen. Ebenfalls noch häufiger werden Sie WS_CHILD verwenden. Damit erzeugen Sie ein abgeleitetes Fenster oder Steuerelement. Benötigen Sie ein Fenster mit einer horizontalen Bildlaufleiste, können Sie WS_HSCROLL verwenden. Wollen Sie das Fenster maximiert oder minimiert starten, verwenden Sie WS_MINIMIZE oder WS_MAXIMIZE. Auch dabei werden Sie in dem einen oder anderen Kapitel noch andere Stile kennen lernen. Einen Überblick können Sie sich auch hierzu wieder in der MSDN-Dokumentation verschaffen. Die Konstanten beginnen alle mit dem Präfix WS_…
x, y: Die x und y Position des Fensters wird durch die linke obere Ecke in Pixel angegeben. In unserem Beispiel wird das neuen Fenster links oben angezeigt. Ist die Position nicht wichtig, können Sie auch CW_USERDEFAULT verwenden.
nWidth, nHeight: Hier geben Sie die Breite und Höhe des Fensters in Pixel an. Sind auch diese Angaben nicht relevant, können Sie dafür CW_USERDEFAULT angeben.
hWndParent: Soll das neu zu erzeugende Fenster ein Unterfenster eines bereits vorhandenen Fensters sein, müssen Sie hier das Handle für das übergeordnete Fenster angeben. In diesem Fall handelt es sich um das Hauptfenster und somit wird NULL angegeben.
hMenu: Ein Handle für das Menü. Wird das Klassenmenü verwendet, können Sie den NULL-Zeiger angeben.
hInstance: Hier muss der Instanz-Handle der Anwendung übergeben werden, welcher der WinMain()-Funktion übergeben wurde.
lpParam: Dies ist ein Zeiger auf Daten, die als LPARAM- oder WM_CREATE-Nachricht übergeben werden. Dieser Parameter wird vorwiegend bei MDI-Anwendungen (multi document interface) verwendet. Meistens wird dieser Parameter mit NULL besetzt.

Bei dieser Menge von Parametern, die Sie alleine für die Erstellung eines Fensters angeben müssen und der Vielzahl von Möglichkeiten, die Sie für jeden einzelnen Parameter verwenden können, ist es unerlässlich mit der MSDN-Dokumentation zu arbeiten. Als Anfänger sollten Sie sich allerdings erst mal mit den nächsten Seiten dieses Tutorials befassen. Denn alleine das Ausprobieren der verschiedenen Möglichkeiten, die Sie bspw. für die Funktion CreateWindowEx() verwenden können, kann Stunden an Zeit beanspruchen.

Fenster anzeigen und zeichnen
Jetzt, nachdem das Fenster fertig erstellt wurde, können Sie es anzeigen lassen. Diese machen Sie mit den folgenden zwei Zeilen:

ShowWindow(hWnd, iCmdShow);
UpdateWindow(hWnd);

Mit ShowWindow() setzen Sie den Anzeigestatus eines Fensters. Damit wird bestimmt, wie ein Fenster auf dem Bildschirm angezeigt wird. Als ersten Parameter übergeben Sie der Funktion den Fenster-Handle und als zweiten Parameter geben Sie an, wie das Fenster angezeigt wird. Diesen Parameter können Sie wieder auf mehreren Werten setzen. Bspw. wird mit SW_HIDE das Fenster versteckt oder mit SW_MAXIMIZE und SW_MINMIZE wird das Fenster maximiert bzw. minimiert. Mit der Konstante SW_RESTORE können Sie die ursprüngliche Größe des Fensters wieder herstellen. SW_SHOW zeigt das Fenster in aktueller Größe und Position an. Für weitere Werte des zweiten Parameters der Funktion ShowWindow() sehen Sie sich in der MSDN-Dokumentation um. Die Konstanten beginnen alle mit dem Präfix SW_… Mit der Funktion UpdateWindow() schickt Windows eine WM_PAINT Nachricht an das vorgegebene Fenster mit Dementsprechenden Handle. Diese Nachricht sorgt dafür, dass das Fenster gleich nach dem Start auf dem Bildschirm gezeichnet wird. Und das unabhängig von der Nachrichtenwarteschlange.

Nachrichten-Schleife
Jetzt kommen Sie zu einem sehr wichtigen Abschnitt der WIN32 Programmierung, den Nachrichten. Im Prinzip ist dieser Abschnitt nicht nur für die WIN32 Programmierung von Bedeutung, sondern für alle echte Multitasking-Betriebssysteme. Multitasking heißt ja, dass mehrere Programme gleichzeitig ausgeführt werden können. Damit Windows die vielen zur gleichen Zeit laufenden Programme überhaupt Herr werden kann, werden Nachrichten benötigt. Zu den Nachrichten einer WIN32 Applikation gehören einfache Dinge, wie Fenster vergrößern, verkleinern oder schließen. Oder ob der Anwender eine Eingabe von der Tastatur oder Maus gemacht hat. Eben alle Interaktionen des Benutzers. Natürlich nicht nur des Benutzers. Eine Nachricht kann auch gesendet und ausgewertet werden, wenn der Anwender eine E-Mail in seinem Postfach hat (entsprechendes Programm was das Postfach abruft vorausgesetzt).

Windows kann die Nachrichten aber nicht alle auf einmal abarbeiten. Jedes aktive Programm bekommt dafür einen extra Puffer bereitgestellt. Die Nachrichten werden so lange im Puffer gespeichert, bis Windows das Programm behandelt. Oder besser, bis das Programm an der Reihe ist.

Ein einfaches Beispiel: Der Anwender klickt auf "Schließen" und will das Programm beenden. Diese Nachricht wird jetzt in eine Warteschlange (Message Queue) abgelegt. Sie sind jetzt mit Ihrer WIN32-Applikation verantwortlich, diese Nachricht von der Warteschlange abzuholen. Diese erledigen Sie im Listing mit der folgenden Nachrichten-

Schleife:
   while (GetMessage(&msg, NULL, 0, 0) > 0)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }

Mit der Funktion GetMessage() holen Sie die Nachricht aus der Warteschlange (Puffer) ab. Der erste Parameter ist ein Zeiger auf eine Struktur vom Typ MSG, welche Sie zum Beginn des Programms deklariert haben. Der zweite Parameter erhält das Handle für das Fenster, welches überprüft werden soll. Mit der Angabe von NULL bewirken Sie, dass alle Nachrichten vom Programm empfangen werden. Mit den letzten beiden Parametern können Sie die Anzahl der Nachrichten angeben, welche Sie min./max. empfangen wollen. Durch die Angabe von 0 können Sie alle Nachrichten empfangen, ohne Einschränkungen. Die Nachrichten-Schleife wird so lange wiederholt, bis die Funktion GetMessage() die Nachricht WM_QUIT erhält. Damit wird die Anwendung beendet. Tritt bei der Funktion ein Fehler auf, wird -1 zurückgegeben. Mit der Funktion TranslateMessage() wird ein virtueller Tastaturcode, wenn eine Tastatur gedrückt wurde an die Nachrichtenwarteschlange der Anwendung weitergegeben. Dies wird hier im Beispiel noch nicht benötigt. Dazu später mehr.

Jetzt zur Funktion DispatchMessage(). Alle Windows-Nachrichten einer Anwendung durchlaufen diese Funktion. Diese Funktion sendet Nachrichten aus der Nachrichten-Schleife, welche zuvor mit GetMessage() ermittelt wurde, an eine Anwendung zur Verarbeitung an das Fenster. Einfach ausgedrückt, verteilt diese Funktion die Nachrichten an entsprechende Windows-Prozeduren (Funktionen). Und die Prozedur, die alle Nachrichten der Fensterklasse abarbeitet, haben Sie zu Beginn des Programms bereits deklariert:

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

Diese Funktion wird aufgerufen, wenn ein bestimmtes Ereignis aufgetreten ist und ist nicht an das Programm, sondern an das Fenster gebunden. Diese Prozedur haben Sie bereits bei der Fensterklasse registriert mit:

wc.lpfnWndProc = WndProc; 

Nochmals, da es wichtig ist und häufig vergessen wird, diese Prozedur ist an das Fenster gebunden und arbeitet alle Ereignisse ab, die in diesem Fenster passieren. Befindet sich bspw. in Ihrem Programm ein weiteres Fenster, können Sie dafür wieder eine extra Prozedur erstellen. Jetzt also zur WndProc Prozedur:

LRESULT CALLBACK WndProc(HWND hWnd, UINT umsg, WPARAM wParam,
                         LPARAM lParam)
{

Der Bezeichner WndProc ist nicht zwingend nötig. Hier können Sie auch wie bei den Funktionen in C einen beliebigen gültigen Bezeichner verwenden. Aber in der Regel findet man für das Hauptfenster diese Bezeichnung vor. Der Rückgabewert von der Funktion WndProc ist LRESULT. Mit CALLBACK geben Sie an, dass diese Funktion von Windows aufgerufen wird. Der erste Parameter (HWND) ist der Fenster-Handle, welcher das Fenster angibt für den diese Nachricht bestimmt war. Der zweite Parameter enthält die Nachricht, die geschickt wurde. Die letzten beiden Parameter enthalten weitere Informationen, welche von der Art der Nachricht abhängig sind.

Die Art der Nachricht können Sie nun mit einem einfachen switch()-Schalter auswerten:

   switch (umsg)
   {
   case WM_DESTROY:
      {
         PostQuitMessage(0);
         return 0;
      }
   }

In diesem Fall überprüfen Sie nur, ob die Nachricht WM_DESTROY (das Fenster wird entfernt) eingetreten ist. Ist dies der Fall, senden Sie mit der Funktion PostQuitMessage() WM_QUIT an die Nachrichten-Schleife zurück. Dies währe das Ende der Schleife und somit auch des Programms.

Damit Sie nicht alle Nachrichten bearbeiten müssen, die bei einer Anwendung auftreten können, finden Sie am Ende die Funktion DefWindowProc() als Rückgabe:

return DefWindowProc(hWnd, umsg, wParam, lParam); 

Damit kümmert sich Windows selbst um die unbehandelten Nachrichten.

Zum Abschluss des ersten Kapitels, nochmals der Quellcode, etwas umfangreicher dokumentiert. Es empfiehlt sich diesen nochmals durchzugehen, ob alles verstanden wurde, bevor Sie mit dem nächsten Kapitel fortfahren.

#include <windows.h>

//Deklaration von WndProc
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

//Name der Applikation
LPCSTR lpszAppName = "AppName";
//Titelleiste der Applikation
LPCSTR lpszTitle   = "Meine erste Applikation";

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{

   HWND       hWnd;   //Fensterhandle
   MSG        msg;    //Nachrichten
   WNDCLASSEX   wc;   //Fensterklasse

   //Größe der Struktur WNDCLASSEX
   wc.cbSize        =  sizeof(WNDCLASSEX);
   //Fensterstil
   wc.style         =  CS_HREDRAW | CS_VREDRAW;
   //Prozedur für das Fenster
   wc.lpfnWndProc   =  WndProc;
   //Speicher für weitere Infos reservieren
   wc.cbClsExtra    =  0;
   wc.cbWndExtra    =  0;
   //Instanz-Handle
   wc.hInstance     =  hInstance;
   //Mauscursor
   wc.hCursor       =  LoadCursor(NULL,IDC_ARROW);
   //Icon
   wc.hIcon         =  LoadIcon(NULL, IDI_APPLICATION);
   //Hintergrundfarbe
   wc.hbrBackground =  (HBRUSH)GetStockObject(WHITE_BRUSH);
   //Applikationsname
   wc.lpszClassName =  lpszAppName;
   //Menüname
   wc.lpszMenuName  =  lpszAppName;
   //Icon neben dem Fenstertitel
   wc.hIconSm       =  LoadIcon(NULL, IDI_APPLICATION);

   //Fensterklasse registrieren
   if( RegisterClassEx(&wc) == 0)
      return 0;

   //Fenster erstellen
   hWnd = CreateWindowEx(//Fensterstil
                         NULL,
                         //Name Fensterklasse
                         lpszAppName,
                         //Fenstertitel
                         lpszTitle,
                         //Art des Fensters
                         WS_OVERLAPPEDWINDOW,
                         //x, y
                         0,0,
                         //Fensterbreite
                         CW_USEDEFAULT,
                         //Fensterhöhe
                         CW_USEDEFAULT,
                      //Fenster-Handle von übergeordnetem Fenster
                         NULL,
                         //Fenster Menü
                         NULL,
                         //Instanz der Anwendung aus WinMain()
                         hInstance,
                         //für MDI-Anwendungen
                         NULL);

   if( hWnd == NULL)
      return 0;

   //Anzeigestatus
   ShowWindow(hWnd, iCmdShow);
   //Fenster zeichnen
   UpdateWindow(hWnd);

   //Nachrichten aus der Warteschlange abfragen
   while (GetMessage(&msg, NULL, 0, 0))
   {
      //für virt. Tastaturcode
      TranslateMessage(&msg);
     //Nachrichten an die Prozedur senden
      DispatchMessage(&msg);
   }
   return msg.wParam;
}

//Prozedur für das Hauptfenster
LRESULT CALLBACK WndProc(HWND hWnd, UINT umsg, WPARAM wParam, LPARAM lParam)
{
   //Nachrichten auswerten
   switch (umsg)
   {
   case WM_DESTROY:
      {
         PostQuitMessage(0);
         return 0;
      }
   }
   //falls Nachricht nicht behandelt wurde von
   //Windows bearbeiten lassen
   return DefWindowProc(hWnd, umsg, wParam, lParam);
}

Weiter mit Kapitel 2: Nachrichten auswerten            zum Inhaltsverzeichnis