У меня есть приложение на 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
, терминал обнаруживает и показывает мне подсказку вместо того, чтобы отправлять ее в программу. Это тоже можно исправить?
@WeatherVane у всех форм спама есть одна причина — привлечь больше внимания.
В статье, которую вы цитируете, не используется SetStdHandle
.
Я использую функции winapi AttachConsole()
или AllocConsole()
, а затем WriteConsole()
.
Я предпочитаю использовать /subsystem:console в режиме отладки и /subsystem:windows в выпуске. Хотя с учетом вышесказанного я пишу приложения с графическим интерфейсом Qt, а не непосредственно Winapi.
@WeatherVane, тебе следует больше беспокоиться о том, как было одобрено редактирование тега java
. Я вижу в этом хороший потенциал аудита.
@user4581301 user4581301 это не было одобрено, но рецензент предложил внести изменения, которые я отклонил.
@WeatherVane интересно. Страница истории изменений читается так, как будто это было полностью одобренное редактирование. Временная шкала в этом отношении не стала более ясной. Если это ошибка, то ее не стоит исправлять перед лицом всего остального, требующего хорошей доработки.
> Короче говоря: не пытайтесь повторно подключиться к консоли. Но мне нужно увидеть стандартный вывод в консоли. Ответ: В более широком смысле у меня есть приложение с графическим интерфейсом, которое мне иногда приходится отлаживать в рабочей среде на стороне пользователя. На самом деле это Rust вызывает привязки C++. Я завишу от библиотек С++, которые выводят информацию об ошибках в стандартный вывод. Используя метод Attach, я могу видеть ошибки этих библиотек даже в рабочей среде, просто запустив приложение с консоли.
@user4581301 user4581301 похоже, что оно было одобрено в ту же секунду, когда я отклонил и заменил редактирование, то есть за долю до этого.
@anonymous, поскольку у вас есть что-то, записывающее данные в объект stdout
, недостаточно просто назначить новый дескриптор вызывающего процесса с помощью SetStdHandle()
, вам нужно использовать freopen()
для повторной инициализации объекта stdout
, чтобы он фактически использовал новый дескриптор.
@RemyLebeau Можно ли имитировать freopen
с помощью WinAPI
?
@anonymous нет, потому что объект stdout
не является частью Win32 API, он принадлежит библиотеке времени выполнения C. Итак, после изменения дескриптора на уровне Win32 вам необходимо обновить библиотеку времени выполнения C, чтобы она знала о новом дескрипторе. Библиотека времени выполнения вызывает GetStdHandle()
только один раз при запуске, если вы не прикажете ей обновиться.
@RemyLebeau Спасибо, теперь стало намного яснее. Как насчет имитации всего поведения WinAPI для достижения аналогичного результата? Я надеюсь, что хотя бы это возможно с WinAPI.
@anonymous, какое именно поведение имитировать?
Обычно достаточно freopen("CONOUT$", "w", stdout);
и std::ios::sync_with_stdio()
. Требуется некоторая дополнительная магия, чтобы убедиться, что уже не выполняются перенаправления, иначе вы окажетесь в неправильных местах, пытаясь повторно привязаться к CONOUT$
, поскольку закрытие предыдущих дескрипторов выходит из строя. (Поэтому AllocConsole()
предпочтительнее AttachConsole()
).
@RemyLebeau Программа, скомпилированная в графическом режиме (subsystem/windows
). Обычно, когда я запускаю ее из консоли, стандартный вывод не отображается. Я хочу показать этот вывод, если программа запускается с консоли (и AttachConsole преуспевает)
много комментариев TL:DR, мой 2c: используйте файл журнала, например. spdlog. тогда ты стал лучше контролировать
@Ext3h Я сделал то же самое во фрагменте, прикрепленном к вопросу. Я пытаюсь сделать это без freopen()
, а с помощью WinAPI или чего-то подобного. Поскольку я делаю это из Rust и предпочитаю не использовать крейты libc (библиотеку, которая является привязкой к libc)
@AndersK У меня нет контроля над библиотеками C++, от которых я завишу, и мне нужно получить от них стандартный вывод
«Как насчет того, чтобы имитировать все поведение WinAPI для достижения аналогичного результата? Я надеюсь, что, по крайней мере, это возможно с WinAPI» - Конечно, это возможно. Однако вам придется вызвать WriteConsole
(или WriteFile
). Вы не можете использовать потоки ввода-вывода C или C++.
@anonymous Вы не сможете сделать это без среды выполнения libc, без нее вы даже не узнаете дескриптор, связанный с stdout
, и вы также не сможете обойти среду выполнения C++, поскольку для std::ios::sync_with_stdio()
тоже нет бэкдора . Итак, что бы вы ни собирались делать, это все равно не будет работать с существующими сторонними библиотеками C++.
@llnspectable См. предыдущий комментарий «У меня нет контроля над библиотеками C++, от которых я завишу, и мне нужно получить от них стандартный вывод»
@anonymous, тогда вы застряли в использовании freopen()
и sync_with_stdio
, поэтому существующие библиотеки продолжат работать правильно и фактически записывают данные в ваш новый стандартный вывод.
@RemyLebeau Почему использование SetStdHandle(STD_ERROR_HANDLE, ...)
с дескриптором консоли не должно работать, а freopen()
работает?
@anonymous Как я уже объяснял ранее, stdout
/stderr
инициализируются один раз при запуске. Это означает, что в вашем случае, когда они вызывают GetStdHandle()
во время настройки приложения, он не вернет действительный дескриптор, который они могли бы использовать, поскольку к процессу еще не подключена консоль. ПОСЛЕ того, как вы позвоните SetStdHandle()
, чтобы назначить новый дескриптор, вам необходимо повторно инициализировать stdout
/stderr
с помощью frepoen()
, чтобы они затем снова позвонили GetStdHandle()
, чтобы получить обновленный дескриптор и начать его использовать.
@RemyLebeau Думаю, теперь я понимаю. Стандартные потоки C++ управляются внутри библиотеки времени выполнения C. SetStdHandle влияет на другие вызовы WinAPI, использующие этот дескриптор, но не на потоки среды выполнения C++. Вот почему мне нужно использовать freopen() или аналогичные функции из libc для изменения потоков времени выполнения С++.
@anonymous, тогда, возможно, следует добавить это в вопрос - что вы используете сторонние библиотеки, в которых вам нужна эта запрошенная функциональность.
Слишком долго для комментария, но я давно этого не делал и не уверен, что что-то пропустил. (Не стесняйтесь редактировать.)
Нет абсолютно ничего плохого в подключении консоли к процессу графического интерфейса в 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.
Я думаю, что может потребоваться дополнительная осторожность (с dup()
и т. д.), если вы планируете играть с младшими дескрипторами файлов 0
, 1
и 2
непосредственно с ISTR чем-то подобным. Я также, кажется, помню, что если вы позвоните close()
на fd
или fclose()
на fp
, вы узнаете, почему...
«следующее C
, поэтому оно работает как с C, так и с C++» — я жду, пока лингвисты-юристы научат вас, что «C и C++ — очень разные языки», сразу после того, как они вас смолили, обмазали и сожгли на ставка. А если серьезно, здесь не хватает многих объяснений, в первую очередь того, как среда выполнения языковой поддержки должна быть замешана в этой шутке. Это не «какая-то глубокая магия Win32». Действительно, Win32 ни в чем из этого не участвует.
@AndrewHenle Да, документы ясно дают понять, что вам не следует fclose()
делать такие вещи.
@IInspectable ??? Разные языки, которые часто пересекаются, как в этом случае. И _open_osfhandle()
определенно является магией Win32. 🙂
Все, что имеет начальное подчеркивание, определенно не является символом в Win32 API. Действительно, _open_osfhandle объявлен в <io.h> (который делегирует <corecrt_io.h>), части Universal CRT. Он поставляется как часть ОС, но касается поддержки языка C.
@Dúthomhas Я также помню, что двоичный/текстовый режим важен как для _open_osfhandle()
, так и для _fdopen()
. Я думаю, что _O_TEXT
в вашем вызове _open_osfhandle( h, _O_TEXT )
может вызвать проблемы для двоичных потоков.
@Dúthomhas Для простоты в примерах я игнорирую проверку ошибок и обработку перенаправления. Я закомментировал freopen и вместо этого использовал console_init_c()
, но это не работает. Pastebin.com/cwjGf813 Кстати, согласно ответам в предыдущих комментариях, это невозможно без использования freopen()
, который меняет внутренний дескриптор libc
@AndrewHenle Если он подключен к консоли, то это текстовый поток. Только перенаправленные данные могут быть двоичными.
@anonymous Предлагаемая вам публичная функция: attach_console_pid()
. Если вы просто вырежете и вставите его кусочки, и особенно если вы не проверите условия ошибки, то, скорее всего, что-то пойдет не так.
@Dúthomhas Я не вижу функции attach_console_pid()
. Я вижу console_attach_pid()
но ты не звонил
Извините, опечатка. Я тоже не определился main()
. Целью было не переписать вашу программу, а дать вам небольшую библиотечную функцию для использования в вашем коде.
@Dúthomhas Вот почему я попытался заменить freopen()
, как вы можете видеть в добавленной мной ссылке. Но по какой-то причине это не работает. Может быть, утверждение, что без freopen()
нельзя, правильное
Судя по ответам в комментариях:
Стандартные потоки c++
управляются внутри библиотеки времени выполнения c. SetStdHandle
влияет на другие вызовы WinAPI
, использующие дескриптор, но не на потоки среды выполнения c++
. Вот почему мне нужно использовать freopen()
или аналогичные функции из libc
, чтобы изменить потоки времени выполнения c++
.
ИДК, почему кто-то захотел добавить тег Java. Это тоже не код C.