Seite 1 von 1

Message Queque bearbeiten verpflichtend?

Verfasst: 13.07.2024, 16:51
von starcow
Schönen Nachmittag liebe Herren und Damen :-)

Ich stehe hier vor einer Frage, auf die ich irgendwie keine (zufriedenstellende) Antwort finde.

Ich registriere (in Windows) meine Fensterklasse und erstelle daraufhin ein schönes Fensterchen.
Nun bin ich ja (nach allem was ich lese) durch die WinAPI verpflichtet, die Message Queque auch wirklich zu bearbeiten und bei "Nicht-Interesse" einer Nachricht, diese zumindest an die DefWindowProc weiterzureichen.
(Bereits nach RegisterClass oder erst nach CreateWindow - ich weiss es leider nicht!)

Eigentlich wollte ich ja jetzt die ganzen Prozess-Schritte für die Erstellung und Initialisierung eines Fensters in separate Init-Funktionen auslagern. Ganz so, wie es die "Grossen" ja bekanntlich auch machen.

Vor meinem inneren Auge müsste das Ganze dann irgendwie so (oder so ähnlich) daherkommen:

Code: Alles auswählen

int main(void)
{                                                /* Ich nehm tatsächlich weiterhin "main", */
	handle_s * handle = init_Proc();         /* da mir mit "WinMain" clang partout kein zusätzliches */
	screen_s * screen = init_Window(handle); /* Konsolenfenster mehr öffnen will (im Gegensatz zu gcc). */

	/* Feinster Code */

	free_Window(screen);
	free_Proc(handle);
	return 0;
}
Nun kann ich natürlich auch noch das Bearbeiten der Messages kapseln

Code: Alles auswählen

while(PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
{
	DispatchMessageA(&msg);
}
Das Problem ist nun aber, dass man jetzt auch weiterhin die entsprechende Funktion regelmässig aufrufen muss, um die Message Queque zu leeren. Das verlangt ja die WinAPI weiterhin! Eine allfällige Kapselung der Nachrichtenschlaufe ist ihr ja völlig Schnurz.

Mein Punkt:
Bei einer Library wie (z. B.) der SDL war das aber irgendwie freiwillig.
Das führt mich dann weiter zur Frage, wie es denn die "Grossen" hinbekommen, den Usern hier freie Hand zu lassen.
Ein Erklärungsversuch: Die SDL erstellt dafür ein separater Thread, der sich im Hintergrund immer um genau diese Sache kümmert.
Das würde dann aber wiederum bedeutet, dass all die Programme zwangsläufig multithreading wären - was ich mir nicht vorstellen kann.

Re: Message Queque bearbeiten verpflichtend?

Verfasst: 13.07.2024, 17:04
von TomasRiker
Auch mit SDL musst du regelmäßig die Messages abarbeiten. Ansonsten reagiert dein Programm nicht richtig auf z. B. Interaktionen mit dem Fenster.

Re: Message Queque bearbeiten verpflichtend?

Verfasst: 13.07.2024, 21:48
von Hannes
Ich verstehe nicht was dich am aufrufen einer Nachrichtenschleifen-Funktion stört?
Eine grundlegende GameLoop wie folgende, findet man in jedem Tutorial oder Buch über Computerspieleprogammierung.

Code: Alles auswählen

int main(void)
{
	CreateGame();

	while(UpdateMessageQueue())
	{
		UpdateInput();
		UpdateGameLogic();
		Render();
	}
	DestroyGame()
	return 0;
}
Nur alte Konsolen, wie der GameBoy, brauchen die Nachrichtenschleife für Fenster nicht zu Aktualisieren, weil es das Konzept Fenster dort nicht gibt.

Re: Message Queque bearbeiten verpflichtend?

Verfasst: 14.07.2024, 11:38
von NytroX
Wie bereits gesagt wurde, die MessageQueue muss immer abgearbeitet werden.
Ich glaube du übersiehst 2 Dinge:

1)
Du verwendest eine Library, um das Fenster zu erstellen, und du hast erstmal keinen Einfluss darauf, was sie tut. Die bindest du auch irgendwo in dein Programm ein, die Implemetierung befindet sich in der user32.dll
Darin gibt es eine Funktion namens "CreateWindow()", die nicht nur ein Fenster erstellt, sondern auch die dazugehörige MessageQueue - und sie teilt dem Betriebssystem mit, dass alle Nachrichten, die das System an dein Programm hat, da rein sollen.
Die MessageQueue ist NICHT THREADSAFE(!), d.h. das Abholen von einem anderen Thread als der, der das Fenster erstellt hat, führt zu undefiniertem Verhalten (GUI-Thread). Das gilt im allgemeinen für alle GUI-Funktionen von Windows, die direkt mit dem System sprechen.
Das gilt dementsprechent auch noch unter Vulkan - da kann man nur so lange mehrere Threads nutzen, wie auf internen Datenstrukturen rumgemacht wird - sobald es ans Rendern (Submission der CommandQueues) geht, ist es Single-Threaded - und das muss der GUI Thread machen.
https://docs.vulkan.org/guide/latest/threading.html

