Skip to content
WindowsDeep Dive Published Updated 4 min readViews unavailable

Windows Console Code Pages: Separate Input, Output, and Redirected Streams

Use the Windows console's independent input and output code pages correctly, and route redirected standard handles through byte-stream I/O instead.

Windows associates two code pages with a console: one for interpreting input and another for displaying byte-oriented output. They are related settings, not a single process-wide “encoding switch.” A program that changes only one can read text correctly but display it incorrectly, or vice versa. A further complication is redirection: when standard input or output refers to a file or pipe rather than a console, console-only APIs are not the right I/O path.

Microsoft’s console code-page documentation maps the responsibilities explicitly. SetConsoleCP and GetConsoleCP control or query the input code page. SetConsoleOutputCP and GetConsoleOutputCP operate on the output code page. The input page translates keyboard input into character values; the output page translates byte character values written by console output functions into glyphs displayed in the console window.

Input and output are independent

Suppose a legacy command-line application uses the console APIs ReadConsoleA and WriteConsoleA and expects UTF-8 bytes. The input path and output path must each match that expectation. If it calls SetConsoleCP(CP_UTF8) but leaves the output code page unchanged, incoming text may decode differently from what the program emits. Setting only SetConsoleOutputCP(CP_UTF8) does not make keyboard input UTF-8. This guidance concerns console-specific conversions; unrelated Win32 A APIs may use other encoding rules. Query the previous values with the matching GetConsoleCP and GetConsoleOutputCP functions if the program needs to restore them later, and check each setter’s Boolean result rather than assuming success.

For new or updated command-line programs, Microsoft recommends Unicode rather than legacy code pages. The W family accepts UTF-16 text for console operations. The documentation says UTF-8 text can be sent through the A family after setting both console input and output code pages to 65001 (CP_UTF8). That recommendation concerns console code-page conversions. It does not magically define the encoding of a redirected file or pipe.

Detect a console before choosing an API

ReadConsole reads from a console input buffer and fails if standard input has been redirected to a non-console handle. WriteConsole writes to a console screen buffer and likewise fails for a redirected standard output handle. Microsoft’s WriteConsole guidance recommends checking whether the handle is a console handle, for example by calling GetConsoleMode and seeing whether it succeeds, then using WriteFile for redirected output. ReadFile can be used with files and pipes; ReadConsole is for a console input-buffer handle.

GetStdHandle may return either NULL or INVALID_HANDLE_VALUE when a standard handle is unavailable. Reject both before probing or reading/writing; they are not redirected streams. A successful GetConsoleMode identifies a console buffer. If that probe fails, do not assume every remaining numeric handle is a valid redirected stream: Microsoft notes that the standard-handle table is not validated by GetStdHandle. Use GetFileType to identify a supported file, pipe, or character handle; treat FILE_TYPE_UNKNOWN as an error, then check the selected byte-I/O call as well.

typedef enum { STREAM_MISSING, STREAM_CONSOLE, STREAM_BYTES, STREAM_ERROR } stream_kind;

static stream_kind classify_stream(HANDLE h)
{
    if (h == NULL || h == INVALID_HANDLE_VALUE)
        return STREAM_MISSING;

    DWORD mode;
    if (GetConsoleMode(h, &mode))
        return STREAM_CONSOLE;
    return GetFileType(h) == FILE_TYPE_UNKNOWN ? STREAM_ERROR : STREAM_BYTES;
}

Use the classification on each handle returned by GetStdHandle, and check the selected I/O call’s result. For a console, WriteConsoleW provides a Unicode path. For a redirected file or pipe, encode text according to the program’s documented stream format and pass the bytes to WriteFile. On input, use ReadConsoleW for console text, but use ReadFile and a deliberate decoder for redirected bytes. This distinction matters for shell pipelines: a file’s byte format is chosen by the producer and consumer contract, not automatically rewritten by the console output code.

Do not assume that changing the console output code page converts every buffer before it reaches any destination. WriteConsole explicitly operates on a console screen buffer, while WriteFile handles a byte stream. Nor should one treat successful output as proof that a chosen font can display every glyph: Microsoft’s SetConsoleOutputCP page notes that raster fonts behave differently from fixed-pitch Unicode fonts for extended characters.

A small validation matrix

Test four cases separately: console input, redirected input, console output, and redirected output. For each, send known non-ASCII test data and compare the received Unicode code points or exact output bytes, not just visual appearance. Test both A and W API paths if maintaining legacy compatibility. A redirected stream should remain byte-for-byte stable regardless of the interactive console’s current output page; a console path should use a documented Unicode or code-page strategy. Check API failures with GetLastError, and do not silently fall back from Unicode to the active OEM page.

This topic is about the classic Windows console and standard handles, not ConPTY’s pseudoconsole protocol. The same principle still helps terminal authors: first determine what kind of handle you have, then select character-oriented console operations or explicit byte-stream I/O. Keeping encoding and transport boundaries separate makes command-line tools predictable when they are used interactively, in a pipeline, or with output redirected to disk.

Related:

Sources:

Comments