windows.h -> Anwendung schließt sich nicht ordnungsgemäß -> ich behandle zwei Windows gleichzeitig



  • Hallo,
    Ich fuchse mich gerade in die WINAPI hinein...
    Beim Start des Programms öffnet sich das MainWindow. Oben im Menü gibt es den Reiter "Über", hier kann die Option "Info" ausgewählt werden, wodurch sich ein zweites, kleines Window (InfoWindow) öffnen soll...
    Das funktioniert soweit auch alles..
    Die beiden Windows sind parallel bedienbar (was kein muss ist), und auch unabhängig schließbar...

    Habe ich jedoch beide Fenster geschlossen, sollte das Programm beendet sein... Visual Studio aber sagt, dass das Programm noch läuft...
    Irgendwo im Code hängt es sich wahrscheinlich auf.. aber ich bin langsam echt überfragt..
    Link Text -> Nach dem schließen aller Windows.

    #include <windows.h>
    #include <cassert>
    
    #define ABOUT_MENU_INFO 2
    
    LRESULT CALLBACK MainWindowProcedure(HWND, UINT, WPARAM, LPARAM);
    LRESULT CALLBACK InfoWindowProcedure(HWND, UINT, WPARAM, LPARAM);
    
    void AddMenus(HWND);
    
    HMENU hMenu;
    
    int infoWindow();
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) {
    
    	hInstance = GetModuleHandle(0);
    	HWND hWnd;
    	WNDCLASSW wcMain;
    	MSG msg;
    
    	wcMain = {};
    	wcMain.hbrBackground = (HBRUSH)COLOR_WINDOW;
    	wcMain.hCursor = LoadCursor(NULL, IDC_ARROW);
    	wcMain.hInstance = hInstance;
    	wcMain.lpszClassName = L"mainWindow";
    	wcMain.lpfnWndProc = MainWindowProcedure;
    
    	assert(RegisterClassW(&wcMain));
    
    	hWnd = CreateWindowW(L"mainWindow", L"Main", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInstance, NULL);
    
    	ShowWindow(hWnd, SW_SHOW);
    	UpdateWindow(hWnd);
    
    	while ( GetMessage(&msg,hWnd,NULL,NULL)){
    		TranslateMessage(&msg);
    		DispatchMessage(&msg);
    	}
    	return 0;
    }
    
    LRESULT CALLBACK MainWindowProcedure(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
    	switch (msg) {
    	case WM_COMMAND:
    		switch (wp) {
    		case ABOUT_MENU_INFO:
    			infoWindow();
    			break;
    		}
    		break;
    	case WM_CREATE:
    		AddMenus(hWnd);
    		break;
    	case WM_DESTROY:
    		PostQuitMessage(0);
    		break;
    	default:
    		return DefWindowProcW(hWnd, msg, wp, lp);
    	}
    	return 0;
    }
    
    LRESULT CALLBACK InfoWindowProcedure(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
    	switch (msg) {
    	case WM_COMMAND:
    		switch (wp) {
    		}
    		break;
    	case WM_CREATE:
    		break;
    	case WM_DESTROY:
    		PostQuitMessage(0);
    		break;
    	default:
    		return DefWindowProcW(hWnd, msg, wp, lp);
    	}
    	return 0;
    }
    
    void AddMenus(HWND hWnd) {
    	hMenu = CreateMenu();
    	HMENU hAboutMenu = CreateMenu();
    
    	AppendMenuW(hAboutMenu, MF_STRING, ABOUT_MENU_INFO, L"Info");
    	AppendMenuW(hMenu, MF_POPUP, (UINT_PTR)hAboutMenu, L"Über");
    
    	SetMenu(hWnd, hMenu);
    }
    
    int infoWindow() {
    	HWND hWnd;
    	WNDCLASSW wcInfo;
    	MSG msg;
    
    	wcInfo = {};
    	wcInfo.hbrBackground = (HBRUSH)COLOR_WINDOW;
    	wcInfo.hCursor = LoadCursor(NULL, IDC_ARROW);
    	wcInfo.lpszClassName = L"infoWindow";
    	wcInfo.lpfnWndProc = InfoWindowProcedure;
    
    	assert(RegisterClassW(&wcInfo));
    
    	hWnd = CreateWindowW(L"infoWindow", L"Info", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, NULL, NULL);
    
    	ShowWindow(hWnd, SW_SHOW);
    	UpdateWindow(hWnd);
    
    	while (GetMessage(&msg, NULL, NULL, NULL)){
    		TranslateMessage(&msg);
    		DispatchMessage(&msg);
    	}
    	return 0;
    }
    

    Ich habe den Code so gut es ging gekürzt.. Ich hoffe ihr versteht, wie ich es meine 🙂



  • Ich habe es nun mit einer globalen Variable gelöst bekommen...
    Das Programm hat sich in einer der beiden While-Schleifen aufgehangen...
    Durch eine weitere Bedingung in den While-Schleifen, kann ich dann beim schließen der Fenster die globale Variable verändern und somit aus den While-Schleifen kommen... Jedoch glaube ich nicht, dass das der reguläre Weg ist..
    Vielleicht erstelle ich die beiden Fenster auch komplett falsch.. Bitte um Hilfe, wie der reguläre Weg ist..



  • Du brauchst eigentlich auch nur eine Message Pump, die in der Main Methode sollte völlig ausreichen. jetzt, wo du schreibst, dass eine while Schleife nicht terminiert ist auch klar, warum nicht. Beide Schleifen arbeiten auf derselben Message Queue, aber nur eine von beiden bearbeitet die WM_QUIT Nachricht und bricht ab. GetMessage entfernt die Nachricht, die sie ausliest, daher kann sie nur von einer der beiden Schleifen behandelt werden.
    Lange Rede, kurzer Sinn: Benutz´ nur eine Message Pump pro Message Queue.

    PS:
    Weil jedes Fenster beim Schließen PostQuitMessage aufruft wird die Anwendung beendet, sobald eins der beiden Fenster geschlossen wird. Ist das so beabsichtigt?



  • Okay, danke.. das ergibt auf jedenfall Sinn. Ich habe noch nicht so ganz den Durchblick..

    • Nein, beabsichtigt ist das nicht. Ist mir allerdings auch nicht aufgefallen, da ich nach dem Schließen (egal welches von den Fenstern), das bestehen bleibende immer noch bedienen kann.

    -> Mein Ziel ist kurz gesagt... Wie bei fast jedem Programm, dass über diesen "Info" Button ein kleines Fenster mit Infos über das Programm erscheint.



  • Kannst du mir bitte erläutern was "Eine Message Bumb pro Message Queue" bedeutet ?
    Ich habe die Begriffe mal gegoogled, verstehe den Hintergrund aber nicht genau



  • Eine Message Pump (oder Message Loop) ist das Verarbeiten der Nachrichten einer Message Queue. Zumindest der Haupthread einer Windows-Anwendung besitzt eine Message Queue, über die das Verhalten der Anwendung und der zugehörigen Fenster gesteuert wird. Diese Message Queue wird durch Benutzerinteraktion (Tastatur, Maus) oder mit vom System erzeugten Nachrichten automatisch befüllt, dazu musst du als Anwender nichts programmieren. Aber um Nachrichten aus der Queue zu entfernen und zu bearbeiten brauchst du eine Message Pump (oder Loop), die oft so aussieht:

    MSG message;
    while( GetMessage( &message, nullptr , 0, 0 ) )
    {
        TranslateMessage( &message );
        DispatchMessage( &message );
    }
    

    Wenn du dir die Doku zu GetMessage ansiehst wirst du sehen, dass nicht alle Situationen abdeckt und der Codeblock richtigerweise so aussehen sollte:

    MSG message;
    while( BOOL success = (GetMessage( &msg, nullptr, 0, 0 ) > 0) )
    { 
       if( success )
       {
          TranslateMessage( &msg ); 
          DispatchMessag( &msg ); 
       }
       else
       {
          // Fehler bei GetMessage oder WM_QUIT empfangen
       }
    }
    

    Wenn du für das Fensterhandle nullptr einsetzt werden alle Nachrichten der Message Queue bearbeitet.

    MSDN Doku zu GetMessage
    MSDN Doku zu Message Queues



  • Das hat mir sehr geholfen. Dank des Tipps mit dem nullptr, funktioniert es super. Danke dir.

    • Nun werden allerdings beide Fenster geschlossen, sobald ich eins schließe, da WM_DESTROY ja für beide Windows gleich gilt. Ich bin mir nicht sicher, aber nun kann ich die Windows ja nicht mehr unterscheiden und unabhängig schließen, oder ?

    Nutze ich aber nicht den nullptr, sondern das WindowHandle, kann ich wiederum nur ein Fenster mit der Message Pump behandeln.. und dadurch ist das Fenster, welches ich nicht mit der Message Pump abarbeite, gefreezed



  • Die MSG Struktur hat ein Feld hwnd, damit kannst du prüfen, ob die Nachricht für dein Fenster bestimmt war oder nicht.

    Ich hab schon ewig nicht mehr sowas von Hand programmiert, bin mir jetzt auch nicht sicher, wie man sowas sauber löst. Du könntest dein Hauptfenster das Infofenster schließen lassen, das könnte auch funktionieren:

        case WM_QUIT:
            DestroyWindow( hInfoWindow );
            HInfoWindow = nullptr;
            ...
            break;
    

    Dazu müsste das hInfoWindow wieder eine globale Variable sein. Ist wohl doch etwas kniffliger, als gedacht.



  • This post is deleted!


  • In den Nachrichtenbehandlungsfunktionen überprüfst du einfach, ob der HWND Parameter mit dem HWND Wert der Nachricht übereinstimmt.



  • @TKuehn Du musst natürlich nicht PostQuitMessage bei beiden Fenstern ausfrufen. Bei dem Info-Fenster würde sich anbieten, dieses mit DestroyWindow zu zerstören.
    Oder man könnte dieses Fenster nur einmal erzeugen und dann nur per ShowWindow ein oder ausblenden (ausblenden dann natürlich als Reaktion im WM_CLOSE-Zweig).
    Typischerweise würde man für einen Infodialog auch kein vollwertiges Fenster erzeugen sondern eben - naja eben einen Dialog.
    Dein GetMessage sollte als HWND auch nullptr (oder NULL) übergeben. Bei einem ungültigen Handle kann GetMessage -1 (obwohl BOOL) zurückliefern. Das wurde vor Jahrzehnten mal geändert und dient eigentlich als gutes Beispiel, dass es nicht immer eine gute Idee ist, nachträglich noch Dinge bei so fundamentalen Sachen zu ändern.



  • @DocShow: Deine Abfrage auf success ist aber falsch (da bei dir -1 auch als erfolgreich angesehen wird).
    Daher auch in der Doku die explizite Abfrage auf -1 bzw. andersherum:

    if ( success > 0)
    

    Oder wenn man auch im Fehlerfall die Schleife beenden will:

    while( GetMessage( &msg, hWnd, 0, 0 ) > 0)
    


  • @Th69
    Autsch, ja.



  • @yahendrik Ohja, danke. Ich wusste nicht, dass es an dem PostQuitMessage(0) liegt.
    Nun funktioniert es, wie ich es mir vorgestellt habe 🙂
    Danke an alle Antworten!


Log in to reply