You may say.
//#DEFINE UNICODE
#include <Windows.h>
int main()
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
LPTSTR msg = TEXT("Hello, World!\n");
WriteConsole(hOut, msg, lstrlen(msg), NULL, NULL);
}
This works, as long as STDOUT
is not redirected, as in main > tmp.txt
. Then, WriteConsole()
errors, with the format message "The handle is invalid.". Then, WriteFile()
should work, so you may say.
WriteFile(hOut, msg, lstrlen(msg), NULL, NULL);
This is great, except if 1) STDOUT
is not redirected, and 2) UNICODE is set. Then it would print H e l l o ,
, because apparently WriteFile()
does not "translate" the characters, it just sends the bytes. Maybe this can be solved by changing the DOS codepage, but it would need to be UTF-16, since Windows calls UTF-16 "UNICODE". On my computer, the codepages for UTF-16 are not available, so I would not rely on it.
The solution I found is to identify if the "file type" of the handle.
if (GetFileType(hOut) == FILE_TYPE_CHAR)
WriteConsole(hOut, msg, lstrlen(msg), NULL, NULL);
else
WriteFile(hOut, msg, lstrlen(msg) * sizeof(TCHAR), NULL, NULL);
Still, is this the proper solution? And if possible, what is the proper way of reading STDIN
, since it suffers from a similar problem?
console handle is file handle (begin form win7 if i not mistake) and we always can use WriteFile
for write to it, redirected it or not. however question here - in which format must be data for WriteFile
. in case we write to console - output is handle in conhost.exe process. and he assume that data in multibyte format and use MultiByteToWideChar
function for convert data to UTF-16 (wide character) string. in place CodePage is used value returned from GetConsoleOutputCP
. we also can change this value by SetConsoleOutputCP
. say set SetConsoleOutputCP(CP_UTF8);
and pass utf-8 data to WriteFile
. in case std handle (out/err) isconsole handle we also can use WriteConsoleW
function. for output just in UTF-16 format. from server side (conhost.exe), when we call WriteFile
or WriteConsole[A/W]
- ApiDispatchers::ServerWriteConsole
called, which call ApiRoutines::WriteConsoleAImpl
(in case WriteFile
or WriteConsoleA
) or ApiRoutines::WriteConsoleWImpl -> WriteConsoleWImplHelper
in case WriteConsoleW
. and ApiRoutines::WriteConsoleAImpl
use MultiByteToWideChar(GetConsoleOutputCP(), ..)
for convert data to UTF-16 and then call WriteConsoleWImplHelper
too of course. so in case console device - bit better use WriteConsoleW
for avoid double conversion - from UTF-16 to multibyte in our process (optional, we just output in multibyte with SetConsoleOutputCP
) and multibyte to UTF-16 in conhost (always). so code can be next:
static HANDLE _G_hFile = 0;
static BOOLEAN _G_bConsole = FALSE;
static UINT _G_CodePage;
inline void PutChars(PCWSTR pwz)
{
PutChars(pwz, (ULONG)wcslen(pwz));
}
void PutChars(PCWSTR pwz, ULONG cch)
{
if (!_G_hFile)
{
return ;
}
if (_G_bConsole)
{
WriteConsoleW(_G_hFile, pwz, cch, &cch, 0);
return ;
}
PSTR buf = 0;
ULONG len = 0;
while (len = WideCharToMultiByte(_G_CodePage, 0, pwz, cch, buf, len, 0, 0))
{
if (buf)
{
WriteFile(_G_hFile, buf, len, &len, 0);
break;
}
if (!(buf = (PSTR)_malloca_s(len)))
{
break;
}
}
if (buf)
{
_freea_s(buf);
}
}
#define _malloca_s(size) ((size) < _ALLOCA_S_THRESHOLD ? alloca(size) : new BYTE[size])
inline void _freea_s(PVOID pv)
{
PNT_TIB tib = (PNT_TIB)NtCurrentTeb();
if (pv < tib->StackLimit || tib->StackBase <= pv) delete [] pv;
}
now question is how detect are std ouput point to console device. say if look to cmd.exeimplementation, it have next internal function
BOOL FileIsConsole(int fd );
fd passed to function hFile = _get_osfhandle
and then used GetFileType(hFile) == FILE_TYPE_CHAR
for detect are console or not: assume that console if GetFileType
returned FILE_TYPE_CHAR
.
then it used in next way
int CmdPutChars(PCWSTR psz, int cch)
{
FileIsConsole(1) ? WriteConsoleW(..psz, cch) : MyWriteFile(..psz, cch..);
}
implementation of GetFileType
is look like:
DWORD WINAPI GetFileType(_In_ HANDLE hFile)
{
switch ((ULONG_PTR)hFile)
{
case 0:
RtlNtStatusToDosError(STATUS_INVALID_HANDLE);
return FILE_TYPE_UNKNOWN;
case STD_ERROR_HANDLE:
case STD_OUTPUT_HANDLE:
case STD_INPUT_HANDLE:
hFile = GetStdHandle((ULONG)(ULONG_PTR)hFile);
break;
}
IO_STATUS_BLOCK iosb;
FILE_FS_DEVICE_INFORMATION ffdi;
NTSTATUS status = NtQueryVolumeInformationFile(hFile, &iosb, &ffdi, sizeof(ffdi), FileFsDeviceInformation);
if (0 > status)
{
RtlNtStatusToDosError(status);
return FILE_TYPE_UNKNOWN;
}
switch (ffdi.DeviceType)
{
case FILE_DEVICE_KEYBOARD:
case FILE_DEVICE_MOUSE:
case FILE_DEVICE_NULL:
case FILE_DEVICE_PARALLEL_PORT:
case FILE_DEVICE_PRINTER:
case FILE_DEVICE_SERIAL_PORT:
case FILE_DEVICE_SCREEN:
case FILE_DEVICE_SOUND:
case FILE_DEVICE_MODEM:
case FILE_DEVICE_CONSOLE:
return FILE_TYPE_CHAR;
case FILE_DEVICE_NAMED_PIPE:
return FILE_TYPE_PIPE;
case FILE_DEVICE_CD_ROM:
case FILE_DEVICE_CD_ROM_FILE_SYSTEM:
case FILE_DEVICE_CONTROLLER:
case FILE_DEVICE_DATALINK:
case FILE_DEVICE_DFS:
case FILE_DEVICE_DISK:
case FILE_DEVICE_DISK_FILE_SYSTEM:
case FILE_DEVICE_VIRTUAL_DISK:
return FILE_TYPE_DISK;
}
SetLastError(NOERROR);
return FILE_TYPE_UNKNOWN;
}
so FILE_TYPE_CHAR
returned not only for FILE_DEVICE_CONSOLE
but for several other devices too (like FILE_DEVICE_NULL
etc). this of course not real problem - say if our output redirected to null device and we detect it as console - WriteConsole
of course retirn error, but and WriteFile
also no any effect will be, despite no error. in any case for be more exactly can use next code:
void InitPrintf()
{
_G_CodePage = GetConsoleOutputCP();
if (_G_hFile = GetStdHandle(STD_OUTPUT_HANDLE))
{
FILE_FS_DEVICE_INFORMATION ffdi;
IO_STATUS_BLOCK iosb;
if (0 <= NtQueryVolumeInformationFile(_G_hFile, &iosb, &ffdi, sizeof(ffdi), FileFsDeviceInformation))
{
switch (ffdi.DeviceType)
{
case FILE_DEVICE_CONSOLE:
_G_bConsole = TRUE;
break;
}
}
}
}
also can use next util for easy formated print
void PutChars_v(PCWSTR format, ...)
{
va_list ap;
va_start(ap, format);
PWSTR buf = 0;
int len = 0;
while (0 < (len = _vsnwprintf(buf, len, format, ap)))
{
if (buf)
{
PutChars(buf, len);
break;
}
++len;
if (!(buf = (PWSTR)_malloca_s(len * sizeof(WCHAR))))
{
break;
}
}
if (buf)
{
_freea_s(buf);
}
va_end(ap);
}