Windows Nachrichtenschleife: Volle Prozessorauslastung

Katzenbauer

Erfahrenes Mitglied
Ich habe mir nun eine kleine Vorlage gebastelt die als Windows-Grundgerüst für spätere Programme dienen soll. Wenn ich aber nun mit der Maus über das Fenster fahre bekomme ich immer eine Sanduhr angezeigt und auch im Windows Taskmanager steht 100% Prozessorauslastung. Ich denke ich habe da irgendetwas mit der Nachrichtenschleide falsch gemacht. Könnt ihr euch diesen wahrscheinlich Standardcode mal anschauen (verwende VS 2005):

C++:
/******************************************************
// <Name der Anwendung>
// winmain.h
// Hier befindet sich der Einstiegspunkt der Anwendung
******************************************************/
#include <windows.h>

// Variablen festlegen
#define APP_NAME "VORLAGE"		// Name der Anwendung/Spiel
#define WINDOW_WIDTH  400		// Breite des Fenster
#define WINDOW_HEIGHT 300		// Höhe des Fensters


// Prototyp der Callback Funktion
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// Einstiegspunkt der Anwendung
//
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpcmdline, int ncmdshow)
{
	WNDCLASSEX windowclass;		// Struktur für Fenstereigenschaften
	HWND	   hWnd;			// Fenster-Handle
	MSG		   message;			// Nachricht

	// Die Strukur, die das Fenster beschreibt füllen
	windowclass.cbSize = sizeof(WNDCLASSEX);
	windowclass.style = CS_HREDRAW | CS_VREDRAW;
	windowclass.lpfnWndProc = WindowProc;
	windowclass.cbClsExtra = 0;
	windowclass.cbWndExtra = 0;
	windowclass.hInstance = hInst;
	windowclass.hIcon = NULL;
	windowclass.hIconSm = NULL;
	windowclass.hCursor = NULL;
	windowclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	windowclass.lpszMenuName = NULL;
	windowclass.lpszClassName = APP_NAME;

	// Nun die Struktur bei Windows Registrieren
	if (!RegisterClassEx(&windowclass)) return -1;

	// Positionsdaten für das Fenster berechnen um es mittig zu platzieren
	const int PosX = GetSystemMetrics(SM_CXSCREEN) / 2 - WINDOW_WIDTH / 2;
	const int PosY = GetSystemMetrics(SM_CYSCREEN) / 2 - WINDOW_HEIGHT / 2;

	// Nach der Registrierung kann das Fenster erzeugt werden -> handle speichern
	hWnd = CreateWindowEx(NULL,
						  APP_NAME,
						  APP_NAME,
						  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
						  PosX, PosY,
						  WINDOW_WIDTH, WINDOW_HEIGHT,
						  NULL,
						  NULL,
						  hInst,
						  NULL);

	// Prüfen ob alles glatt ging
	if (hWnd == NULL) return -1;

	// Die Nachrichtenschlange
	ZeroMemory(&message, sizeof(MSG)); // Nachricht Initialisieren
	while (message.message != WM_QUIT)
	{
		// Nachrichten Verarbeiten
		while (PeekMessage(&message, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&message);
			DispatchMessage(&message);
		}

		// Hier ist Platz für Move() und Render() Funktionen
	}

	// Programm erfolgreich beenden
	return (int)message.wParam; 
	
} // WinMain()


// Callback-Funktion zur Nachrichtenverarbeitung
//
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	// Message Auswerten
	switch (message)
	{
		// Fenster schließen (Auch Alt-F4)
		case WM_DESTROY:
		{
			// Nachricht zum Beenden schicken
			PostQuitMessage(0);
			return 0;
		}
	}

	// Alles von unserer Anwendung nicht behandlete direkt an Windows schicken
	return (DefWindowProc(hWnd, message, wParam, lParam));
} // WindowProc
 
Katzenbauer hat gesagt.:
C++:
	// Die Nachrichtenschlange
	ZeroMemory(&message, sizeof(MSG)); // Nachricht Initialisieren
	while (message.message != WM_QUIT)
	{
		// Nachrichten Verarbeiten
		while (PeekMessage(&message, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&message);
			DispatchMessage(&message);
		}

		// Hier ist Platz für Move() und Render() Funktionen
	}
