WinApi - Wofür GetMessage-Loop?

12/16/2015 20:40 Shadow992#1
Hallo liebe Community,

ich versuche gerade herauszufinden, was genau bei der WinApi-GUI-Programmierung dieses Grundgerüst macht:

Code:
#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);
}
Dabei ist das ganze C/C++ spezifische Zeugs durchaus klar (ich programmiere ja nicht erst seit gestern). :D
Viel mehr geht es mir um die Windows-Besonderheiten, die mich ein klein wenig verwirren.

Ich habe jetzt ein bisschen nachgelesen und msdn benutzt, aber mir ist sowohl das Gefundene zu ungenau als auch msdn.

Ich geh mal der Reihe nach den Code (ganz grob) durch, damit hier nicht jedes Stück erklärt wird und ihr seht was ich falsch verstanden habe/was nicht. :D

Quote:
LRESULT CALLBACK WndProc(HWND hWnd, UINT umsg, WPARAM wParam, LPARAM lParam)
So viel ich verstanden habe wird diese Funktion aufgerufen sobald mein Fenster eine Windows-Nachricht bekommt. Ich stelle mir das wie eine Art "Soft-Interrupt" vor. Das heißt in meinem Kopf sieht das so aus:

Fenster bekommt Message gesendet (z.B. über Maus, SendMessage, o.ä.) --> Fenster (bzw. genauer gesagt Windows) ruft für jede ankommende Nachricht automatisch die CALLBACK Funktion auf --> in der Funktion wird dann auf die Message reagiert (oder auch nicht) :D

Läuft das wirklich so ab? Fehlen da Schritte dazwischen? Oder kann man das grob zusammengefasst so sagen?

Dann gehts weiter mit der main, die ist soweit ja auch klar, bissel Structs füllen und Pointer/Objekte/etc. holen.
Bis zu der Stelle der While-Schleife ist mir alles klar.


Code:
   while (GetMessage(&msg, NULL, 0, 0) > 0)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
Was das macht habe ich noch nicht ganz verstanden, laut msdn soll ich mir ungefähr folgendes drunter vorstellen:

Zuerst wird per GetMessage die aktuelle message aus der Warteliste des Prozesses/Threads geholt (von dem angegebenen Fenster).
Anschließend wandle ich ich die Message in irgendeine Art von "virtual key" um (was auch immer das genau ist). Für mich klingt das einfach nur nach eine Art Mapping bzw. casting.
Anschließend wird die message versandt, aber wohin?

DispatchMessage hat keinerlei Handle-Angabe, die message scheint also nicht an mein Fenster verschickt zu werden, sondern "irgendwohin"?!

Worauf das alles hinausläuft ist unter anderem auf diese Frage:
Kann ich die While-Schleife auch weglassen (bzw. zumindest leeren) ohne, dass ich Einschränkungen in der Message-Verarbeitung habe? Dass man dies Messages dann irgendwo anders holen/verwerfen/etc. muss ist mir klar, aber warum dann nicht direkt in der Callback-Funktion am Anfang/Ende?
12/16/2015 21:03 Jeoni#2
Das Design dieses Teils der WinAPI ist zu Teilen meiner Meinung nach ziemlich misslungen, da will man sich nicht wirklich mit beschäftigen. Daher auch alle Informationen von mir unter Vorbehalt sehen. Habe damit lange nichts mehr gemacht und vieles selbst nur gehört.
Du brauchst die while-Schleife an einer Stelle deines Programmes. Erst durch diese findet die Messageverarbeitung statt. Irgendwo durch DispatchMessage wird afaik deine Callback-Funktion aufgerufen. Theoretisch könntest du die Callback-Funktion weglassen und die Verarbeitung direkt in der while-Loop machen.
Die Angabe eines Handles für DispatchMessage ist nicht nötig bzw. nicht wirklich möglich, weil du durch den Loop Messages für jedes mit dem Thread assoziierte Fenster bekommst (oder für den Thread generell). Das entsprechende Handle zum betroffenen Fenster befindet sich schon in der [Only registered and activated users can see links. Click Here To Register...].
Nettes Projekt mit der Sprache im Übrigen. Rein vom lehr- bzw. lernwert (dort aber umso mehr), denn generell halte ich den Markt der Sprachen für durchaus gesättigt.
Mit freundlichen Grüßen
Jeoni

P.S.: Die oberen beiden Links ("About ..." und "Using ...") von [Only registered and activated users can see links. Click Here To Register...] könnten vielleicht noch etwas zum Verständnis beitragen.
12/16/2015 21:44 Shadow992#3
Quote:
Originally Posted by Jeoni View Post
Das Design dieses Teils der WinAPI ist zu Teilen meiner Meinung nach ziemlich misslungen, da will man sich nicht wirklich mit beschäftigen. Daher auch alle Informationen von mir unter Vorbehalt sehen. Habe damit lange nichts mehr gemacht und vieles selbst nur gehört.
Du brauchst die while-Schleife an einer Stelle deines Programmes. Erst durch diese findet die Messageverarbeitung statt. Irgendwo durch DispatchMessage wird afaik deine Callback-Funktion aufgerufen. Theoretisch könntest du die Callback-Funktion weglassen und die Verarbeitung direkt in der while-Loop machen.
Die Angabe eines Handles für DispatchMessage ist nicht nötig bzw. nicht wirklich möglich, weil du durch den Loop Messages für jedes mit dem Thread assoziierte Fenster bekommst (oder für den Thread generell). Das entsprechende Handle zum betroffenen Fenster befindet sich schon in der [Only registered and activated users can see links. Click Here To Register...].
Nettes Projekt mit der Sprache im Übrigen. Rein vom lehr- bzw. lernwert (dort aber umso mehr), denn generell halte ich den Markt der Sprachen für durchaus gesättigt.
Mit freundlichen Grüßen
Jeoni