2)
Dein Program hat sowieso IMMER eine Schleife. Und da kannst du ja auch gleich die Queue abarbeiten, alles andere macht halt wenig Sinn, deshalb ist die o.g. Library darauf ausgelegt.
Wenn du keine Schleife hättest, würde das Programm sich ja sofort beenden. Dann kannst du auch eine Konsolenanwendung schreiben, da gibts das alles nicht. Dein Bildschirm aktualisiert sich vermutlich mindestens 60 mal die Sekunde - dass das Fenster "offen bleibt" ist ja quasi schon ein Ausdruck dafür, dass es irgendwo eine Schleife gibt. Ein Prozessor kann grundsätzlich nicht "warten", er verarbeitet immer x Befehle pro Sekunde (die Anzahl an (Giga-)Hertz, die er hat). Das heißt, wenn ein Programm auf Benutzereingabe "wartet", dann läuft es in einen Loop bis es eine Nachricht vom Betriebssystem in der MessageQueue findet.
Die PeekMessage(MSG* msg, void* hndl) Implementierung kann man sich so vorstellen:

Code: Alles auswählen

if (MessageQueueHatNachricht(hndl)) {
    *msg = HoleNachrichtAbFürFensterHandle(hndl);
    return true;
} else {
    return false;
}
Und die GetMessage(MSG* msg, void* hndl) hat zusätzlich einen Loop und ein Sleep:

Code: Alles auswählen

bool NachrichtDa = PeekMessage(MSG* msg, void* hndl);
while(!NachrichtDa){
   SagDemBetriebssystemSchedulerDassEinAndererThreadMeineRestlicheCpuZeitVonMirHabenDarf(); // a.k.a. "Sleep(0);"
   NachrichtDa = PeekMessage(MSG* msg, void* hndl);
}
return true
Die SDL macht auch nichts anderes, als PeekMessage() aufzurufen - die Regeln gelten dort also auch, es muss immer der gleiche Thread sein und die Nachrichten müssen abgeholt werden.
https://github.com/libsdl-org/SDL/blob/ ... ts.c#L1926

Re: Message Queque bearbeiten verpflichtend?

Verfasst: 14.07.2024, 11:48
von starcow
TomasRiker hat geschrieben: 13.07.2024, 17:04 Auch mit SDL musst du regelmäßig die Messages abarbeiten. Ansonsten reagiert dein Programm nicht richtig auf z. B. Interaktionen mit dem Fenster.
Oh, tatsächlich? Dann hatte ich das irgendwie nicht mehr richtig in Erinnerung. Macht aber natürlich jetzt auch mehr Sinn so! Danke Tomas!
Hannes hat geschrieben: 13.07.2024, 21:48 Ich verstehe nicht was dich am aufrufen einer Nachrichtenschleifen-Funktion stört?
Hey Hannes!
Es ist nicht so, dass mich daran irgendwas stören würde.
Irgendwie hatte ich das Gefühl, dass dies beim nutzen einer Library wie der SDL optional wäre - also das abarbeiten der Queque.
Es war dann gewissermassen mehr ein Interesse, wie sich sowas realisieren liesse. Das ist jetzt ja aber hinfällig, weil man sich auch beim Nutzen der SDL (oder auch Raylib?) in "Eigenverantwortung" drum kümmern muss.

Ein Gedanken der mir noch gekommen ist:
Man könnte vielleicht Windows dazu bringen, in regelmässigen Abständen (über einen Tick) die Callback Funktion aufzurufen - und dann an diesem Ort die Nachrichtenschleife bearbeiten. Das müsste dann zwar zu einer Rekursion führen, jedoch nur über zwei "Ebenen", wenn man entsprechende Massnahmen (in der Callback) trifft.
Aber vielleicht übersehe ich jetzt noch einen entscheidenden Punkt und die Idee kann ab in den Papierkorb. :-)

Edit:
NytroX, deine Antwort kam gerade beim Tippen dieses Posts - ich werds gleich lesen. Vielen Dank!

Re: Message Queque bearbeiten verpflichtend?

