The 2024 Wheel Reinvention Jam just concluded. See the results.

How to read from and write to the console in Win32

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?


Replying to mmozeiko (#30175)

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.


Edited by Mārtiņš Možeiko on
Replying to longtran2904 (#30176)

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?


Edited by longtran2904 on
Replying to mmozeiko (#30177)

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.


Edited by Mārtiņš Možeiko on
Replying to longtran2904 (#30178)

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?


Replying to mmozeiko (#30180)

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.


Edited by Mārtiņš Možeiko on
Replying to longtran2904 (#30184)

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


Replying to mmozeiko (#30188)

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.


Replying to longtran2904 (#30189)

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?


Replying to mmozeiko (#30190)

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.


Replying to longtran2904 (#30192)
  1. Before disabling line buffering, I want to know what happens if the user's input is bigger than my buffer (like in my example).
  2. How can I disable line buffering?

Replying to mmozeiko (#30193)

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.


Edited by Mārtiņš Možeiko on
Replying to longtran2904 (#30194)

So I've been testing ReadFile's behavior with other input types like pipes and files and noticed a lot of interesting things:

  1. When 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.
  2. When you pipe 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.
  3. If you pipe 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?


Edited by longtran2904 on
Replying to mmozeiko (#30195)