zurück zur Startseite

API-Windows-Programmierung (ohne MFC)

Inhalt

1. WinMain(...) und windows.h
2. Eigene Fenster erzeugen
3. Zwei Fenster auf Basis einer WNDCLASS-Struktur erzeugen
4. Nachrichtenverarbeitung
5. Interaktionen mit Kindfenstern
6. DLL-Programmierung
 

4. Nachrichtenverarbeitung

Als Basis für die Untersuchung der Nachrichtenverarbeitung verwenden wir unser erstes Fenster,
hier noch einmal das vollständige Programm:
 
#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); 
//Deklaration der Windows-Nachrichten-Prozedur

int WINAPI WinMain (HINSTANCE hI, HINSTANCE hPrI, PSTR szCmdLine, int iCmdShow)
{
char szName[] = "Fensterklasse";
WNDCLASS wc;

wc.style         = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc   = WndProc;
wc.cbClsExtra    = 0;
wc.cbWndExtra    = 0;
wc.hInstance     = hI;
wc.hIcon         = LoadIcon (NULL, IDI_WINLOGO);
wc.hCursor       = LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
wc.lpszMenuName  = NULL;
wc.lpszClassName = szName;

RegisterClass (&wc);

HWND hwnd = CreateWindow (szName, "", WS_SYSMENU | WS_THICKFRAME, 0, 0, 200, 100, NULL, NULL, hI, NULL);

ShowWindow (hwnd, iCmdShow);
UpdateWindow (hwnd);

// Nachrichten-Schleife
MSG msg;
    while (GetMessage (&msg, NULL, 0, 0))
    {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
return msg.wParam;
}

// Windows-Nachrichten-Prozedur
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;

switch (message)
  {
  case WM_PAINT:
    hdc = BeginPaint (hwnd, &ps);
        TextOut (hdc, 20, 20, "Ich bin ein Fenster.", 20);
    EndPaint (hwnd, &ps);
  return 0;

  case WM_DESTROY:
    PostQuitMessage (0);
  return 0;
  }

return DefWindowProc (hwnd, message, wParam, lParam);
}

Wenn wir das Programm kompilieren und starten, erscheint folgendes einfaches Fenster in der linken oberen Ecke.

Jetzt interessieren wir uns nicht für die Fenstermerkmale dieses Fensters, sondern wenden uns der Nachrichtenverarbeitung in dieser Windows-Anwendung zu. Zuständig für die Verarbeitung der Nachrichten innerhalb unseres Programmes ist die Windows-Prozedur,
deren Namen wir mittels   wc.lpfnWndProc = WndProc; in der WNDCLASS-Struktur als  WndProc  festgelegt haben.

Nachricht heißt innerhalb Windows message. Die Kennung WM_... bedeutet Windows Message.
In WndProc(...) werden die Nachrichten über eine switch/case-Kontrollstruktur sozusagen ausgefiltert,
um mittels individuellem Code darauf zu reagieren. Alle Nachrichten, für die wir keine besondere Aktion vorgesehen haben,
übergeben wir der allgemeinen Windows-Nachrichten-Prozedur DefWindowProc(...).

Analysieren wir als Beispiel die wichtige Nachricht WM_DESTROY:
Innerhalb von WndProc(...) haben wir für WM_DESTROY folgendes vorgesehen:

case WM_DESTROY:
    PostQuitMessage (0);
return 0;

Beginnen wir mit WM_DESTROY und PostQuitMessage (0). Was ist bzw. bewirkt das genau?
Wenn Sie herausfinden wollen, was ein Codefragment, das Sie irgendwo übernommen haben,
genau bewirkt, dann sollten Sie folgendermaßen vorgehen:

Also schauen wir nach, was MSDN ergibt. Übrigens funktioniert das in VC++ bei korrekter MSDN-Installation sehr einfach:
Den gesuchten Begriff im VC++-Code markieren und dann Funktionstaste F1 drücken.
MSDN liefert uns daraufhin die gewünschten Erläuterungen:

"The WM_DESTROY message is sent when a window is being destroyed.
It is sent to the window procedure of the window being destroyed after the window is removed from the screen.
This message is sent first to the window being destroyed and then to the child windows (if any) as they are destroyed.
During the processing of the message, it can be assumed that all child windows still exist.

The PostQuitMessage function indicates to the system that a thread has made a request to terminate (quit).
It is typically used in response to a WM_DESTROY message.