Verfasst: 14.07.2024, 12:51
von Mirror
NytroX hat geschrieben: 14.07.2024, 11:38 Und die GetMessage(MSG* msg, void* hndl) hat zusätzlich einen Loop und ein Sleep:

Code: Alles auswählen

bool NachrichtDa = PeekMessage(MSG* msg, void* hndl);
while(!NachrichtDa){
   SagDemBetriebssystemSchedulerDassEinAndererThreadMeineRestlicheCpuZeitVonMirHabenDarf(); // a.k.a. "Sleep(0);"
   NachrichtDa = PeekMessage(MSG* msg, void* hndl);
}
return true
Windows hat seit Windows95 preemptives Multitasking. Das bedeudet auch wenn man seinen Thread ständig laufen hat, ohne Sleep oder warten auf ein Event oder sowas, die CPU geshared wird. Unabhängig von der CPU Zahl. Hier hat Microsoft vom Amiga gelernt, der das von Anfang an hatte.
Bei früheren Windows Versionen war noch kooperatives Multitasking angesagt und da wurde der Thread ( soweit ich weiss ) in Sleep oder ähnlichem umgeschaltet. Bin aber kein Windows - Experte...

Re: Message Queque bearbeiten verpflichtend?

Verfasst: 15.07.2024, 19:08
von NytroX
@Mirror: ja, das ist richtig. Ein while(true){} setzt aber deine CPU trotzdem auf 100% (also einen Core/Thread jedenfalls), weil alle anderen Programme sie meist nicht brauchen - den Lüfter hörst du dann auch ganz fix. Bei einem while(true){Sleep(0);} nicht so.

Der Scheduler weist jedem Prozess "Zeitscheiben" zu - meist irgendwas zwischen 5ms und 200ms. Während der Prozess also in der Schleife steckt, denkt der Scheduler, dass der Prozess gerade ganz tolle Berechnungen macht und gibt ihm die komplette Zeitscheibe. Und er bekommt gegenüber anderen Prozessen eine höhere Prio. Aber dank preemptive Multitasking wird der Prozess dann irgendwann Zwangspausiert.

Das Sleep(0); sorgt dafür, dass der Rest der Zeitscheibe nicht aufgebraucht wird. Und auch der Scheduler merkt dann: "Hey, das Programm benutzt gar nicht die komplette zugewiesene Zeitscheibe, macht also grad nix wichtiges (oder wartet auf I/O)" und kann dann entsprechend andere Prozesse bevorzugen.
Siehe auch: https://learn.microsoft.com/en-us/windo ... hapi-sleep
"A value of zero causes the thread to relinquish the remainder of its time slice to any other thread that is ready to run"

Generell war mir das hier aber gar nicht so wichtig, kann auch intern ganz anders umgesetzt sein (WaitForSingleObject(), I/O completion ports, etc) - es ging mir nur grob darum wie das GetMessage() arbeitet ;-)

Re: Message Queque bearbeiten verpflichtend?

Verfasst: 24.07.2024, 01:19
von Krishty
Entschuldigt das Aufkochen; war im Urlaub.
starcow hat geschrieben: 14.07.2024, 11:48Ein Gedanken der mir noch gekommen ist:
Man könnte vielleicht Windows dazu bringen, in regelmässigen Abständen (über einen Tick) die Callback Funktion aufzurufen - und dann an diesem Ort die Nachrichtenschleife bearbeiten. Das müsste dann zwar zu einer Rekursion führen, jedoch nur über zwei "Ebenen", wenn man entsprechende Massnahmen (in der Callback) trifft.
Aber vielleicht übersehe ich jetzt noch einen entscheidenden Punkt und die Idee kann ab in den Papierkorb. :-)
Der entscheidende Punkt, den du übersiehst, ist: Timer werden ebenfalls von der Nachrichtenschleife ausgelöst. Bearbeitest du keine Nachrichten, wird auch der Timer nicht ausgelöst. Kann also in den Papierkorb.

Wie gesagt erfordern auch SDL und Qt, dass dein Thread die Nachrichtenschleife aufruft ("Pumping messages").

Falls das aus NytroX' Antwort nicht klar wurde: Dein Thread bekommt erst eine Message Queue, sobald du ein Fenster erzeugst (Sonderfälle außen vor). Du musst also gewöhnlich auch nur so lange Nachrichten verarbeiten, wie Fenster existieren. Keine Notwendigkeit, das schon ab RegisterClass() zu tun.

Falls es zum Verständnis hilft: Einige Win32-Funktionen pumpen indirekt Nachrichten. Etwa MessageBox() (sonst würde dein Fenster nicht mehr gezeichnet werden, während eine Message Box darüber liegt) oder CreateWindow() (sonst könnten WM_CREATE & Co. nicht abschließen, bevor die Funkion zurückkehrt).

