У меня есть приложение на C++, которое я компилирую в режиме графического интерфейса и иногда запускаю его из консоли в целях отладки. Поскольку обычно он ничего не пишет в консоль, я подключил консоль и использовал freopen()
для перенаправления стандартного вывода на консоль, используя следующий код:
#include <iostream>
#include <cstdio>
#include <windows.h>
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// Attach to the parent console
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
// Redirect stdout and stderr to the console
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
}
std::cout << "print to stdout" << std::endl;
std::cerr << "print to stderr" << std::endl;
return 0;
}
Однако я бы хотел сделать это, используя WinAPI напрямую, а не freopen()
.
Я пытался использовать SetStdHandle()
или следовать https://asawicki.info/news_1326_redirecting_standard_io_to_windows_console, но безуспешно, он ничего не печатает по сравнению с freopen
, который работает.
Кроме того, когда программа, на которой работает терминал, плохо определяет, что она в данный момент работает, и если я нажимаю Enter
, терминал обнаруживает и показывает мне подсказку вместо того, чтобы отправлять ее в программу. Это тоже можно исправить?
🤔 А знаете ли вы, что...
Язык C++ поддерживает метапрограммирование с использованием шаблонов.
Слишком долго для комментария, но я давно этого не делал и не уверен, что что-то пропустил. (Не стесняйтесь редактировать.)
Нет абсолютно ничего плохого в подключении консоли к процессу графического интерфейса в Windows. Вам просто нужно расставить все точки над «i».
AttachConsole()
предназначен для подключения к существующему процессу-конхосту. Если консоль родительского процесса отсутствует, произойдет сбой.
Если вы хотите просто открыть консоль, принадлежащую только вам, используйте AllocConsole()
.
Обе функции не будут работать, если вы уже подключены к консоли. Используйте FreeConsole()
, чтобы сначала отключиться.
После подключения к консоли вам необходимо использовать глубокую магию Win32, чтобы правильно связать стандартные потоки с этой консолью. Вся важная магия заключена в этой console_init_c()
рутине. Мы также стараемся не отбрасывать перенаправленные потоки, как бы странно это ни было запускать графический интерфейс с перенаправленным стандартным вводом-выводом.
(Хотя Q помечен C++
, следующий — C
, поэтому он работает как с C, так и с C++):
// Windows library code requires the following to
// be true in order to use the console functions.
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#elif _WIN32_WINNT < 0x0501
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#ifndef __cplusplus
#include <iso646.h>
#include <stdbool.h>
#include <stdio.h>
#else
#include <ciso646>
#include <cstdio>
#endif
static bool is_console_redirected( DWORD id )
{
HANDLE h = GetStdHandle( id ); // (the handle is redirected if)
return (h != INVALID_HANDLE_VALUE) // the standard handle is already attached to something
and (GetFileType( h ) != FILE_TYPE_CHAR); // AND: file type is PIPE, DISK, etc
}
static bool console_init_c( DWORD id, FILE * stdfp, const char * mode )
{
intptr_t h = (intptr_t) GetStdHandle( id );
int fd = _open_osfhandle( h, _O_TEXT ); if (!fd) return false;
FILE * fp = _fdopen( fd, mode ); if (!fp) { _close( fd ); return false; }
*stdfp = *fp;
setvbuf( stdfp, NULL, _IONBF, 0 ); // stdin, stdout, and stderr are all unbuffered on Windows
return true;
}
bool console_attach_pid( DWORD pid, bool is_detach_first )
//
// pid
// 0 :: Attach to my own (new) console
// ATTACH_PARENT_PROCESS :: Attach to the parent process's console
// (some process's PID) :: Attach to the given process's console
//
// is_detach_first
// false :: Returns true/success if the process is already attached
// to any console, regardless of the value of `pid`.
// true :: Use this if you wish to attempt to _change_ a console
// attachment from (for example) the parent's to a new one.
//
{
// Are any of the standard streams currently attached to a redirected stream?
bool is_stdin_redirected = is_console_redirected( STD_INPUT_HANDLE );
bool is_stdout_redirected = is_console_redirected( STD_OUTPUT_HANDLE );
bool is_stderr_redirected = is_console_redirected( STD_ERROR_HANDLE );
// Attempt to connect to the desired console host
if (is_detach_first)
FreeConsole();
BOOL ok = pid
? AttachConsole( pid )
: AllocConsole();
if (!ok)
switch (GetLastError())
{
// Already attached to a console
case ERROR_ACCESS_DENIED:
return true;
// No parent; try to create a new console
case ERROR_INVALID_HANDLE:
case ERROR_INVALID_PARAMETER:
if (!AllocConsole())
return false;
// All other errors
default:
return false;
}
// Now for the Windows magic that makes C I/O work
// (Read the docs for why we don't need to worry about
// managing resources beyond what we have done here.)
bool success =
(is_stdin_redirected or console_init_c( STD_INPUT_HANDLE, stdin, "rt" )) and
(is_stdout_redirected or console_init_c( STD_OUTPUT_HANDLE, stdout, "wt" )) and
(is_stderr_redirected or console_init_c( STD_ERROR_HANDLE, stderr, "wt" ));
if (!success)
FreeConsole();
return success;
}
Я думаю, что некоторые дополнительные меры (с dup()
и т. д.) могут потребоваться, если вы планируете напрямую играть с младшими файловыми дескрипторами 0
, 1
и 2
, но опять же, я не уверен (и не хочу возиться с этим прямо сейчас). Опять же, отредактируйте, если вам виднее).
Обратите внимание, что код технически UB, но это то, как это сделать в Windows.
Судя по ответам в комментариях:
Стандартные потоки c++
управляются внутри библиотеки времени выполнения c. SetStdHandle
влияет на другие вызовы WinAPI
, использующие дескриптор, но не на потоки среды выполнения c++
. Вот почему мне нужно использовать freopen()
или аналогичные функции из libc
, чтобы изменить потоки времени выполнения c++
.