Win32-Programmierung mit VC++ und der Win-Api

1.1 - Das Win32-Konstrukt

So, ich hoffe das letzte Kapitel (Erstellen einer Windows-Anwendung) habt Ihr gut überstanden… Jetzt gehts erstmal an die Erklärung des ganzen Codes. Ihr könnt euch das Win32-Konstrukt herunterladen. Mit diesem Win32-Konstrukt werden wir alle künftigen Programme aufbauen. Das Konstrukt enthält ein Minimum an Code, um ein Win32-Programm zum laufen zu bekommen. Am Ende eines jeden Abschnittes befinden sich einige Links und Downloadmöglichkeiten.

Dialog01.cpp

#include <windows.h>
#include <commctrl.h>

#include "resource.h"

LRESULT CALLBACK WindProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK HelpProc(HWND hwnd,UINT Message,WPARAM wParam,LPARAM lParam);

HINSTANCE hInstance;
HDC hdc;

//---------------------------------------------------------------------------
// HAUPTFENSTER
//---------------------------------------------------------------------------
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT ("DIALOG01") ;
    HWND hwnd;
    MSG msg;
    WNDCLASS windclass;

    windclass.style = CS_HREDRAW | CS_VREDRAW;
    windclass.lpfnWndProc = WindProc ;
    windclass.cbClsExtra = 0;
    windclass.cbWndExtra = DLGWINDOWEXTRA ;
    windclass.hInstance = hInstance ;
    windclass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
    windclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
    windclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1) ;
    windclass.lpszMenuName = 0;
    windclass.lpszClassName = szAppName ;

    if (!RegisterClass (&windclass))
        return 0 ;

    hwnd = CreateDialog (hInstance, szAppName, 0, NULL);

    InitCommonControls();
    ShowWindow (hwnd, iCmdShow);
    UpdateWindow(hwnd);
    PostMessage(hwnd, WM_COMMAND, IDC_INIT,0);

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

void Init (HWND hwnd)
{
}

LRESULT CALLBACK WindProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_CREATE:
        break;

        case WM_DESTROY:
        PostQuitMessage(0);
        break;

    case WM_COMMAND:
    {
        switch( LOWORD( wParam ) )
        {
            case IDC_INIT:
            {
                Init(hwnd);
            }
            break;
        }
    }
    return 0 ;
    }
    return DefWindowProc (hwnd, message, wParam, lParam) ;
}

Ein Windows-Programm arbeitet grundsätzlich anders als ein Dos-Programm. Ein Dos-programm agiert (d.h. es leitet die Befehle, die der Programmierer geschrieben hat, an das Betriebssystem weiter), während ein Windows-Programm nur reagiert (d.h. es wartet bis es vom Betriebssystem oder vom Benutzer einen Befehl bekommt).

In jedem Windows-Programm gibt es eine WinMain-Funktion. Sie entspricht in etwa der Main-Funktion eines Dos-Programms. In ihr werden Fensterklassen erzeugt. Was mit den Fenstern passiert, ob der Benutzer etwas eingegeben hat oder das Betriebssystem eine Nachricht an das Fenster gesendet hat, um das alles kümmert sich aber eine andere Funktion: die Windowsprozedur (WndProc).

Zugegeben: Für ein Hallo-Welt-Programm nicht gerade kurz. Aber dafür tut es mehr, als wir am Bildschirm sehen können. Doch dazu später mehr. Schauen wir uns das Listing noch mal an. Schon (oder erst) in der 12. Zeile fällt etwas auf: Die komischen Variablennamen (z.B. hInstance, szCmdLine). Die kleinen Buchstaben am Anfang des Namens rühren von dem legendären Microsoft-Programmierer Charles Simonyi her und werden ihm zu Ehren “Ungarische Notation” genannt. Der Vorteil: Am Variablennamen, der ja (fast) beliebig lauten kann, kann man erkennen, was für eine Variable es ist. Die meisten Programmierer halten sich an diese “quasi-Konvention”.

Das Programm lässt sich, wie bereits am Anfang erwähnt, in zwei Teile zerlegen: die Hauptfunktion WinMain und die Nachrichtenfunktion WindProc. Betrachten wir zuerst WinMain. WinMain übernimmt 4 Argumente: HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine und int iCmdShow. Die ersten beiden Argumente sind vom Typ HINSTANCE. Wenn ein Programm mehrere Male gestartet wird, laufen mehrere Instanzen des Programms gleichzeitig. Damit Windows diese auseinanderhalten kann, bekommt jedes Programm beim Start eine Kennziffer (in unserem Beispiel hInstance). Die Kennziffer, die die vorhergehende Instanz bezitzt (hPrevInstance), war bei 16-Bit Programmen wichtig. Mit ihrer Hilfe würden unveränderliche Daten des Programms (z.B. das Menü) nur einmal geladen und jede Instanz hat darauf zugegriffen. Im heutigen 32-Bit Zeitalter laufen aber alle Instanzen voneinander getrennt ab. Deshalb brauchen wir und um diesen Parameter ebensowenig zu kümmern, als um die Zuweisung der Kennziffern; das erledigt Windows automatisch. Der dritte Parameter, PSTR szCmdLine, ist vergleichbar mit argc und argv der Dos-Programmierung. Er enthält die Kommandozeilenparameter, mit denen das Programm aufgerufen wurde. Bei unserem Programm ist das aber nicht der Fall. Der vierte Parameter, int iCmdShow, enthält Informationen, wie das Programm gestartet wird (z.B minimiert oder im Vollbild). Als erstes wird ein Char-Array angelegt. Es wird später für die Bezeichnung der Fensterklasse eingesetzt. Dann wird ein Fensterhandle hwnd und eine Message msg angelegt. Ihr Bedeutung wird gleich klarer werden.