Das Nicht-bearbeiten der Message Queue hat systemweite Auswirkung. Beispielsweise holt sich Alt+Tab dein Programmsymbol und die Echtzeitvorschau durch Nachrichten. Hängt ein Programm, rennt das in einen Timeout und die Alt+Tab-Liste zeigt nur Platzhalter, oder öffnet stark verzögert.

Das Ändern von Windows-Einstellungen wird ebenfalls via Nachricht an alle Programme gebroadcastet. Änderst du über die Systemeinstellungen deine Vordergrundfarbe, aber ein Fenster hängt und bearbeitet die entsprechende WM_DWMCOLORIZATIONCOLORCHANGED-Nachricht nicht, dauert es mitunter eine Minute (Timeout) bis die Farbänderung überall ankommt.

Jetzt zur ganz ursprünglichen Frage: Zum Kapseln der Nachrichtenschleife. Das ist AFAIK nicht Fenster-agnostisch möglich, weil die Nachrichtenschleife pro Fenster unterschiedlich aussieht. Du kennst ja bspw. die Keyboard Shortcuts Strg+S für Datei > Speichern usw.? Damit die funktionieren, musst du TranslateAccelerator() mit einer Tabelle der möglichen Tastenkürzel aufrufen - und zwar direkt nach GetMessage() und vor DispatchMessage(). Du musst also innerhalb der Nachrichtenschleife wissen, welche Tastenkürzeltabelle das Fenster erfordert, für das die nächste Nachricht bestimmt ist. (Selbes mit IsDialogMessage(), falls du innerhalb deines Fensters mit Pfeiltasten/Tab navigieren möchtest; falls die Return-Taste den Standardknopf drücken soll; usw.)

Ich habe bisher absolut keine zufriedenstellene Möglichkeit gefunden, das zu abstrahieren. Du kannst eine thread-lokale globale Variable anlegen, die dir das mappt, und dann hoffen, dass sich alle Fenster dort eintragen.

Das nur als Beispiel, auf was für Hürden du stößt, wenn du die Nachrichtenschleife zu kapseln versuchst.

Qt, WinUI, Firefox/Chrome umgehen das, indem sie Windows-Nachrichten nur für das Nötigste benutzen. Alle Buttons, Toolbars, Texte usw. sind nicht als Win32-Objekte realisiert, sondern werden auf die Zeichenfläche des Parent-Fensters gemalt. Das erfordert gigantischen Aufwand, um so etwas wie Screen Reader für Sehbehinderte zu unterstützen. Außerdem bekommt man den neuesten App-Look (abgerundete Kanten etc.) nicht automatisch, sondern muss das immer selber implementieren. Dass du sauber kapseln kannst, ist dann aber Vorteil von "wir machen eine komplette GUI selber from Scratch".

Re: Message Queque bearbeiten verpflichtend?

Verfasst: 24.07.2024, 17:28
von starcow
Oh, wow! Das war jetzt nochmals richtig viel Hilfreiches, danke Krishty!

Ich hab jetzt einen guten Teil von Charles Petzold Windows Programmierung gelesen (knapp die Hälfte, würde ich schätzen). Das Buch hilft zwar wirklich sehr (wirklich!) - lässt aber auch Entscheidendes der Windows-Interna (weiterhin) im Dunkeln (mein Eindruck bisher jedenfalls).

So genau, wie du die Details immer weisst, vermute ich, dass du so einige windows-spezifische Bücher gelesen haben wirst.
Falls ja, gibt es etwas, was du empfehlen kannst?
Krishty hat geschrieben: 24.07.2024, 01:19 Entschuldigt das Aufkochen; war im Urlaub.
Sehr gut! In welcher Himmelsrichtung warst du denn unterwegs? (Ich rate: Norden) :)

Re: Message Queque bearbeiten verpflichtend?

Verfasst: 24.07.2024, 17:40
von Krishty
starcow hat geschrieben: 24.07.2024, 17:28So genau, wie du die Details immer weisst, vermute ich, dass du so einige windows-spezifische Bücher gelesen haben wirst.
Falls ja, gibt es etwas, was du empfehlen kannst?
Habe keine Bücher gelesen, nur quasi alle Artikel von The Old New Thing (insbesondere die ersten paar Jahre).
Krishty hat geschrieben: 24.07.2024, 01:19Sehr gut! In welcher Himmelsrichtung warst du denn unterwegs? (Ich rate: Norden) :)
Nein, China. ZFX lädt trotz Firewall ganz gut.