From 04e6ebcc122638ffc467838a27abf0001307ece1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sun, 12 Apr 2015 11:54:50 +0200 Subject: [PATCH] Add new version of WINAPI code for attaching console --- CMakeLists.txt | 3 +- src/include/SetupConsole_win32.h | 32 +++++ src/stratagus/stratagus.cpp | 7 + src/win32/SetupConsole_win32.cpp | 230 +++++++++++++++++++++++++++++++ src/win32/attachconsole.cpp | 225 ------------------------------ 5 files changed, 271 insertions(+), 226 deletions(-) create mode 100644 src/include/SetupConsole_win32.h create mode 100644 src/win32/SetupConsole_win32.cpp delete mode 100644 src/win32/attachconsole.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fc7fafa3e..919be159d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -354,7 +354,7 @@ set(video_SRCS source_group(video FILES ${video_SRCS}) set(win32_SRCS - src/win32/attachconsole.cpp + src/win32/SetupConsole_win32.cpp src/win32/stratagus.rc ) source_group(win32 FILES ${win32_SRCS}) @@ -584,6 +584,7 @@ set(stratagus_generic_HDRS src/include/viewport.h src/include/wav.h src/include/widgets.h + src/include/SetupConsole_win32.h ) diff --git a/src/include/SetupConsole_win32.h b/src/include/SetupConsole_win32.h new file mode 100644 index 000000000..ec5db0437 --- /dev/null +++ b/src/include/SetupConsole_win32.h @@ -0,0 +1,32 @@ +/* + Copyright (C) 2015 Pali Rohár + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifndef SETUPCONSOLE_WIN32_H +#define SETUPCONSOLE_WIN32_H + +/* Returns true if standard output points to console */ +bool IsStdoutConsole(); + +/* Returns true if standard error points to console */ +bool IsStderrConsole(); + +/* Attach console from parent process and setup console standard input, output and error */ +/* Must be called before any console I/O, ideally as first call in main() */ +void SetupConsole(); + +#endif diff --git a/src/stratagus/stratagus.cpp b/src/stratagus/stratagus.cpp index 06b84c3b4..e2a9e75e6 100644 --- a/src/stratagus/stratagus.cpp +++ b/src/stratagus/stratagus.cpp @@ -227,6 +227,10 @@ extern void beos_init(int argc, char **argv); #define REDIRECT_OUTPUT #endif +#if defined(USE_WIN32) && ! defined(REDIRECT_OUTPUT) +#include "SetupConsole_win32.h" +#endif + /*---------------------------------------------------------------------------- -- Variables ----------------------------------------------------------------------------*/ @@ -696,6 +700,9 @@ int stratagusMain(int argc, char **argv) #endif #ifdef USE_WIN32 SetUnhandledExceptionFilter(CreateDumpFile); +#endif +#if defined(USE_WIN32) && ! defined(REDIRECT_OUTPUT) + SetupConsole(); #endif // Setup some defaults. #ifndef MAC_BUNDLE diff --git a/src/win32/SetupConsole_win32.cpp b/src/win32/SetupConsole_win32.cpp new file mode 100644 index 000000000..0c3a6585a --- /dev/null +++ b/src/win32/SetupConsole_win32.cpp @@ -0,0 +1,230 @@ +/* + Copyright (C) 2015 Pali Rohár + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +/* _WIN32_WINNT must be set to at least 0x0501 */ +#ifdef _WIN32_WINNT +#if _WIN32_WINNT < 0x0501 +#undef _WIN32_WINNT +#endif +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#endif + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +#include +#endif + +#include "SetupConsole_win32.h" + +/* + * Microsoft systems were and always are one big mess where even standard output does not work :-( + * There are at least 4 different I/O API levels and each WINAPI function initilize correctly just one... + * For example standard output information is stored in: DWORD STD_OUTPUT_HANDLE, std::istream std::cout, FILE* stdout, int fd (value 1) + * This code tries to use console window of parent process and properly initialize all I/O API levels and make sure they are not out of sync + */ + +bool IsHandleConsole(HANDLE handle) { + + if (!handle || handle == INVALID_HANDLE_VALUE) + return false; + + /* GetConsoleMode() returns false on non console handlers */ + DWORD mode; + return GetConsoleMode(handle, &mode); + +} + +bool IsStdoutConsole() { + + return IsHandleConsole(GetStdHandle(STD_OUTPUT_HANDLE)); + +} + +bool IsStderrConsole() { + + return IsHandleConsole(GetStdHandle(STD_ERROR_HANDLE)); + +} + +bool IsHandleValid(HANDLE handle) { + + if (!handle || handle == INVALID_HANDLE_VALUE) + return false; + + DWORD type = GetFileType(handle); + DWORD error = GetLastError(); + + /* When error occure GetFileType() returns FILE_TYPE_UNKNOWN and GetLastError() returns error code */ + return (type != FILE_TYPE_UNKNOWN || error == NO_ERROR); + +} + +bool IsStreamValid(FILE *stream) { + + return _fileno(stream) >= 0; + +} + +bool IsFdValid(int fd) { + + /* Function _isatty returns non zero value when parameter is a terminal, console, printer, or serial port */ + /* When parameter is bad file descriptor, then zero is returned and errno is set to EBADF */ + errno = 0; + return (_isatty(fd) != 0 || errno != EBADF); + +} + +bool SetStreamAndFdFromStdConsole(FILE *stream, int fd, DWORD std, bool input) { + + /* Retrieves a handle to the standard device of active console screen */ + HANDLE handle = GetStdHandle(std); + if (!handle || handle == INVALID_HANDLE_VALUE) + return false; + + /* Allocates new file descriptor and associates it with handle */ + int fd_new = _open_osfhandle((intptr_t)handle, _O_TEXT | (input ? _O_RDONLY : _O_APPEND)); + if (fd_new < 0) + return false; + + /* Retrieves existing file descriptor from FILE* stream */ + int fd_old = _fileno(stream); + if (fd_old >= 0 && fd_old != fd_new) { + /* Reassigns new file descriptor to existing one from FILE* stream */ + if (_dup2(fd_new, fd_old) < 0) { + _close(fd_new); + return false; + } + } + + if (fd_new != fd) { + /* Reassigns new file descriptor to one provided by function arg */ + if (_dup2(fd_new, fd) < 0) { + _close(fd_new); + return false; + } + } + + /* Clean up */ + if (fd_old < 0 || fd_old != fd_new) + _close(fd_new); + + /* Allocates new FILE* stream and associates it with file descriptor */ + FILE *stream_new = _fdopen(fd, (input ? "r" : "a")); + if (!stream_new) { + _close(fd); + return false; + } + + /* Close old FILE* stream */ + fclose(stream); + + /* Set new FILE* stream */ + *stream = *stream_new; + + /* Disable bufferring */ + setvbuf(stream, NULL, _IONBF, 0); + + return true; + +} + +void SetupConsole() { + + /* Get current handlers */ + HANDLE inputHandle = GetStdHandle(STD_INPUT_HANDLE); + HANDLE outputHandle = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE errorHandle = GetStdHandle(STD_ERROR_HANDLE); + + bool stdin_ok = IsHandleValid(inputHandle); + bool stdout_ok = IsHandleValid(outputHandle); + bool stderr_ok = IsHandleValid(errorHandle); + + /* If stdin, stdout and stderr is valid we do not need to do anything */ + if (stdin_ok && stdout_ok && stderr_ok) + return; + + /* Attaches the calling process to the console of parent process */ + /* After successfull call STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and STD_ERROR_HANDLE are changed to new console screen */ + if (!AttachConsole(ATTACH_PARENT_PROCESS)) + return; + + /* If console handlers were valid before AttachConsole() call, set handlers back (we do not want to overwrite them) */ + if (stdin_ok) + SetStdHandle(STD_INPUT_HANDLE, inputHandle); + if (stdout_ok) + SetStdHandle(STD_OUTPUT_HANDLE, outputHandle); + if (stderr_ok) + SetStdHandle(STD_ERROR_HANDLE, errorHandle); + + /* And now STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and STD_ERROR_HANDLE are valid and set correctly */ + + stdin_ok = (IsStreamValid(stdin) && IsFdValid(0)); + stdout_ok = (IsStreamValid(stdout) && IsFdValid(1)); + stderr_ok = (IsStreamValid(stderr) && IsFdValid(2)); + + bool stdin_console = false; + bool stdout_console = false; + bool stderr_console = false; + + /* Try to set standard C streams (stdin/stdout/stderr) and fds (0/1/2) from active console screen (STD_INPUT_HANDLE/STD_OUTPUT_HANDLE/STD_ERROR_HANDLE) */ + if (!stdin_ok) + stdin_console = SetStreamAndFdFromStdConsole(stdin, 0, STD_INPUT_HANDLE, true); + if (!stdout_ok) + stdout_console = SetStreamAndFdFromStdConsole(stdout, 1, STD_OUTPUT_HANDLE, false); + if (!stderr_ok) + stderr_console = SetStreamAndFdFromStdConsole(stderr, 2, STD_ERROR_HANDLE, false); + + /* If everything failed then detach console and try to restore original console handlers... we cannot do more */ + if (!stdin_console && !stdout_console && !stderr_console) { + FreeConsole(); + SetStdHandle(STD_INPUT_HANDLE, inputHandle); + SetStdHandle(STD_OUTPUT_HANDLE, outputHandle); + SetStdHandle(STD_ERROR_HANDLE, errorHandle); + return; + } + +#ifdef __cplusplus + /* Clear C++ std buffers */ + if (stdin_console) + std::cin.clear(); + if (stdout_console) + std::cout.clear(); + if (stderr_console) + std::cerr.clear(); + + /* Synchronise C streams with C++ iostream */ + std::ios::sync_with_stdio(); +#endif + + /* Write two empty lines to prevent overwriting command prompt in cmd.exe */ + if (stdout_console) { + fprintf(stdout, "\n\n"); + fflush(stdout); + } else if (stderr_console) { + fprintf(stderr, "\n\n"); + fflush(stderr); + } + +} diff --git a/src/win32/attachconsole.cpp b/src/win32/attachconsole.cpp deleted file mode 100644 index 1c12e3591..000000000 --- a/src/win32/attachconsole.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/* - attachconsole.cpp - WINAPI AttachConsole - Copyright (C) 2009-2011 Pali Rohár - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -*/ - -#if ( defined(WIN32) || defined(_MSC_VER) ) && ( defined(NO_STDIO_REDIRECT) || ! defined(REDIRECT_OUTPUT) ) - -#include -#include -#include -#include - -#ifdef __cplusplus -#include -#endif - -#ifndef _WIN32_WINNT_WIN2K -#define _WIN32_WINNT_WIN2K 0x0500 -#endif - -static int fixmode = 0; - -static HINSTANCE lib_kernel32 = NULL; -static HINSTANCE lib_ntdll = NULL; - -typedef int FAR WINAPI proto_AttachConsole(int); -typedef int FAR WINAPI proto_NtQueryObject(HANDLE, int, void *, unsigned long int, unsigned long int *); - -static proto_AttachConsole *func_AttachConsole = NULL; -static proto_NtQueryObject *func_NtQueryObject = NULL; - -/// Check if HANDLE is attached to console -static int WINAPI_CheckIfConsoleHandle(HANDLE handle) -{ - - wchar_t filename[MAX_PATH]; - unsigned long int length = 0; - - // Try to get filename of HANDLE - if (func_NtQueryObject) { - func_NtQueryObject(handle, 1, filename, MAX_PATH, &length); - } - - // Filename start at position 8 - if (length > 8) { - return 0; - } else { - return 1; - } - -} - -/// Try to reopen FILE* from WINAPI HANDLE -static void WINAPI_ReopenFileFromHandle(HANDLE handle, FILE *file, const char *mode) -{ - - int fd; - FILE *newfile; - - if (! handle || handle == INVALID_HANDLE_VALUE) { - return; - } - - // Get file descriptor from HANDLE - fd = _open_osfhandle((intptr_t)handle, O_TEXT); - - if (fd < 0) { - return; - } - - // Get C structure FILE* from file descriptior - newfile = _fdopen(fd, mode); - - if (! newfile) { - return; - } - - // Close current file - fclose(file); - - // Set new file from HANDLE - *file = *newfile; - - setvbuf(file, NULL, _IONBF, 0); - - // If stdout/stderr write 2 empty lines to cmd console - if (! fixmode && strcmp(mode, "w") == 0) { - - fprintf(file, "\n\n"); - fixmode = 1; - - } - -} - -/// Try to set std HANDLE from FILE* -static void WINAPI_SetStdHandleFromFile(int type, FILE *file) -{ - - int fd; - HANDLE handle; - - fd = _fileno(file); - - if (fd < 0) { - return; - } - - handle = (HANDLE)_get_osfhandle(fd); - - if (! handle || handle == INVALID_HANDLE_VALUE) { - return; - } - - SetStdHandle(type, handle); - -} - -/// Try attach console of parent process for std input/output in Windows NT, 2000, XP or new -static void WINAPI_AttachConsole(void) -{ - - OSVERSIONINFO osvi; - int hasVersion; - int version; - int attached; - int reopen_stdin; - int reopen_stdout; - int reopen_stderr; - - ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - - hasVersion = GetVersionEx(&osvi); - - if (! hasVersion) { - return; - } - - version = (osvi.dwMajorVersion << 8) | osvi.dwMinorVersion; - - // We need Windows 2000 or new - if (version < _WIN32_WINNT_WIN2K) { - return; - } - - lib_kernel32 = LoadLibrary("kernel32.dll"); - - if (! lib_kernel32) { - return; - } - - func_AttachConsole = (proto_AttachConsole *)GetProcAddress(lib_kernel32, "AttachConsole"); - - if (! func_AttachConsole) { - return; - } - - lib_ntdll = LoadLibrary("ntdll.dll"); - - if (lib_ntdll) { - func_NtQueryObject = (proto_NtQueryObject *)GetProcAddress(lib_ntdll, "NtQueryObject"); - } - - // Ignore if HANDLE is not attached console - reopen_stdin = WINAPI_CheckIfConsoleHandle(GetStdHandle(STD_INPUT_HANDLE)); - reopen_stdout = WINAPI_CheckIfConsoleHandle(GetStdHandle(STD_OUTPUT_HANDLE)); - reopen_stderr = WINAPI_CheckIfConsoleHandle(GetStdHandle(STD_ERROR_HANDLE)); - - attached = func_AttachConsole(-1); - - if (! attached) { - return; - } - - if (reopen_stdin) { - WINAPI_ReopenFileFromHandle(GetStdHandle(STD_INPUT_HANDLE), stdin, "r"); - } else { - WINAPI_SetStdHandleFromFile(STD_INPUT_HANDLE, stdin); - } - - if (reopen_stdout) { - WINAPI_ReopenFileFromHandle(GetStdHandle(STD_OUTPUT_HANDLE), stdout, "w"); - } else { - WINAPI_SetStdHandleFromFile(STD_OUTPUT_HANDLE, stdout); - } - - if (reopen_stderr) { - WINAPI_ReopenFileFromHandle(GetStdHandle(STD_ERROR_HANDLE), stderr, "w"); - } else { - WINAPI_SetStdHandleFromFile(STD_ERROR_HANDLE, stderr); - } - -#ifdef __cplusplus - std::cin.clear(); - std::cout.clear(); - std::cerr.clear(); - std::ios::sync_with_stdio(); -#endif - -} - -/// This section set that WINAPI_AttachConsole() will be called at application startup before main() -#ifdef _MSC_VER -#pragma section(".CRT$XCU", long, read) -__declspec(allocate(".CRT$XCU")) void (*initialize)(void) = WINAPI_AttachConsole; -#else -__attribute__((constructor)) static void initialize(void) { WINAPI_AttachConsole(); } -#endif - -#endif