Mit WNDCLASS wndclass wird eine Fensterklasse wndclass angelegt, deren Members auch gleich Werte zugewiesen werden. Wir werden sie später genauer betrachten. Mit RegisterClass(&wndclass) wird die eben erzeugt Klasse “angemeldet”. Jetzt wird ein Fenster der Klasse wndclass generiert, das über den am Anfang angelegten Handle hwnd angesprochen werden kann. Mit ShowWindow(hwnd, iCmdShow) und UpdateWindow(hwnd) wird das Fenster am Bildschirm dargestellt und aktualisiert.

Jetzt folgt die Ereignisschleife. Sie wartet auf Nachrichten des Betriebssystems oder des Benutzers, um die an die Nachrichtenfunktion WndProc weiterzuleiten. Falls WndProc nichts mit der Nachricht anfangen kann, wird eine Standardroutine aufgerufen.

WindClass

windclass.style= CS_HREDRAW | CS_VREDRAW;

Dieser Teil enthält Informationen über die Art der Fensterklasse. Die beiden Werte CS_HREDRAW und CS_VREDRAW, die mit einer Pipe ( | ) verknüpft werden, besagen, dass Windows jedesmal, wenn das Fenster in seiner horizontalen und vertikalen Größe verändert wird, eine WM_PAINT Message an das Programm sendet (Mehr über Messages in der Beschreibung von WndProc).

windclass.lpfnWndProc = WindProc;

Legt die Windowsprozedur der Fensterklasse fest.

windclass.cbClsExtra = 0;
windclass.cbWndExtra = DLGWINDOWEXTRA;

Wenn man Speicherplatz für eigene Daten im Rahmen von Fensterklassen bzw. Fenstern reservieren will, kann man dies hier tun. Wir machen aber keinen Gebrauch davon, also werden die Werte auf 0 gesetzt.

windclass.hInstance = hInstance;

Legt die Instanz des Programms fest (die ja der WinMain-Funktion übergeben wird).

windclass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
windclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

Der erste Aufruf legt fest, welches Icon jedes Fenster dieser Klasse am oberen linken Rand und in der Taskleiste anzeigt. Wir verwenden ein eingebundenes Icon. Der zweite Aufruf legt den Cursor, der über dem Fenster erscheinen soll, fest. IDC_ARROW ist einer der Standardcursor.

windclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1) ;

Hiermit wird der Hintergrund der Fensterklasse festgelegt. HBRUSH steht für “handle [to a] brush”. Mit (COLOR_BTNFACE + 1) wird dem Hintergrund das Standard-Grau gegeben.

windclass.lpszMenuName = 0;

Wenn unsere Fensterklasse ein Menu besitzen soll (z.B. Datei, Bearbeiten oder Hilfe) müssten wir die Klasse mit dem entsprechenden Handle verknüpfen. Wir verwenden aber (noch) kein solches Menü, also setzen wir NULL (0) ein.

windclass.lpszClassName = szAppName;

Jetzt wird der Fensterklasse ein Name gegeben. Wir verwenden das Char-Array, das gleich am Anfang unseres Programms erzeugt wurde.

WndProc In der Fensterprozedur WndProc werden mehrer Variablen erzeugt. Diese Variablen und was genau in den verschiedenen Nachrichten passiert, werden wir uns im Kapitel über Textausgabe genauer anschauen. Unser Programm reagiert bisher auf zwei Nachrichten: WM_CREATE und WM_DESTROY.

WM_CREATE wird aufgerufen, wenn das Fenster erzeugt wird, also nur einmal. (Unser Programm verwendet diese Nachricht, um die Standardschriftgröße, die von System zu System variiren kann, zu ermitteln und in den Variablen cxChar, cyChar und cxCaps zu speichern) Diese Nachricht muss nicht in jedem Programm vorhanden sein.

WM_DESTROY wird aufgerufen, wenn das Programm beendet wird. Sei es durch das Programm selbst, einen Button, oder bei drücken des Kreuzen (oben rechts). Sie sendet mit PostQuitMessage(0) eine Beenden-Nachricht mit dem Wert 0 an Windows. Diese Nachricht kann auch dazu verwendet werden, um vor dem Beenden noch z.B. Einstellungen zu speichern. Sie sollte in jedem Programm vorhanden sein.

Falls die Nachricht, die das Programm empfängt, nicht durch einen der “Empfänger” behandelt werden kann, wird sie mit return DefWindowProc(hwnd, message, wParam, lParam) an die Standardroutine von Windows weitergegeben.

Download: Win32-Konstrukt 37KB - Kompletter VC++ Workspace

Für diesen Text geht der Dank an Valentin Hans. Von Ihm habe ich grundlegende Informationen und Erläuterungen. Seine Homepage liefert einen tollen Überblick über die Windowsprogrammierung.