3

I have a Qt gui application with commandline functionality. To make this work I added this to the top of the main() function:

#ifdef _WIN32
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
    freopen("CONOUT$", "w", stdout);
    freopen("CONIN$", "r", stdin);
    freopen("CONOUT$", "w", stderr);
}
#endif

Then an instance of my main class is constructed. In the constructor, QCommandLineParser determines if there are any arguments, and either creates an instance of the cmdline parsing class, or the gui application class.

In the cmdline parsing class, I ask the user to input certain values:

QString qanswer;

// `answerToInt` is an std::unordered_map
while (answerToInt.find(qanswer) == answerToInt.end()) {
    std::cout << std::endl << "File will be overwritten:" << std::endl
              << path.toStdString() << std::endl
              << "Are you sure? " << (multiple ? "(Yes/YesAll/No/NoAll)" : "(Yes/No)") << std::endl;

    std::string answer;
    std::cin >> answer;
    qanswer = QString::fromStdString(answer).toLower();
    std::cin.clear();
}

When "Yes" "No", "YesAll" or "NoAll" (case insensitive) is input, the program continues as expected, but when the user inputs something other than that, cmd throws this:

'[input]' is not recognized as an internal or external command [...]

And then the "C:\path\to\exe>" is displayed again, where the user can continue to input until one of the correct values is typed. Once a valid string is input, it continues again as expected.

I tried this answer as well as std::getline(), but it makes no difference.

So how do I prevent the error from showing up and continue displaying the cout?

bur
  • 604
  • 5
  • 20
  • This looks like QString isn't able to parse `answer` from a glance. Headed into an appointment though, couldn't read too far into it. I'd start there, and include your inputs and expected outputs in the question – Rogue May 01 '20 at 16:02
  • I edited the question a bit, hope it's clearer now. When a valid value is input it works correctly as expected, so I don't think it's a problem with QString. I suspect it's because it's a GUI application, where normal cmd functionality doesn't work, and `freopen`s aren't enough to enable it. – bur May 01 '20 at 16:24

1 Answers1

2

AttachConsole just attaches to the parent process's console, it does not stop the parent process from also reading from it. So the console input is being interleaved between the parent process (cmd.exe) and your app, which can be problematic to manage (some people advice killing the parent process, which obviously isn't a good idea).

What you can do instead is to always create a new console (see AllocConsole).

Or if you want to re-use the same console, it might be an idea to target the Console subsystem instead (linker option /SUBSYSTEM:CONSOLE) and have a regular main() function instead of WinMain (yes you can create Win32 windows and handle console I/O all within main()).

You can even have a multi-subsystem source that can be linked as Windows as well as Console subsystem with a shim like this (nCmdShow and command-line arguments remain to be implemented):

HWND hwnd;

int main() {
    std::thread t([] {
        // let GUI run in its own thread ...
        WinMain(GetModuleHandle(NULL), NULL, "", SW_SHOWDEFAULT);
        exit(0);
    });
    // meanwhile in this thread we handle console I/O ...
    std::string s;
    std::cout << "Press Enter to exit" << std::endl;
    while (std::getline(std::cin, s)) {
        if (s == "")
            break;
        std::cout << "Hello " << s << std::endl;
    }
    PostMessageA(hwnd, WM_CLOSE, 0, 0);
    t.join();
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
     // Your normal WinMain.
     // CreateWindow, GetMessage loop etc. . .
rustyx
  • 80,671
  • 25
  • 200
  • 267
  • Using `AllocConsole` seems like the best solution here. Is there a way to close the first console window? – bur May 01 '20 at 16:53
  • Windows users typically don't start programs from the console. You could check if the parent is `cmd.exe` and kill it... Personally though I would prefer if my console remained open. Some projects ship two executables - `foo.exe` for GUI and `fooc.exe` for console mode, so that the user can choose which one they want. – rustyx May 01 '20 at 17:02
  • I suppose you're right, the user might not want the console to close. I considered splitting it into two .exe, but I prefer to keep them unified for now. Thanks for the help! – bur May 01 '20 at 17:07