Перенаправление cout на консоль в windows

У меня есть приложение, которое является относительно старым. С некоторыми незначительными изменениями он почти отлично сочетается с Visual С++ 2008. Одна вещь, которую я заметил, это то, что моя "консоль отладки" работает не совсем правильно. В основном в прошлом я использовал AllocConsole() для создания консоли для вывода моего отладки. Затем я использовал бы freopen для перенаправления stdout на него. Это отлично работало с IO типа C и С++.

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

Вот код, который использовался для работы:

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

EDIT: одна вещь, которая пришла мне в голову, - это то, что я могу создать пользовательский streambuf, метод переполнения которого записывается с использованием стиля C style IO и заменяет его буфером по умолчанию 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?

Ответ 1

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

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;
}

Ответ 2

EDIT:

Исходный метод, который я опубликовал, не работает в VS2015, здесь фиксированная процедура:

void BindStdHandlesToConsole()
{
    // Redirect the CRT standard input, output, and error handles to the console
    freopen("CONIN$", "r", stdin);
    freopen("CONOUT$", "w", stdout);
    freopen("CONOUT$", "w", stderr);

    //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 console or not.
    std::wcout.clear();
    std::cout.clear();
    std::wcerr.clear();
    std::cerr.clear();
    std::wcin.clear();
    std::cin.clear();
}

Гораздо лучше, не так ли? Вы когда-нибудь делали это, когда внимательно смотрели на код, который вы писали в прошлом, и, когда вы понимаете, что он делает, на вашем лице появляется слегка испуганное выражение, и вы думаете себе: "Что, черт возьми, я думал"? Да, у меня был только один из этих моментов.

Я основал этот код перенаправления на чем-то, что я встретил буквально, как 10 лет назад, и с тех пор я его несу. Это сработало, поэтому я использовал его, но до сих пор я не слишком внимательно смотрел на него. Почему старый метод не работает в VS2015, потому что он пытается назначить содержимое одного объекта FILE другому. Да, это никогда не было хорошей идеей. На самом деле это действительно очень плохая идея. Код разбивается на VS2015, потому что при работе с единой CRT-системой, которую Microsoft сделала, они в основном перенесли все фактические данные на внутренний тип и лишили весь контент из общедоступного типа FILE, что справедливо, потому что никогда не было законным эта структура в первую очередь.

Исходный код просто пытался открыть дескриптор новой консоли, связать ее с дескриптором файла C и обновить номер дескриптора файла в структурах FILE для стандартных ручек ввода/вывода C. Так как это так, freopen - это функция, которая может выполнять точно, явно и безопасно, в одном вызове функции. Как отметил Бен Фойгт, это правильный способ сделать это.

Все сказанное, здесь есть одно изменение поведения. Если ваш процесс был запущен с перенаправленным вводом/выводом, IE, кто-то использовал структуру STARTUPINFO в вызове CreateProcess для перенаправления ввода/вывода для вашего процесса, исходный код, который я опубликовал, уважал бы это, а не использовал консольные дескрипторы. С новым кодом перенаправление ввода/вывода через STARTUPINFO будет проигнорировано. Я оставлю это вам, чтобы решить, хорошо это или плохо. К сожалению, здесь я не вижу много альтернатив, потому что я не вижу другого способа в Microsoft CRT изменять номер файла для существующей структуры FILE, отличной от функции freopen, или отбрасывать структуру FILE во внутренний тип и вызывать значение прямо (что, прошу вас, пожалуйста, не делайте).

Оригинальное сообщение:

У меня была эта проблема с моим кодом ввода-перенаправления при миграции из Visual Studio 2005 до 2013 года. Проблема оказалась из-за потоков С++, которые устанавливают состояние ошибки badbit, после чего никакие чтения или записи не удаются. Вы можете легко reset, вызвав метод clear для объектов потока. Я нашел ответ на эту проблему из ответа, заданного Матс Петерссоном, на соответствующий вопрос: Выполнение std:: endl перед AllocConsole не вызывает отображения std:: cout

Здесь должно произойти изменение в поведении библиотеки, где в Visual Studio 2005 потоки С++ будут работать после их перенаправления, если пока не будут предприняты попытки получить к ним доступ. В 2008 году и выше код инициализации для среды выполнения, похоже, как-то проверяет стандартные дескрипторы ввода/вывода, и устанавливает badbit с самого начала. В действительности, мы должны были всегда очищать состояние ошибки после перенаправления стандартного ввода/вывода, так как кажется вполне разумным ожидать, что потоки могут находиться в состоянии ошибки, если ручки ранее были недействительными.

Здесь рабочая процедура, которую я использую в своем коде. Это, как доказано, работает в Visual Studio 2013:

void BindStdHandlesToConsole()
{
    //Redirect unbuffered STDIN to the console
    HANDLE stdInHandle = GetStdHandle(STD_INPUT_HANDLE);
    if(stdInHandle != INVALID_HANDLE_VALUE)
    {
        int fileDescriptor = _open_osfhandle((intptr_t)stdInHandle, _O_TEXT);
        if(fileDescriptor != -1)
        {
            FILE* file = _fdopen(fileDescriptor, "r");
            if(file != NULL)
            {
                *stdin = *file;
                setvbuf(stdin, NULL, _IONBF, 0);
            }
        }
    }

    //Redirect unbuffered STDOUT to the console
    HANDLE stdOutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    if(stdOutHandle != INVALID_HANDLE_VALUE)
    {
        int fileDescriptor = _open_osfhandle((intptr_t)stdOutHandle, _O_TEXT);
        if(fileDescriptor != -1)
        {
            FILE* file = _fdopen(fileDescriptor, "w");
            if(file != NULL)
            {
                *stdout = *file;
                setvbuf(stdout, NULL, _IONBF, 0);
            }
        }
    }

    //Redirect unbuffered STDERR to the console
    HANDLE stdErrHandle = GetStdHandle(STD_ERROR_HANDLE);
    if(stdErrHandle != INVALID_HANDLE_VALUE)
    {
        int fileDescriptor = _open_osfhandle((intptr_t)stdErrHandle, _O_TEXT);
        if(fileDescriptor != -1)
        {
            FILE* file = _fdopen(fileDescriptor, "w");
            if(file != NULL)
            {
                *stderr = *file;
                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 console or not.
    std::wcout.clear();
    std::cout.clear();
    std::wcerr.clear();
    std::cerr.clear();
    std::wcin.clear();
    std::cin.clear();
}

Небольшая заметка о функции ios::sync_with_stdio(), не беспокойтесь об этом. Вызов этой функции с помощью arg true не имеет ничего общего с тем, чтобы установить состояние, которое по умолчанию по умолчанию включено. Все, что вызывает вызов этой функции, - это случайный побочный эффект.

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 7

Раймонд Мартино замечает, что это "первое, что ты делаешь".

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

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

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

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

Ответ 8

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

if(AllocConsole())
{
    freopen("CONOUT$", "wt", stdout);
    freopen("CONIN$", "rt", stdin);
    SetConsoleTitle(L"Debug Console");
    std::ios::sync_with_stdio(1);
}

Ответ 9

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

Извините заранее, если это было неуместно