In Konsolenanwendung CTRL-C behandeln
-
Hallo,
ich habe eine Konsolenanwendung und möchte, dass die Anwendung durch CTRL-C beendet wird. Vorher möchte ich aber eine Abfrage schalten, ob der Benutzer das wirklich möchte.
Das Minimalbeispiel sieht so aus:
#include <cstdio> #include <iostream> #include <limits> #include <windows.h> bool terminate_app = false; bool handler_busy = false; BOOL __stdcall myhandler( DWORD event ) { // if( handler_busy ) return; // handler_busy = true; // if( event == CTRL_C_EVENT ) { // Versuch 1: alle Handler aushängen // ::SetConsoleCtrlHandler( myhandler, TRUE ); // Versuch 2: nur eigenen Handler aushängen // ::SetConsoleCtrlHandler( myhandler, FALSE ); std::cout << "Anwendung beenden (J/N):"; std::cin.ignore( std::numeric_limits<std::streamsize>::max() ); std::string input; std::getline( cin, input ); if( !input.empty() && (input[0] == 'j' || input[0] == 'J' ) ) { terminate_app = true; } // Handler wieder einhängen // ::SetConsoleCtrlHandler( myhandler, TRUE); } // handler_busy = false; return TRUE; } int _tmain( int argc, _TCHAR* argv[] ) { ::SetConsoleCtrlHandler( myhandler, TRUE ); while( !terminate_app ) { ::Sleep( 50 ); } }
Das Ganze funktioniert, so lange ich nur 1x CTRL-C drücke, dann erscheint die Bestätigungsmeldung und wenn ich "J" eingebe beendet sich die Anwendung. Wenn ich jedoch mehrmals schnell hintereinander CTRL-C drücke passieren seltsame Dinge: Ich kann nichts mehr eingeben und die Anwendung lässt sich nicht mehr beenden oder sie beendet sich, ohne dass ich "J" gedrückt habe. Hängt vermutlich damit zusammen, was noch im Eingabestrom steht, obwohl ich den ja in Zeile 24 eigentlich leere.
Ich habe jetzt mehrere Sachen erfolglos ausprobiert:
- durch globale Variable sichergestellt, dass der Handler nur 1x angesprungen wird
- den eigenen Handler aus- und wieder eingehängt
- alle Handler ausgehängt und den eigenen wieder eingehängt
Wie implementiert man sowas richtig?
-
Also ich habe das so gelöst (Visual Studio 2019-Windows 10):
… #include <iostream> #include <csignal> #include <conio.h> bool end = false; void signalCtrlC(int i) { std::cout << "Ctrl-C gedrueckt...[j] für Ende:"; signal( SIGINT, ::signalCtrlC ); char ioch = _getch(); if ( ioch == 'j' ) { end = true; } } int main( int argc, char * argv[] ) { signal( SIGINT, ::signalCtrlC ); while( !::end ); return 0; }
Hab ich jetzt kurz aus dem Kopf abgeschrieben, weil aus der Firma gesendet. Jedenfalls kann ich auf den Tasten [Strg]+[c] stehen.
-
Danke für die Antwort, aber das geht genauso kaputt wie mein Ansatz mit
SetConsoleCtrlHandler
Hier mein Quelltext:
#include <signal.h> #include <cstdio> #include <iostream> #include <conio.h> #include <windows.h> using namespace std; bool stop = false; void signal_handler( int cause ) { cout << "Anwendung beenden (J/N): "; signal( SIGINT, signal_handler ); char ch = _getch(); stop = (ch == 'j' || ch == 'J'); } int _tmain( int argc, _TCHAR* argv[] ) { signal( SIGINT, signal_handler ); while( !stop ) { ::Sleep( 100 ); } return 0; }
-
Kann das nicht kompilieren mit dem:
int _tmain( int argc, _TCHAR* argv[] )
mit
int main( int argc, char * argv[] )
geht es.
Was auffällt, dass ich nur nach jedem zweiten [Ctrl]+[C] die Anzeige "Anwendung beenden (J/N): " bekomme. dann ein 'j' und es bricht ordendlich ab. Wenn keine Anzeige nach dem [Ctrl]+[C] und es wird ein 'j' eingegeben, wird erst nach dem nächsten [Ctrl]+[C] ohne weitere Eingabe abgebrochen.
Ansonsten habe ich kein auffälliges Verhalten festegestellt.
Ich kann auf auf Dauer [Ctrl]+[C] drücken ohne das etwas unvorhergesehenes passiert; ich hab Dein Sleep gegen eincout << ".";
ausgetauscht um zu sehen ob was passiert.
-
@Helmut-Jakoby sagte in In Konsolenanwendung CTRL-C behandeln:
Kann das nicht kompilieren mit dem:
main function and command-line arguments - Microsoft-specific extensions
-
@Helmut-Jakoby
Merkwürdig. Welches OS benutzt du? Ich habe hier Windows 10 64Bit Professional.
Hab das Spielprojekt grad noch mal frisch aufgesetzt, wenn ich 2x CTRL-C drücke erscheint für jeden CTRL-C-Tastendruch die Meldung, aber ich kann nichts eingeben und das Programm lässt sich nicht beenden.
-
Benutzt du denn die Eingabeaufforderung (CMD.exe) oder die Powershell?
-
@DocShoe
Also ich nutze Windows 10 Home Version 20H2 (64 Bit). Visual Studio 2019 Version 16.10.0.Alle Einstellungen kann ich Dir jetzt nicht nennen, aber ggf. wichtig; ISO C++17/-Standard (/std:c++17).
Ich kann folgende Applikation sowohl im Debug- und Release-Modus (CMD) laufen lassen.
!Achtung! Ich sitze am Arbeitsplatz in der Firma und habe das Beispiel von meinem Laptop 'abgeschrieben'.
Da ich hier keinen C++ Compiler habe, konnte ich es nicht testen; es können Schreibfehler vorhanden sein.
Wenn es Probleme beim Übersetzen gibt, helfe ich gerne. Kann ggf. nicht sofort antworten da ja grad auf Arbeit.//Minimalbeispiel #include <csignal> #include <cstdio> #include <iostream> #include <conio.h> #include <algorithm> void signalCtrlC(int i) { signal( SIGINT, ::signalCtrlC ); } int main( int argc, char * argv[] ) { std::string sInput; signal( SIGINT, ::signalCtrlC ); std::cout << "Programm beenden mit [Q]\n"; do { do { sInput.clear(); std::cout << "\nEingabe:"; if ( std::cin.fail() || std::cin.eof() ) { std::cin.clear(); break; } std::cout << "[" << sInput << "]\n"; } while ( sInput != "q" ); { char cIO; std::cout << "Programm jetzt beenden druecke'y'"; cIO = _getche(); sInput = cIO; std::transform( sInput.begin(), sInput.end(), sInput.begin(), ::tolower ); if ( sInput != "y" ) { std::cout << "\nEs wurde nicht 'y' gedrueckt, es geht weiter."; } } } while ( sInput != "y" ); return 0; }
-
Also,
nachdem ich mich weiter damit beschäftigt habe sind mir ein paar Sachen klar geworden. Mit dem CTRL-C Handler lässt sich das nicht lösen und in der Handlerfunktion auf Benutzereingaben zu warten ist eine blöde Idee. Das Drücken von CTRL-C unterbricht auch die Eingaben über
std::in
, was in meinem Programm oben passiert ist Folgendes:- CTRL-C drücken: Handler wird angesprungen und bleibt in Zeile 26 stehen, um die Benutzereingabe zu lesen
- CTRL-C drücken: Die Benutzereingabe wird abgebrochen und der Handler erneut angesprungen. Aus irgendwelchen Gründen kann der Benutzer jetzt aber nichts mehr eingeben.
- nächstes Drücken von CTRL-C: goto 2)
Meine Lösung sieht jetzt so aus, dass ich die Konsole in den Raw-Modus versetze, in dem CTRL-C nicht mehr gefangen wird und auch keinen Handler mehr anspringt. Stattdessen landet das Event als 0x03 im Eingabestrom der Konsole. Den kann ich über
ReadFile
lesen und entsprechend reagieren. Dazu muss ich noch ein paar Konsolenflags bei Bedarf ein- und ausschalten, aber jetzt funktioniert´s genau so, wie ich es haben will:#include <cctype> #include <iostream> #include <windows.h> void enable_console_flag( HANDLE console, DWORD flag ) { DWORD mode = 0; BOOL const r1 = ::GetConsoleMode( console, &mode ); mode |= flag; BOOL const r2 = ::SetConsoleMode( console, mode ); } void disable_console_flag( HANDLE console, DWORD flag ) { DWORD mode = 0; BOOL const r1 = ::GetConsoleMode( console, &mode ); mode &= ~flag; BOOL const r2 = ::SetConsoleMode( console, mode ); } int main() { HANDLE console_in = ::GetStdHandle( STD_INPUT_HANDLE ); HANDLE console_out = ::GetStdHandle( STD_OUTPUT_HANDLE ); disable_console_flag( console_in, ENABLE_PROCESSED_INPUT ); // Konsole in Raw Modus versetzen disable_console_flag( console_in, ENABLE_LINE_INPUT); // Eingaben direkt verarbeiten, ohne auf CR zu warten for( ;; ) { char input_buffer[16]; DWORD bytes_read = 0; ::ReadFile( console_in, input_buffer, sizeof( input_buffer ), &bytes_read, nullptr ); // alle Eingaben außer CTRL-C verwerfen if( bytes_read == 1 && input_buffer[0] == 0x03 ) { std::cout << "Anwendung beenden (J/N)? "; enable_console_flag( console_in, ENABLE_LINE_INPUT ); // Eingabe erst bei CR bearbeiten, aktiviert automatisch ECHO ::ReadFile( console_in, input_buffer, sizeof( input_buffer ), &bytes_read, nullptr ); ::FlushConsoleInputBuffer( console_in ); disable_console_flag( console_in, ENABLE_LINE_INPUT); // zurück zum direkten Eingabemodus std::cout << "\r\n"; if( bytes_read == 2 ) { // nur abbrechen, wenn die beiden Tasten 'j' und <CR> gedrückt wurden char const key1 = std::tolower( input_buffer[0] ); char const key2 = input_buffer[1]; if( key1 == 'j' && key2 == '\r' ) { break; } } } } return 0; }
Danke für eure Hilfe.