1

I'm following the example from a another post here Reading a password from std::cin where you grab the handle to standard input, get the current console mode, change the mode to stifle echo on input. For some reason, when i call GetStdHandle() it returns a valid handle, but when I invoke GetConsoleMode() it fails and returns error code 6. I'm using this in a cmake project. Are there any debug flags I am supposed to set for this to work as intended? Anyone else encountered this?

void set_stdin_echo(bool enable) {
    HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
    if (hStdin == INVALID_HANDLE_VALUE) {
        std::cout << "invalid handle " << GetLastError() << std::endl;
        return;
    }
    else if (hStdin == NULL) {
        std::cout << "no handle associated" << std::endl;
        return;
    }

    DWORD mode = 0;
    if (GetConsoleMode(hStdin, &mode) == 0) {
        std::cout << "Could not get console mode" << GetLastError() << std::endl;
    }

    std::cout << mode << std::endl;
    if (!enable)
        mode &= ~ENABLE_ECHO_INPUT;
    else
        mode |= ENABLE_ECHO_INPUT;
    std::cout << mode << std::endl;

    if (SetConsoleMode(hStdin, mode) == 0) {
        std::cout << "Could not set input mode" << std::endl;
        std::cout << GetLastError() << std::endl;
    }
}

EDIT: reproduced with this code.

CMakeLists.txt

cmake_minimum_required (VERSION 2.6)
project(tester)

if(CMAKE_COMPILER_IS_GNUCXX)
    set(CMAKE_CXX_FLAGS "-g -w -Wall -pedantic-errors -std=c++11")
elseif(MSVC)
    set(CMAKE_CXX_FLAGS "/EHsc")
    set(CMAKE_CXX_FLAGS_DEBUG "/EHsc /MTd")
    set(CMAKE_CXX_FLAGS_RELEASE "/EHsc /MT")
endif()

set(SOURCE_FILES "main.cpp")

add_executable(tester ${SOURCE_FILES})

main.cpp

#include <iostream>
#include <string>
#include <windows.h>

void set_stdin_echo(bool enable) {
    DWORD error;
    HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
    error = GetLastError();
    if (hStdin == INVALID_HANDLE_VALUE) {
        std::cout << "invalid handle " << error << std::endl;
        return;
    }
    else if (hStdin == NULL) {
        std::cout << "no handle associated" << std::endl;
        return;
    }

    DWORD mode = 0;
    if (GetConsoleMode(hStdin, &mode) == 0) {
        error = GetLastError();
        std::cout << "Could not get console mode" << error << std::endl;
    }

    std::cout << mode << std::endl;
    if (!enable)
        mode &= ~ENABLE_ECHO_INPUT;
    else
        mode |= ENABLE_ECHO_INPUT;
    std::cout << mode << std::endl;

    if (SetConsoleMode(hStdin, mode) == 0) {
        error = GetLastError();
        std::cout << "Could not set input mode" << std::endl;
        std::cout << error << std::endl;
    }
}

int main() {
    std::string input;
    std::cout << "password: ";
    set_stdin_echo(false);
    std::cin >> input;
    set_stdin_echo(true);

    return 0;
}

Visual Studio 12. Invoke these in some directory. I get "Could not get console mode 6", implying GetLastError() returns 6.

mkdir build
cmake ..
msbuild.exe tester.sln
./Debug/tester.exe

EDIT: It appears that this only fails when run from the command line. Within visual studio from the visual studio debugger, it succeeds. Still not enough knowledge of visual studio to determine what is going on. I just want to make a simple exe that can run from the command line and hide its input.

EDIT: cleanliness and also obtaining GetLastError asap after calls. Still no change.

Community
  • 1
  • 1
