What is the proper way to handle input/output from/to the console in Win32? There are a lot of functions out there: GetStdHandle/CreateFile
to get a handle, ReadFile/ReadConsole/ReadConsole(Input/Output)
to read, and WriteFile/WriteConsole/WriteConsole(Input/Output)
to write. There are still more functions with the word Console
in it.
What I want is to do something like printf
. I can print it to the console and redirect it to a file or pipe. I settled down with using GetStdHandle
with ReadFile
and WriteFile
. It works fine, but if I use it simultaneously with normal printf
and output the result to a file, the order is wrong. And even though the document says The handle has GENERIC_READ and GENERIC_WRITE access rights
, if I tried to read from stdout/err
or write to stdin
it will fail.
ReadFile/WriteFile
is fine if you want to output raw bytes - so whatever you'll write, it will be 1:1 written. That means you need to take care of outputting proper encoding. If you output utf8 bytes, then the output will receive utf8 bytes. If you output utf16 bytes, then output will receive utf16 bytes.
If you want windows to take care of encoding for you - use WriteConsoleW
. That will convert utf16 input to correct console encoding (user can set it to whatever they want - chcp
command in console). But this won't work to writing to redirected output to file, it will work only to write for console.
You can detect if output is redirected to file with GetConsoleMode
function and then use WriteConsoleW
if it is not redirected, and WriteFile
when it is redirected.
The reason why printf does not mix with other output functions is because FILE*
based printf is buffered. It does not output immediately, it buffers up to some internal buffer and then outputs everything at once (or when other conditions happen, like line ending). You can call fflush
to force it to output internal buffer, then next call to WriteFile/WriteConsole will be correctly coming after all printf output. Alternatively turn off all buffering - setvbuf(stdout, NULL, _IONBF, 0);
You cannot read from stdout, or write to stdin. stdout/err is only for output (writing), and stdin is for input (reading).
If you output utf8 bytes, then the output will receive utf8 bytes. If you output utf16 bytes, then output will receive utf16 bytes.
Is the reason for caring about the user console's encoding because if I output the wrong encoding, the console will display it incorrectly (that's why I don't need to care about this when outputting to a file)? If my app only outputs ASCII, do I still need to care about this?
If you want windows to take care of encoding for you - use WriteConsoleW
What about ReadConsole
and other Read/WriteConsoleInput/Output
functions?
You cannot read from stdout, or write to stdin. stdout/err is only for output (writing), and stdin is for input (reading).
I thought this was only a convention and not something the OS would enforce. If I change the standard handles using SetStdHandle
would this work?
If you output wrong encoding than user expects, they will see garbage (or you'll receive garbage input if reading input). For ascii only it will work fine, no problems there.
ReadConsoleInput is for different things - that's to get more granular inputs, like your WindowProc gets in GUI program - individual key presses or releases, mouse button & movemenet, console window resize event. You can use to get text input too, but that's just not really needed if you want to just get a text.
WriteConsoleInput is for putting back events inside this buffer, so you can read them later. This is rarely used function.
Both of these functions are kind of obsolete. New way of communicating low-level events to terminal is using VTE sequences - call SetConsoleMode
with ENABLE_VIRTUAL_TERMINAL_INPUT
/ ENABLE_VIRTUAL_TERMINAL_PROCESSING
and then all the events will come as VTE sequences: https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
I have never used SetStdHandle, but to me it sounds like it will just change which values GetStdHandle function returns. But it will not affect original out/in/err handle that parent process passes/creates for child process.
For ascii only it will work fine, no problems there.
What if the terminal expected UTF-16? Isn't UTF-16 not compatible with ASCII, unlike UTF-8?
ReadConsoleInput is for different things - that's to get more granular inputs, like your WindowProc gets in GUI program - individual key presses or releases, mouse button & movemenet, console window resize event. You can use to get text input too, but that's just not really needed if you want to just get a text.
What about the Read/WriteConsoleOutput
and ReadConsole
functions?
But it will not affect original out/in/err handle that parent process passes/creates for child process.
So the reason that ReadFile
failed for stdout/err and WriteFile
failed for stdin is because the original handles that the OS gave you didn't all have read/write permission?
I'm not sure if you can set codepage to utf-16. I think it is only relevant for 8-bit code pages and utf-8.
*ConsoleOutput functions is to specify attributes for each character - meaning background & foreground color. Just look at the arguments - it has a structure for that. But that is kind of deprecated way, same as with *ConsoleInput. Instead you can use VTE sequences for much more capabilities.
OS opens stdin only for reading, and stdout/err for writing. There's no reason to do otherwise. Nobody will be ever writing to stdin, or reading from stdout/err.
The ReadFile
function takes in a buffer, and usually, when I actually read a file I would know the file size. If I use this for stdin, I don't know the size beforehand, so if it surpasses my internal buffer, then how would I handle it?
I think by default ReadFile
stop when you type a \r\n
. How can I also make it stop when it hits an EOF?
Do you also know the typical size for the internal buffer inside printf
and gets
?
ReadFile will not stop at \r\n. It will read up to N bytes you ask, or stop earlier if there is nothing else in input, or will stop if handle is closed. ReadFile() is more like read() function, not fread().
You can call ReadFile multiple times, every time it will give you piece of input without losing anything. Thus you ask it to read smaller amounts of data into your buffer, and parse this buffer until you find your \r\n or whatever. Then use data from buffer. Next time continue reading data into same buffer without discarding what was there (leftovers from previous read).
For MSVC I believe buffer size is 4096. But it can be changed by user to anything with setvbuf function.
ReadFile will not stop at \r\n. It will read up to N bytes you ask, or stop earlier if there is nothing else in input, or will stop if handle is closed. ReadFile() is more like read() function, not fread().
I'm confused. In the ReadFile
document:
By default, the console mode is ENABLE_LINE_INPUT, which indicates that ReadFile should read until it reaches a carriage return
Ah, you're right. It has special behavior when it is reading from console. Just be aware that user can enter file.exe < input.txt
and your stdin will come from file, not console - then it will be not line buffered.
So let's say my internal buffer size was 4 bytes, and the user types something like 8 bytes and press enter, can I still call ReadFile
3 times to the 8 bytes plus the 2-bytes newline? Also, how can I change ReadFile
from stopping at \r\n
to stop at something else?
Simply disable line buffering on input handle and do buffering yourself. Have whatever size buffer. Call ReadFile into it, parse the buffer to find whatever delimiters you want and then use the data from buffer. After that continue calling ReadFile into same buffer by appending data, not overwriting, from last place where you stopped using data.
Then your ReadFile will read full buffer. And rest of the input you'll get on next call to ReadFile. And so on... until last ReadFile call gets to point where nothing else is left on input.
Sorry, I mean that "termination" on newline, not buffering. You change it with SetConsoleMode + ENABLE_LINE_INPUT.
So I've been testing ReadFile
's behavior with other input types like pipes and files and noticed a lot of interesting things:
ReadFile
with the standard input handle and your buffer size is less than 256 bytes, you can still type up to 254 characters (2 bytes for \r\n
), after that, the terminal literally stops you from typing more. If your buffer is bigger than 256 bytes, you can type up to that size minus 2 for \r\n
.stdin
with type foo.txt | main.exe
, ReadFile
only reads up to 4176 characters (including \r\n
) even though your buffer and foo.txt
is way bigger. Weirder is that GetFileSize(Ex)
and PeekNamedPipe
(lpTotalBytesAvail
) also return 4176
bytes. If foo.txt
is smaller than 4176
bytes then those work fine.stdin
with echo a_bunch_of_characters | main.exe
, ReadFile
only reads up to 8148
bytes (so 8146
characters and the remaining 2 bytes are for \r\n
). If a_bunch_of_characters
is bigger than 8146
characters, it just flat-out fails. It doesn't fill up your buffer or anything, it just fails. I know about the terminal's 8191-characters limitation where it literally stops you from typing more, but this is way smaller than that. You can still type more than 8146
characters and below 8191
characters, it just doesn't work.Why do any of these things happen and are there any documents for it?