VOID PostQuitMessage(  int nExitCode   // exit code );

nExitCode:        Specifies an application exit code. This value is used as the wParam parameter of the WM_QUIT message.
Return Values:    This function does not return a value.
Remarks:          The PostQuitMessage function posts a WM_QUIT message to the thread's message queue and returns immediately;
                  the function simply indicates to the system that the thread is requesting to quit at some time in the future.
                  When the thread retrieves the WM_QUIT message from its message queue, it should exit its message loop and return
                  control to the system. The exit value returned to the system must be the wParam parameter of the WM_QUIT message."

Fazit:
WM_DESTROY wird gesendet, wenn das Fenster zerstört wird. Daraufhin müssen wir mittels
PostQuitMessage(0) eine Nachricht WM_QUIT mit wParam=0 erzeugen, damit die Nachrichtenschleife (message loop) endet.

Jetzt schauen wir schnell in MSDN noch bei WM_QUIT nach:
"The WM_QUIT message indicates a request to terminate an application and is generated when the application calls the PostQuitMessage function.
It causes the GetMessage function to return zero.
nExitCode:     Value of wParam. Specifies the exit code given in the PostQuitMessage function.
Return Values: This message does not have a return value, because it causes the message loop to terminate before the message is sent to the application's window procedure."

Jetzt haben wir diese typische Situation, in die Einsteiger oft geraten. MSDN oder andere Literatur beschreiben theoretisch die notwendigen Zusammenhänge, damit ein Windows-Programm reibungslos funktioniert. In der Praxis versteht man es jedoch erst wirklich, wenn man an allem "herumschraubt". Also los: Zunächst betrachten wir das Experimentierfeld. Was haben wir zur Verfügung? Zunächst unsere Anwendung, in der wir Änderungen durchführen können. Ein weiteres wichtiges Instrument ist der Taskmanager, der durch STRG+ALT+Entf gestartet wird.
Wenn wir unsere Anwendung starten, finden wir dort einen Eintrag für unser Programm, z.B. Einfachesfenster01 oder ähnlich. Wenn wir nun die Anwendung schließen ("X", Alt+F4, Systemmenü), dann verschwindet dieser Eintrag im Taskmanager. Das bedeutet, dass das Programm wirklich beendet ist. Das Verschwinden des Fensters reicht hierzu nicht aus, wie Sie bereits ahnen.

O.k., starten wir unser erstes Experiment: Wir erzeugen einfach keine Nachricht WM_QUIT:

case WM_DESTROY:
    //PostQuitMessage (0);
return 0;

Was passiert? Das Fenster verschwindet, aber das Programm läuft weiter, wie Sie mittels Taskmanager beweisen können.
Probieren Sie es mehrfach mit und ohne PostQuitMessage(0) aus, damit Sie den Unterschied sicher erkennen.

Wo befindet sich unser Programm, wenn es das Fenster schließt, aber kein WM_QUIT sendet?
In der Nachrichtenschleife:

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

Aha, in dieser while-Schleife werden also die "Messages" eingesammelt. Wir drücken auf "X". Das Fenster verschwindet.
GetMessage(...) liefert WM_DESTROY. Nachrichten an unser Fenster werden in WndProc(...) bearbeitet.
In WndProc(...) erzeugen wir mittels PostQuitMessage(...) die Nachricht WM_QUIT,
die bei GetMessage(...) den Rückgabewert Null (FALSE) erzeugt,
der dann die while-Schleife beendet und msg.wParam als Rückgabewert von WinMain(...) zurückgibt.
Daraufhin endet das Programm. Alles klar? Nein?

Dann schauen wir uns die Nachrichtenschleife (message loop) doch noch genauer an:
Zunächst finden wir dort eine MSG-Struktur, die Basis für unsere Messages (Nachrichten):
 
typedef struct tagMSG 

   HWND hwnd
   UINT message
   WPARAM wParam
   LPARAM lParam
   DWORD time
   POINT pt
}  MSG;

hwnd:       Handle des Fensters, dessen Window-Prozedur die Nachricht erhält.
message:    Message-Nummer.
wParam:     Information über die Nachricht.
lParam:     Information über die Nachricht.
time:       Zeitpunkt, an dem die Nachricht gesendet wurde.
pt:         Cursor Position (Bildschirmkoordinaten), als die Nachricht gesendet wurde.

Die MSG-Struktur ist das Transportmittel für die Windows-Nachrichten. Innerhalb der Struktur befinden sich alle notwendigen Informationen
zur Beschreibung der Nachricht. Wie können wir dies austesten? Ganz einfach, wir ergänzen unsere switch/case-Struktur um einen Mausklick mit der linken Taste, sodaß wir die Parameter hwnd, message, wParam und lParam ausgeben können:

