Перенаправление cout на консоль в windows - PullRequest
36 голосов
/ 23 ноября 2008

У меня есть довольно старое приложение. Благодаря некоторым незначительным изменениям, он почти идеально собирается с Visual C ++ 2008. Одна вещь, которую я заметил, это то, что моя «консоль отладки» работает не совсем правильно. В прошлом я использовал AllocConsole(), чтобы создать консоль для вывода отладочной информации. Тогда я бы использовал freopen, чтобы перенаправить stdout на него. Это прекрасно работает как с вводом-выводом в стиле C и C ++.

Теперь кажется, что он будет работать только с вводом-выводом в стиле Си. Как правильно перенаправить такие вещи, как cout, на консоль, выделенную с помощью AllocConsole()?

Вот код, который раньше работал:

if(AllocConsole()) {
    freopen("CONOUT$", "wt", stdout);
    SetConsoleTitle("Debug Console");
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);
}

EDIT : одна вещь, которая пришла мне в голову, это то, что я мог бы создать собственный потоковый буфер, метод переполнения которого пишет с использованием ввода-вывода в стиле C, и заменить на него потоковый буфер по умолчанию std::cout. Но это похоже на отговорку. Есть ли правильный способ сделать это в 2008 году? Или это, возможно, что-то, что MS упустил из виду?

EDIT2 : ОК, поэтому я реализовал идею, изложенную выше. В основном это выглядит так:

class outbuf : public std::streambuf {
public:
    outbuf() {
        setp(0, 0);
    }

    virtual int_type overflow(int_type c = traits_type::eof()) {
        return fputc(c, stdout) == EOF ? traits_type::eof() : c;
    }
};

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
    // create the console
    if(AllocConsole()) {
        freopen("CONOUT$", "w", stdout);
        SetConsoleTitle("Debug Console");
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);  
    }

    // set std::cout to use my custom streambuf
    outbuf ob;
    std::streambuf *sb = std::cout.rdbuf(&ob);

    // do some work here

    // make sure to restore the original so we don't get a crash on close!
    std::cout.rdbuf(sb);
    return 0;
}

У кого-нибудь есть лучшее / более чистое решение, чем просто принуждение std::cout быть прославленным fputc?

Ответы [ 10 ]

30 голосов
/ 19 сентября 2014

Обновлено февраль 2018 года:

Вот последняя версия функции, которая решает эту проблему:

void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr)
{
    // Re-initialize the C runtime "FILE" handles with clean handles bound to "nul". We do this because it has been
    // observed that the file number of our standard handle file objects can be assigned internally to a value of -2
    // when not bound to a valid target, which represents some kind of unknown internal invalid state. In this state our
    // call to "_dup2" fails, as it specifically tests to ensure that the target file number isn't equal to this value
    // before allowing the operation to continue. We can resolve this issue by first "re-opening" the target files to
    // use the "nul" device, which will place them into a valid state, after which we can redirect them to our target
    // using the "_dup2" function.
    if (bindStdIn)
    {
        FILE* dummyFile;
        freopen_s(&dummyFile, "nul", "r", stdin);
    }
    if (bindStdOut)
    {
        FILE* dummyFile;
        freopen_s(&dummyFile, "nul", "w", stdout);
    }
    if (bindStdErr)
    {
        FILE* dummyFile;
        freopen_s(&dummyFile, "nul", "w", stderr);
    }

    // Redirect unbuffered stdin from the current standard input handle
    if (bindStdIn)
    {
        HANDLE stdHandle = GetStdHandle(STD_INPUT_HANDLE);
        if(stdHandle != INVALID_HANDLE_VALUE)
        {
            int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
            if(fileDescriptor != -1)
            {
                FILE* file = _fdopen(fileDescriptor, "r");
                if(file != NULL)
                {
                    int dup2Result = _dup2(_fileno(file), _fileno(stdin));
                    if (dup2Result == 0)
                    {
                        setvbuf(stdin, NULL, _IONBF, 0);
                    }
                }
            }
        }
    }

    // Redirect unbuffered stdout to the current standard output handle
    if (bindStdOut)
    {
        HANDLE stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
        if(stdHandle != INVALID_HANDLE_VALUE)
        {
            int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
            if(fileDescriptor != -1)
            {
                FILE* file = _fdopen(fileDescriptor, "w");
                if(file != NULL)
                {
                    int dup2Result = _dup2(_fileno(file), _fileno(stdout));
                    if (dup2Result == 0)
                    {
                        setvbuf(stdout, NULL, _IONBF, 0);
                    }
                }
            }
        }
    }

    // Redirect unbuffered stderr to the current standard error handle
    if (bindStdErr)
    {
        HANDLE stdHandle = GetStdHandle(STD_ERROR_HANDLE);
        if(stdHandle != INVALID_HANDLE_VALUE)
        {
            int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
            if(fileDescriptor != -1)
            {
                FILE* file = _fdopen(fileDescriptor, "w");
                if(file != NULL)
                {
                    int dup2Result = _dup2(_fileno(file), _fileno(stderr));
                    if (dup2Result == 0)
                    {
                        setvbuf(stderr, NULL, _IONBF, 0);
                    }
                }
            }
        }
    }

    // Clear the error state for each of the C++ standard stream objects. We need to do this, as attempts to access the
    // standard streams before they refer to a valid target will cause the iostream objects to enter an error state. In
    // versions of Visual Studio after 2005, this seems to always occur during startup regardless of whether anything
    // has been read from or written to the targets or not.
    if (bindStdIn)
    {
        std::wcin.clear();
        std::cin.clear();
    }
    if (bindStdOut)
    {
        std::wcout.clear();
        std::cout.clear();
    }
    if (bindStdErr)
    {
        std::wcerr.clear();
        std::cerr.clear();
    }
}

