将cout重定向到Windows中的控制台

时间:2008-11-23 00:36:42

标签: c++ winapi

我有一个相对较旧的应用程序。通过一些小的改动,它几乎完全用Visual C ++ 2008构建。我注意到的一件事是我的“调试控制台”工作不正常。基本上在过去,我使用AllocConsole()为我的调试输出创建一个控制台。然后我会使用freopenstdout重定向到它。这与C和C ++风格的IO完美配合。

现在,它似乎只适用于C风格的IO。将cout之类的内容重定向到使用AllocConsole()分配的控制台的正确方法是什么?

以下是以前使用的代码:

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

编辑:我遇到的一件事是我可以创建一个自定义streambuf,其溢出方法使用C样式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

11 个答案:

答案 0 :(得分:30)

2018年2月更新:

以下是解决此问题的最新版功能:

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进程关联的当前标准句柄同步。正如the documentation中提到的,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版本。

答案 1 :(得分:19)

我正在以答案形式发布便携式解决方案,因此可以接受。基本上,我将cout的{​​{1}}替换为使用c文件I / O实现的,最终被重定向。感谢大家的意见。

streambuf

答案 2 :(得分:8)

如果控制台仅用于调试,则可以使用OutputDebugStringA / OutputDebugStringW函数。如果您处于调试模式,它们的输出将定向到VS中的“输出”窗口,否则您可以使用DebugView来查看它。

答案 3 :(得分:1)

原作你可以使用sync_with_stdio(1) 例如:

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

答案 4 :(得分:1)

这适用于VC ++ 2017 for c ++风格的I / O

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;

答案 5 :(得分:0)

ios库具有一个功能,允许您将C ++ IO重新同步到标准C IO使用的任何内容:ios :: sync_with_stdio()。

这里有一个很好的解释:http://dslweb.nwnexus.com/~ast/dload/guicon.htm

答案 6 :(得分:0)

据我所知,你的代码应该适用于VC 2005,如果它是你在控制台上的第一个活动。

在检查了几种可能性之后,您可能会在分配控制台之前尝试编写内容。此时写入std :: cout或std :: wcout将失败,您需要在进一步输出之前清除错误标志。

答案 7 :(得分:0)

雷蒙德·马蒂诺(Raymond Martineau)提出了“你做的第一件事”的好处。

我遇到了重定向问题,我忘记了现在的细节,结果发现在应用程序执行的早期,运行时会对输出方向作出一些决定,然后这些决定将持续到应用程序的其余部分。 / p>

通过CRT源跟进之后,我能够通过清除CRT中的变量来破坏这种机制,这使我在完成AllocConsole后再看一下这些内容。

显然,这种东西不是可移植的,甚至可能跨工具链版本,但它可能会帮助你。

在你的AllocConsole之后,一步一步到下一个cout输出,找出它的去向和原因。

答案 8 :(得分:0)

试试这2个班轮:

    AllocConsole(); //debug console
    std::freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); //just works

答案 9 :(得分:0)

我不知道,但是关于为什么发生这种情况,freopen("CONOUT$", "w", stdout);可能不会将过程参数块(NtCurrentPeb()->ProcessParameters->StandardOutput)中的stdout句柄重定向到对CSRSS / Conhost的LPC调用返回的任何内容响应对进程的附加控制台(NtCurrentPeb()->ProcessParameters->ConsoleHandle)的stdout句柄的请求。它可能只是进行LPC调用,然后将句柄分配给FILE * stdout全局变量。 C ++ cout根本不使用FILE * stdout,并且对于标准句柄而言,可能仍未与PEB同步。

答案 10 :(得分:-1)

我不确定我是否完全理解这个问题,但是如果你想简单地将数据吐出到控制台以用于诊断目的..为什么不尝试使用System :: Diagnostics :: Process :: Execute()方法或者该命名空间中的一些方法?

如果不相关则提前道歉