switch (message)
{
case WM_PAINT:
    hdc = BeginPaint (hwnd, &ps);
      //TextOut (hdc, 20, 20, "Ich bin ein Fenster.", 20);
    EndPaint (hwnd, &ps);
return 0;

case WM_DESTROY:
    PostQuitMessage (0);
return 0;

case WM_LBUTTONDOWN:
 hdc = GetDC(hwnd);
  char str[50];
  sprintf(str,"%i|%i|%i|%i",hwnd,message,wParam,lParam);
  TextOut (hdc, 20, 20, str, strlen(str));
 ReleaseDC(hwnd,hdc);
return 0;
}

Wegen der Anweisung sprintf(...) müssen wir auch den zugehörigen header stdio.h einbinden:

#include <windows.h>
#include <stdio.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain (HINSTANCE hI, HINSTANCE hPrI, PSTR szCmdLine, int iCmdShow)
{
...

Wenn Sie jetzt mit der linken Maustaste im Fenster klicken, erhalten Sie Fensterhandle,  Nachricht (513 entspricht offenbar WM_LBUTTONDOWN), wParam und lParam als Integer-Ausgabe:

Bei einem Neustart des Programms erhält das Fenster jeweils einen neuen Handle. Die Nummer der Message bleibt bei 513.
wParam ist 1 und lParam ändert sich ständig. Der Hauptinformationsgehalt steckt also dort in lParam.

Verdauen Sie dies bitte in aller Ruhe! Windows besteht fast nur aus Zahlen, die zum besseren Programmieren als Konstanten definiert sind.
Wir werden dies prüfen, indem wir WM_LBUTTONDOWN einfach durch die oben ermittlete Nachrichten-Nummer 513 ersetzen:

case 513: // WM_LBUTTONDOWN:
 hdc = GetDC(hwnd);
  char str[50];
  sprintf(str,"%i|%i|%i|%i",hwnd,message,wParam,lParam);
  TextOut (hdc, 20, 20, "                               ", 30);  // 30 * Leertaste
  TextOut (hdc, 20, 20, str, strlen(str));
 ReleaseDC(hwnd,hdc);
return 0;

Es funktioniert! Sie glauben es noch immer nicht? Die Datei winuser.h liefert Ihnen die Antwort.
Schauen Sie sich dort gut um, damit Sie das Windows-Nachrichtensystem besser verstehen :
...
#define WM_MOUSEFIRST                   0x0200
#define WM_MOUSEMOVE                    0x0200
#define WM_LBUTTONDOWN                  0x0201 // dezimal 513
#define WM_LBUTTONUP                    0x0202
#define WM_LBUTTONDBLCLK                0x0203
#define WM_RBUTTONDOWN                  0x0204
#define WM_RBUTTONUP                    0x0205
#define WM_RBUTTONDBLCLK                0x0206
#define WM_MBUTTONDOWN                  0x0207
#define WM_MBUTTONUP                    0x0208
#define WM_MBUTTONDBLCLK                0x0209
...

Damit Sie mehr Freude an diesem kleinen Testprogramm haben, hier die Details von LBUTTONDOWN:

WM_LBUTTONDOWN wird bei einem Linksklick der Maus erzeugt,  während man sich im "client area" befindet.
Das Fenster unter dem Mauscursor erhält diese Nachricht nur dann, wenn die Maus nicht im "capture" eines anderen Fensters ist.

WM_LBUTTONDOWN
fwKeys  =  wParam;
xPos      =  LOWORD ( lParam ); // x-Koordinate relativ zur oberen linken Ecke der "client area"
yPos      =  HIWORD  ( lParam ); // y-Koordinate relativ zur oberen linken Ecke der "client area"

fwKeys
Hier ist die Information enthalten, ob verschiedene "virtual keys" (VK_...) gedrückt sind.
Folgende Werte können dort enthalten sein:
 
MK_CONTROL Strg-Taste
MK_LBUTTON Linke Maustaste
MK_MBUTTON Mittlere Maustaste
MK_RBUTTON Rechte Maustaste
MK_SHIFT  Shift-Taste

Hier sehen Sie, dass wParam nicht immer 1 sein muß. Er zeigt die key-flags an. Ausprobieren! Wenn Sie Shift drücken, erhalten Sie 5,
bei Strg erhalten Sie 9. Drücken Sie die linke Maus bei gedrückter rechter Maus, dann ergibt sich 3.

Jetzt noch mal zu lParam. Dieser Parameter hat ein LOWORD und ein HIWORD. Also verfeinern wir unser Programm ein wenig:

switch (message)
{
case WM_PAINT:
    hdc = BeginPaint (hwnd, &ps);
  //TextOut (hdc, 20, 20, "Ich bin ein Fenster.", 20);
    EndPaint (hwnd, &ps);
return 0;

case WM_DESTROY:
    PostQuitMessage (0);
return 0;

case WM_LBUTTONDOWN:
 hdc = GetDC(hwnd);
  char str[50];
  sprintf(str,"%i|%i|%i|%i|%i",hwnd,message,wParam,LOWORD(lParam),HIWORD(lParam));
  TextOut (hdc, 20, 20, "                               ", 30);  // 30 * Leertaste
  TextOut (hdc, 20, 20, str, strlen(str));
 ReleaseDC(hwnd,hdc);
return 0;
}

Ein Mausklick in die linke obere Ecke (0,0) liefert entsprechend die x- und y-Koordinate:

Ich hoffe, dass Sie nun das Nachrichtensystem und die Verarbeitung in der Windows-Prozedur (hier: WndProc)
prinzipiell verstanden haben.

Zurück zur Nachrichtenschleife:

    MSG msg;
    while (GetMessage   (&msg, NULL, 0, 0) )
    {
     TranslateMessage (&msg);
     DispatchMessage  (&msg);
    }
 
GetMessage(...) holt die Nachrichten aus der Windows-Nachrichtenschlange für unseren Thread.
TranslateMessage(...)  übersetzt Virtual-Key-Nachrichten in Character-Nachrichten und
DispatchMessage(...) liefert die Nachrichten bei der Windows-Prozedur ab.

Schauen Sie diesbezüglich auch in MSDN nach.

Die Parameterliste von GetMessage(...) ist wie folgt:

BOOL GetMessage(
  LPMSG lpMsg,
  HWND hWnd,
  UINT wMsgFilterMin,     // first message
  UINT wMsgFilterMax      // last message
);

Wenn hWnd auf NULL gesetzt wird, erhält GetMessage Nachrichten aller Fenster, die zum aufrufenden Thread gehören.
Mit Hilfe von wMsgFilterMin und wMsgFilterMax kann man die eingehenden Nachrichten filtern.
Der Rückgabewert dieser Funktion ist TRUE, solange nicht WM_QUIT als Message auftaucht.
Dieser Mechanismus wird durch die while-Schleife genutzt:

while (GetMessage   (&msg, NULL, 0, 0) )  {/*Nachrichtenverarbeitung*/}

Wir befinden uns solange in der "Nachrichtenschleife", wie der Rückgabewert von GetMessage TRUE ist.
Ansonsten (bei WM_QUIT) wird diese Schleife verlassen.

Diesen Mechanismus der "Nachrichtenpumpe" testen wir sofort aus. Also, wir nehmen unser Mausklick-Programm von oben und streichen einfach TranslateMessage und DispatchMessage:

// Nachrichten-Schleife
MSG msg;
    while (GetMessage (&msg, NULL, 0, 0))
    {
        //TranslateMessage (&msg);
        //DispatchMessage (&msg);
    }
return msg.wParam;
}