Es ist ganz klar, dass hier volle Prozessorauslastung entsteht. PeekMessage schaut nur in der Nachrichtenwarteschlange nach, ob Nachrichten vorliegen, ansonsten wartet es nicht und gibt die Kontrolle gleich an den aufrufenden Programmteil zurück. Kommen keine Nachrichten an, so kann man sich die geschachtelte while-Schleife also gleich wegdenken und übrig bleibt die „ungebremst“ laufende äußere while-Schleife.

Als Lösungsansatz gibt es jetzt prinzipiell zwei Möglichkeiten
  • GetMessage anstatt PeekMessage verwenden. Dadurch wird beim Überprüfen der Nachrichtenschleife gewartet, bis auch wirklich Nachrichten vorliegen. Nachteil: der Codeteil außerhalb der verschachtelten Schleife wird erst erreicht, wenn sich das Programm beendet. Für eine Echtzeitanwendung ist das natürlich ungeeignet. Abhilfe: Aufteilung in verschiedene Threads.
  • Als Platzhalter für die späteren Methoden Move() und Render() (laut Kommentar) einfach kurz warten, und zwar mit Sleep (z.B. Sleep(10);). Dadurch wird die Nachrichtenschleife etwas „ausgebremst“, da sich die Programmausführung die meiste Zeit in der Sleep-Funktion befindet. Später kann man diesen Aufruf dann bei Bedarf wieder entfernen.
 
Cool Danke so eine ausführliche Antwort habe ich nicht erwartet *ThumbsUp*

Gut also brauche ich Peek, weil meine "Spielfunktionen" sollen ja bei jedem Durchlauf aufgerufen werden, während die Nachrichtenschleife auch abgearbeitet werden soll.

Nun noch eine Verständnisfrage: Wodurch entsteht die scheinbar große Prozessorauslastung? Dadurch, dass das Programm so lange im Leerlauf ist und sich so alle Windows-Zeitscheiben krallt? Und dadurch, dass man das ganze ein wenig verzögert holt sich das Programm die nicht mehr so schnell und lässt was für den Leerlaufprozess übrig? Eine wirkliche Prozessorbelastung ensteht dadurch nicht sondern es verwirrt nur den Windows Taskmanager ein wenig, oder?

Ich weiß viele Fragen, aber, wenn man mal so einen kompetenten Mann an der Strippe hat muss man das auch ausnutzen?;-)
 
Katzenbauer hat gesagt.:
Nun noch eine Verständnisfrage: Wodurch entsteht die scheinbar große Prozessorauslastung? Dadurch, dass das Programm so lange im Leerlauf ist und sich so alle Windows-Zeitscheiben krallt?
Dadurch, dass das Programm ständig was „tut“. (message.message auf WM_QUIT überprüfen, PeekMessage aufrufen, ggf. Nachrichten behandeln, message.message auf WM_QUIT überprüfen, PeekMessage aufrufen, ggf. Nachrichten behandeln usw.) Die Prozessorauslastung ist auch nicht nur scheinbar so hoch, sie ist es auch tatsächlich ;)

Und dadurch, dass man das ganze ein wenig verzögert holt sich das Programm die nicht mehr so schnell und lässt was für den Leerlaufprozess übrig?
Genau, die Programmausführung wird verzögert. Sleep tritt, wie du schon gesagt hast, Rechenzeit an den Leerlaufprozess ab. Und der erzählt wiederum der CPU, dass sie jetzt mal 'ne ruhige Kugel schieben kann ;)

Eine wirkliche Prozessorbelastung ensteht dadurch nicht sondern es verwirrt nur den Windows Taskmanager ein wenig, oder?
Nein, das ist schon tatsächliche Prozessorauslastung. Siehe oben.
 
Okay ich habe das gedacht, weil mein Windows wenn das Ding läuft immer noch schnell reagiert und nicht wie als wenn ich im Hintergrund z.B irgendwas encode alles ewig braucht. Liegt das etwa an den Prioritäten?

Wenn ich nun z.B in die render() Funktion etwas schreibe was per Direct3D (wurde dann vor der Schleife initialisiert) irgendein primitiv zeichnet. Würde dann die Prozessorauslastung auch abnehmen? Aber eigentlich tut das Programm dann auch die ganze Zeit etwas. Dann zeichnet es wahrscheinlich die meiste Zeit, was dem Leerlaufprozess ja auch keine Ruhe gibt. Wenn es damit fertig ist dann halt wieder von vorne und immer wieder. Aber, wenn ich nun irgendein Spiel minimiere, dann hat das auch meistens nur so 20%-30% Auslastung.
 
Zurück