P.S.: Die oberen beiden Links ("About ..." und "Using ...") von [Only registered and activated users can see links. Click Here To Register...] könnten vielleicht noch etwas zum Verständnis beitragen.
Vor allem der About-Link hat mir jetzt die erhoffte "Einsicht" gebracht.
Um es noch einmal kurz zusammen zu fassen, was wirklich passiert, wenn ein Prozess eine Message bekommt (für eventuelle Googler):

1. Die Message wird in die Message-Warteschlange des Threads eingereiht nach dem FIFO-Prinzip. Es wird keinerlei Message/Callback oder ähnliches aufgerufen.
2. Die Message muss "per Hand" aus der Warteschlange entfernt werden, dafür benutzt man GetMessage.
3. TranslateMessage ist dafür da, um Keyboard-Eingaben richtig zu verarbeiten (also scheinbar in den Basis-Fällen ala "isButtonClicked", etc. überflüssig).
4. Sobald DispatchMessage aufgerufen wird, wird asynchron (?) die festgelegte Prozedur aufgerufen und die Message entsprechend übergeben.

Der Clou an der ganzen Sache (vor allem am DispatchMessage) ist, dass das WindowHandle auch in der Message-Struct selbst gespeichert wird:

Quote:
The DispatchMessage function sends a message to the window procedure associated with the window handle specified in the MSG structure.

Quelle: [Only registered and activated users can see links. Click Here To Register...]
Zusammengefasst heißt das also:
Das Weglassen der Schleife (bzw. der DispatchMessage-Methode) heißt gleichzeitig, dass das Fenster auf keinerlei Eingaben/Messages mehr reagiert.
12/17/2015 08:24 Terrat#4
Ja das ist richtig.
Falls du jetzt in richtung multi Window gehst musst du darauf achten das 1 Schleife mehrere Fenster handelt.
12/17/2015 10:49 MrSm!th#5
Kleine Ergänzungen:

Quote:
3. TranslateMessage ist dafür da, um Keyboard-Eingaben richtig zu verarbeiten (also scheinbar in den Basis-Fällen ala "isButtonClicked", etc. überflüssig).
Das mag vielleicht aktuell so sein (kann mir gut vorstellen, dass da auch mal andere Mappings hinzukommen könnten), ich wüsste aber nicht, welchen Vorteil es einem bringen würde, den Aufruf wegzulassen. Falls es dir um Performance geht: Die Internals der Message Loop bzw. das Holen, Vorverarbeiten und Dispatchen dürften nun wirklich die letzten Aspekte sein, an denen man Performance rausholen kann. Immerhin geht es hier um ein GUI, etwas Interaktives - da ist sowieso der Mensch das Bottleneck.
Quote:
4. Sobald DispatchMessage aufgerufen wird, wird asynchron (?) die festgelegte Prozedur aufgerufen und die Message entsprechend übergeben.
Nein, das geschieht nicht asynchron, es geschieht ganz normal als Funktionsaufruf irgendwo innerhalb von DispatchMessage bzw. einer davon aufgerufenen Funktion. Deshalb ist es ja auch so wichtig, keine blockierenden I/O-Aufrufe oder allgemein zeitintensive Aufgaben in der WndProc durchzuführen. Wenn du dort blockierst, blockierst du die Message Loop und somit die gesamte Reaktionsfähigkeit des Programms. Das zieht sich durch alle Layer, die auf der Win32 API aufbauen und trifft auch z.B. bei .NET Programmen zu, die die eigentliche Message Loop abstrahieren (aber eben nicht umgehen).

Und deswegen könntest du die Loop auch nicht weglassen. Nicht nur, dass dein Programm dann keine Nachrichten verarbeiten würde, es würde de facto sofort beendet werden. Das Verweilen in der Message Loop ist schließlich das einzige in deinem Code, das den Main Thread davon abhält, direkt wieder aus der WinMain zu returnen und somit den Prozess zu beenden.

Quote:
Der Clou an der ganzen Sache (vor allem am DispatchMessage) ist, dass das WindowHandle auch in der Message-Struct selbst gespeichert wird:
Jo, die Message Loop verarbeitet alle Messages, die in die Queue des Threads gesteckt werden, sprich für alle Fenster, die zu dem Thread gehören, in dem GetMessage() aufgerufen wird. Die Zuweisung an die jeweiligen Fenster ist für dich als Aufrufer von GetMessage() nicht direkt relevant, das ist sogesehen eine Eigenschaft der Message selbst und erst innerhalb von DispatchMessage() von Bedeutung.
12/17/2015 14:10 Shadow992#6
Quote:
Originally Posted by MrSm!th View Post
Kleine Ergänzungen:
Falls es dir um Performance geht: Die Internals der Message Loop bzw. das Holen, Vorverarbeiten und Dispatchen dürften nun wirklich die letzten Aspekte sein, an denen man Performance rausholen kann. Immerhin geht es hier um ein GUI, etwas Interaktives - da ist sowieso der Mensch das Bottleneck.
Nö darum gings mir eigentlich nicht, war mehr ein "wenn das was ich schreibe falsch ist, wird jetzt jemand aufspringen und sich beschweren". :D

Danke dir für die Ergänzungen, gut zu wissen, dass das als "simpler" Funktionsaufruf implementiert ist. Und ich dachte Windows ist was Besonderes. :D