Oh je, oh je! Was für ein Monster haben wir hier erzeugt. Es reagiert nicht mehr auf Mausklick und läßt sich auch nur noch über den Taskmanager schließen! Das liegt daran, daß unsere Nachrichten, die wir durch unsere Aktionen erzeugen, einfach ignoriert werden.
Niemand reicht die von GetMessage in die Nachrichtenstruktur msg weitergegebenen Botschaften an unsere WndProc(...) weiter.

Also nehmen wir zumindest DispatchMessage(...) wieder in Gebrauch:

// Nachrichten-Schleife
MSG msg;
    while (GetMessage (&msg, NULL, 0, 0))
    {
      //TranslateMessage (&msg);
    DispatchMessage (&msg);
    }
return msg.wParam;
}

Jetzt funktioniert das System wieder. GetMessage(...) fängt die Nachrichten ein, reicht diese an die Struktur msg weiter. DispatchMessage(...) gibt diese Nachrichten geordnet an WndProc(...) weiter. Auch die Shift- und Strg-Taste werden beim Mausklick korrekt erfaßt.

Damit Sie ein Gefühl für die Viehlzahl an Nachrichten bekommen, setzen Sie bitte das VC-Dienstprogramm Spy++ ein.
Nachstehend sehen Sie die anfallenden Nachrichten zwischen Drücken (WM_LBUTTONDOWN) und Loslassen (WM_LBUTTONUP)
der linken Maustaste. Die Nachrichten 1-57 fielen an, als die Maus in das Fenster eindrang:

Wie Sie sehen, zeigt Spy++ z.B. in Nachricht <00058> auch die zugehörigen Parameter der Message-Struktur an.
Sie sehen hier auch mein "Zittern" beim Mausklick von xpos=115 nach xpos=116, das durch WM_MOUSEMOVE angezeigt wird.
Experimentieren Sie munter weiter. Ein Eldorado für Message-Fans! Wenn Sie das Nachrichtensystem begreifen, haben Sie den Kern von Windows verstanden.


weiter zu Teil 5: Interaktionen mit Kindfenstern

zurück zur Startseite