FatalCatharsis
  • 3,407
  • 5
  • 44
  • 74
  • How can we reproduce this? – David Heffernan Nov 02 '15 at 11:21
  • 3
    You are calling `GetLastError` too late. The stream insertion operator (`operator<<`) could change the last error code behind your back. You need to call `GetLastError` **immediately** after a Windows API call fails (if the call does set the last error code). – IInspectable Nov 02 '15 at 11:29
  • how can i print to console the result of GetLastError without the insertion operator? – FatalCatharsis Nov 02 '15 at 11:48
  • @DavidHeffernan - added code that reproduces the results, (at least for me). – FatalCatharsis Nov 02 '15 at 11:49
  • Off site links are no good. Why should I download multiple files? You should be able to make a 20 line complete program. – David Heffernan Nov 02 '15 at 11:51
  • Well that's the thing, I can only reproduce the error, when there are multiple files. One sec, i'll put their bodies in the question. – FatalCatharsis Nov 02 '15 at 11:52
  • You don't have to write results to the console. [OutputDebugString](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363362.aspx) is a less intrusive alternative. If you wish to write debug information to the console, simply don't interleave the call to `GetLastError` with stream output. – IInspectable Nov 02 '15 at 11:52
  • Alrighty, I'm getting to the root of the issue here. I'm not a fan of visual studio, so i never open it. I do most of my building with msbuild.exe. Just because I was curious, I opened up the generated sln with visual studio and ran it with the visual studio debugger. It worked exactly as expected. However, If I run the exe from command line, It gives this error. So now i guess the question is, how can i get the expected functionality using command line. – FatalCatharsis Nov 02 '15 at 12:11
  • 2
    We cannot help you, if you refuse to report the error code you get. You need to call `GetLastError` **immediately** (***immediately***) after a Windows API call fails. Do not call **anything** in between. – IInspectable Nov 02 '15 at 12:30
  • It might have something to do with the difference of having a console created just for your application, as happens when Visual Studio runs it, versus you running it from an existing console, as with msbuild. It could be that console ownership gets in the way. If the `GetLastError` is truly 6, then this is `ERROR_INVALID_HANDLE`, which could mean to be here that the stdinput handle is not that of a console, but of a pipe. – MicroVirus Nov 02 '15 at 12:30
  • @IInspectable - Sorry, I should elaborate. OutputDebugString prints to the internal visual studio debug console. I am trying to run from command line without the help of the visual studio debugger. Within the visual studio debugger, the program works fine. From command line, it fails where i cannot print to the screen without use of the stream operator or c style print functions. – FatalCatharsis Nov 02 '15 at 12:39
  • Visual Studio is **one** tool, that can display the output of `OutputDebugString`. [DebugView](https://technet.microsoft.com/en-us/sysinternals/bb896647.aspx) is another tool. But that is all besides the point, and I don't know how to put it any more direct, comprehensible, or clear: ***Do not call anything in between a Windows API call failing and `GetLastError`.*** – IInspectable Nov 02 '15 at 13:21
  • @IInspectable - cleaned up code and obtained GetLastError before calls to cout. No change. – FatalCatharsis Nov 02 '15 at 13:34
  • Cannot reproduce here. What console are you using? – David Heffernan Nov 02 '15 at 14:20
  • @DavidHeffernan - shoot, I didn't even think about it. A long while ago i set up cygwin/minnty. I pulled up a classic command prompt and it executed as expected. So the issue arises from trying to get the standard input from cygwin? Any easy way to achieve the functionality i'm looking for without being shell specific? (if i should spin up a seperate question say the word). – FatalCatharsis Nov 02 '15 at 14:25
  • 1
    *Any easy way to achieve the functionality i'm looking for without being shell specific?* No. – David Heffernan Nov 02 '15 at 14:26
  • I get error 6 if the input is not the console, i.e. if it is redirected. For example "type | tester.exe". How are you running the program? – Ben Nov 02 '15 at 14:31
  • @Ben He's using a non-standard console, cygwin – David Heffernan Nov 02 '15 at 14:34
  • Don't accept an answer, that doesn't address the problem. Future visitors will use the indicator for an accepted answer as a hint, only to find out, that it doesn't solve their issue. You can still upvote an answer, if it adds useful information. There's nothing wrong with leaving a question open. This also places it in the *"unanswered questions"* list, that some members use, to browse through questions. That'll make getting an answer more likely, too. – IInspectable Nov 05 '15 at 10:49

1 Answers1

5

This error 6 means "invalid handle" and means that the handle you have passed to GetConsoleMode is not a console handle. I.e. the STDIN has been redirected from somewhere.

To ensure you have the handle to the current console, regardless of whether STDIN is redirected from somewhere else, use CreateFile with the special name "CONIN$", which opens the console (if present).

Example:

HANDLE hConsole = CreateFile("CONIN$",  
    GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 
    NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
Ben
  • 34,935
  • 6
  • 74
  • 113