Add new version of WINAPI code for attaching console

This commit is contained in:
Pali Rohár 2015-04-12 11:54:50 +02:00
parent 3a6107b267
commit 04e6ebcc12
5 changed files with 271 additions and 226 deletions

View file

@ -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
)

View file

@ -0,0 +1,32 @@
/*
Copyright (C) 2015 Pali Rohár <pali.rohar@gmail.com>
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 <http://www.gnu.org/licenses/>.
*/
#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

View file

@ -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

View file

@ -0,0 +1,230 @@
/*
Copyright (C) 2015 Pali Rohár <pali.rohar@gmail.com>
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 <http://www.gnu.org/licenses/>.
*/
/* _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 <windows.h>
#include <stdio.h>
#include <io.h>
#include <errno.h>
#include <fcntl.h>
#ifdef __cplusplus
#include <iostream>
#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);
}
}

View file

@ -1,225 +0,0 @@
/*
attachconsole.cpp - WINAPI AttachConsole
Copyright (C) 2009-2011 Pali Rohár <pali.rohar@gmail.com>
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 <http://www.gnu.org/licenses/>.
*/
#if ( defined(WIN32) || defined(_MSC_VER) ) && ( defined(NO_STDIO_REDIRECT) || ! defined(REDIRECT_OUTPUT) )
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#include <stdio.h>
#ifdef __cplusplus
#include <iostream>
#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