WinAPI C ++ client detects write on anonymous pipe before reading

I am writing a C ++ (Windows) client console application that reads from an anonymous pipe to STDIN. I would like to be able to use my program like this:

echo input text here | my_app.exe

      

and do something in the app with the text sent to

OR

my_app.exe

      

and then use the default text inside the app instead of the input from the feed.

I currently have some code that reads successfully from a pipe to STDIN given the first situation:

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

#define BUFSIZE 4096

int main(int argc, const char *argv[]) {
    char char_buffer[BUFSIZE]; 
    DWORD bytes_read;
    HANDLE stdin_handle;
    BOOL continue_reading;
    unsigned int required_size;
    bool read_successful = true;

    stdin_handle = GetStdHandle(STD_INPUT_HANDLE);

    if (stdin_handle == INVALID_HANDLE_VALUE) {
        std::cout << "Error: invalid handle value!\n\n";
    } else {
        continue_reading = true;

        while (continue_reading) { 
            continue_reading = ReadFile(stdin_handle, char_buffer, BUFSIZE,
                &bytes_read, NULL); 

            if (continue_reading) {
                if (bytes_read != 0) {
                    // Output what we have read so far
                    for (unsigned int i = 0; i < bytes_read; i++) {
                        std::cout << char_buffer[i];
                    }
                } else {
                    continue_reading = false;
                }
            }
        }
    }

    return 0;
}

      

I know my only option with anonymous pipes is to make the lock read with ReadFile. If I understand correctly, as far as how I call it, ReadFile will keep reading from the buffer to STDIN until it detects the end of a write operation on the other end of the pipe (perhapse reads some "end of write" token ??). I would like to know if there is some sort of "begin write" marker that will be in the buffer if something is piped in which I can check STDIN BEFORE . I am calling ReadFile. If that were the case, I could just skip the ReadFile call and use the default text.

If I don't, I can always pass a command line argument, which means I shouldn't check the pipe and just use the default text (or vice versa), but I would prefer to do it the way I indicated.

+3


source to share


3 answers


It looks like what you are really trying to do here is to determine if you have console input (where you are using the default) versus pipe input (where you are using input from a pipe).

Suggest testing this directly, rather than trying to check if the input is ready: catch by trying to sniff if there is data in the pipe, if the original application is slowly generating output, your application might make a wrong guess just because the input is not yet available. (It is also possible that, due to typeahead, the user may have been typing characters that were ready to be read from the STDIN console before your application is checked to see if input is available.)

Also, keep in mind that it can be useful to have your application used with file redirects, not just pipes - for example:



myapp.exe < some_input_file

      

The classic way of doing this "interactive mode used with redirected input" on unix is ​​using isatty (); and, fortunately, the Windows CRT has an equivalent - see the _isatty () function ; or use GetFileType () checking FILE_TYPE_CHAR on GetStdHandle (STD_INPUT_HANDLE) - or use GetConsoleMode command as Remy which will only succeed on the real console handle.

+3


source


Take a look PeekNamedPipe()

. Despite its name, it works for both anonymous and anonymous channels.



int main(int argc, const char *argv[])
{
    char char_buffer[BUFSIZE]; 
    DWORD bytes_read;
    DWORD bytes_avail;
    DWORD dw;
    HANDLE stdin_handle;
    bool is_pipe;

    stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
    is_pipe = !GetConsoleMode(stdin_handle, &dw);

    if (stdin_handle == INVALID_HANDLE_VALUE) {
        std::cout << "Error: invalid handle value!\n\n";
    } else {
        while (1) { 
            if (is_pipe) {
                if (PeekNamedPipe(stdin_handle, NULL, 0, NULL, &bytes_avail, NULL)) {
                    if (bytes_avail == 0) {
                        Sleep(100);
                        continue;
                    }
                }
            }

            if (!ReadFile(stdin_handle, char_buffer, min(bytes_avail, BUFSIZE), &bytes_read, NULL)) {
                break; 
            }

            if (bytes_read == 0) {
                break;
            }

            // Output what we have read so far
            for (unsigned int i = 0; i < bytes_read; i++) {
                std::cout << char_buffer[i];
            }
        }
    }

    return 0;
}

      

+5


source


This also works without overlapping I / O by using a second thread that makes a synchronous call to ReadFile. The main thread then waits for an arbitrary amount of time and acts as above ...

Hope it helps ...

0


source







All Articles