Чтобы определить эту функцию, вам понадобится следующий набор включений:

#include <windows.h>
#include <io.h>
#include <fcntl.h>
#include <iostream>

В двух словах, эта функция синхронизирует стандартные дескрипторы ввода / вывода / ошибки стандартной среды выполнения C / C ++ с текущими стандартными дескрипторами, связанными с процессом Win32. Как упоминалось в документации , AllocConsole изменяет эти дескрипторы процесса для нас, поэтому все, что требуется, это вызвать эту функцию после AllocConsole, чтобы обновить дескрипторы времени выполнения, в противном случае мы останемся с дескрипторами, которые были зафиксированы при время выполнения было инициализировано. Основное использование выглядит следующим образом:

// Allocate a console window for this process
AllocConsole();

// Update the C/C++ runtime standard input, output, and error targets to use the console window
BindCrtHandlesToStdHandles(true, true, true);

Эта функция прошла несколько ревизий, поэтому проверьте изменения этого ответа, если вас интересует историческая информация или альтернативы. Однако текущий ответ - лучшее решение этой проблемы, обеспечивающее большую гибкость и работающий с любой версией Visual Studio.

19 голосов
/ 08 января 2009

Я публикую переносимое решение в форме ответа, чтобы его можно было принять. По сути, я заменил cout streambuf на тот, который реализован с использованием файлового ввода-вывода c, который в итоге перенаправляется. Спасибо всем за ваш вклад.

class outbuf : public std::streambuf {
public:
    outbuf() {
        setp(0, 0);
    }

    virtual int_type overflow(int_type c = traits_type::eof()) {
        return fputc(c, stdout) == EOF ? traits_type::eof() : c;
    }
};

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
    // create the console
    if(AllocConsole()) {
        freopen("CONOUT$", "w", stdout);
        SetConsoleTitle("Debug Console");
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);  
    }

    // set std::cout to use my custom streambuf
    outbuf ob;
    std::streambuf *sb = std::cout.rdbuf(&ob);

    // do some work here

    // make sure to restore the original so we don't get a crash on close!
    std::cout.rdbuf(sb);
    return 0;
}
8 голосов
/ 24 ноября 2008

Если консоль предназначена только для отладки, вы можете просто использовать функции OutputDebugStringA / OutputDebugStringW. Их вывод направляется в окно вывода в VS, если вы находитесь в режиме отладки, в противном случае вы можете использовать DebugView , чтобы увидеть его.

1 голос
/ 12 июля 2018

Работает с VC ++ 2017 для ввода / вывода в стиле c ++

AllocConsole();

// use static for scope
static ofstream conout("CONOUT$", ios::out); 
// Set std::cout stream buffer to conout's buffer (aka redirect/fdreopen)
cout.rdbuf(conout.rdbuf());

cout << "Hello World" << endl;
1 голос
/ 16 сентября 2016

Для оригинала вы можете просто использовать sync_with_stdio (1) Пример:

if(AllocConsole())
{
    freopen("CONOUT$", "wt", stdout);
    freopen("CONIN$", "rt", stdin);
    SetConsoleTitle(L"Debug Console");
    std::ios::sync_with_stdio(1);
}
0 голосов
/ 31 мая 2018

Попробуйте этот 2 вкладыша:

    AllocConsole(); //debug console
    std::freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); //just works
0 голосов
/ 23 ноября 2008

Раймон Мартино делает хороший вывод о том, что это «первое, что вы делаете».

У меня была проблема с перенаправлением, о которой я сейчас забываю, когда выяснилось, что в самом начале работы приложения среда выполнения принимает некоторые решения о направлениях вывода, которые затем сохраняются для остальной части приложения. 1003 *

После этого через источник CRT я смог подорвать этот механизм, очистив переменную в CRT, что заставило его по-другому взглянуть на вещи после того, как я сделал свою AllocConsole.

Очевидно, что такого рода вещи не будут переносимыми, возможно, даже в версиях набора инструментов, но они могут вам помочь.

После вашей AllocConsole полностью перейдите к следующему выводу cout и выясните, куда он идет и почему.

0 голосов
/ 23 ноября 2008

Я не уверен, что полностью понимаю проблему, но если вы хотите иметь возможность просто выплевывать данные на консоль для диагностических целей ... почему бы вам не попробовать метод System :: Diagnostics :: Process :: Execute () или какой-то метод в этом пространстве имен ??

Заранее извиняюсь, если это не имеет значения

0 голосов
/ 23 ноября 2008

Из того, что я могу сказать, ваш код должен работать с VC 2005, если это ваше первое действие с консолью.

После проверки нескольких возможностей вы, возможно, пытаетесь что-то написать, прежде чем выделять консоль. Запись в std :: cout или std :: wcout в этот момент не удастся, и вам необходимо очистить флаги ошибок перед дальнейшим выводом.

0 голосов
/ 23 ноября 2008

В библиотеке ios есть функция, которая позволяет вам повторно синхронизировать ввод-вывод C ++ с тем, что использует стандартный ввод-вывод C: ios :: sync_with_stdio ().

Здесь есть хорошее объяснение: http://dslweb.nwnexus.com/~ast/dload/guicon.htm.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...