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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/311955/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:33:19  来源:igfitidea点击:

Redirecting cout to a console in windows

c++winapi

提问by Evan Teran

I have an application which is a relatively old. Through some minor changes, it builds nearly perfectly with Visual C++ 2008. One thing that I've noticed is that my "debug console" isn't quite working right. Basically in the past, I've use AllocConsole()to create a console for my debug output to go to. Then I would use freopento redirect stdoutto it. This worked perfectly with both C and C++ style IO.

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

Now, it seems that it will only work with C style IO. What is the proper way to redirect things like coutto a console allocated with AllocConsole()?

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

Here's the code which used to work:

这是曾经工作的代码:

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

EDIT: one thing which occurred to me is that I could make a custom streambuf whose overflow method writes using C style IO and replace std::cout's default stream buffer with it. But that seems like a cop-out. Is there a proper way to do this in 2008? Or is this perhaps something that MS overlooked?

编辑:我想到的一件事是我可以制作一个自定义std::cout流缓冲,其溢出方法使用 C 样式 IO 写入并用它替换默认流缓冲区。但这似乎是一种逃避。2008 年有没有合适的方法来做到这一点?或者这可能是 MS 忽略的东西?

EDIT2: OK, so I've made an implementaiton of the idea I spelled out above. Basically it looks like this:

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

Anyone have a better/cleaner solution than just forcing std::coutto be a glorified fputc?

有没有人有比强迫std::cout成为美化者更好/更清洁的解决方案fputc

采纳答案by Evan Teran

I'm posting a portable solution in answer form so it can be accepted. Basically I replaced cout's streambufwith one that is implemented using c file I/O which does end up being redirected. Thanks to everyone for your input.

我正在以答案形式发布一个便携式解决方案,以便可以接受。基本上,我将cout's替换streambuf为使用 c 文件 I/O 实现的,最终会被重定向。感谢大家的投入。

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

回答by Roger Sanders

Updated Feb 2018:

2018 年 2 月更新:

Here is the latest version of a function which fixes this problem:

这是修复此问题的函数的最新版本:

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

In order to define this function, you'll need the following set of includes:

为了定义这个函数,你需要以下一组包含:

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

In a nutshell, this function synchronizes the C/C++ runtime standard input/output/error handles with the current standard handles associated with the Win32 process. As mentioned in the documentation, AllocConsole changes these process handles for us, so all that's required is to call this function after AllocConsole to update the runtime handles, otherwise we'll be left with the handles that were latched when the runtime was initialized. Basic usage is as follows:

简而言之,此函数将 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);

This function has gone through several revisions, so check the edits to this answer if you're interested in historical information or alternatives. The current answer is the best solution to this problem however, giving the most flexibility and working on any Visual Studio version.

此功能已经历多次修订,因此如果您对历史信息或替代方案感兴趣,请查看对此答案的编辑。然而,当前的答案是解决此问题的最佳解决方案,它提供了最大的灵活性并适用于任何 Visual Studio 版本。

回答by Dmitry Khalatov

If console is for debug only, you can just use OutputDebugStringA/OutputDebugStringWfunctions. Their output directed to Output window in VS if you are in debug mode, otherwise you can use DebugViewto see it.

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

回答by Alberto

This works with VC++ 2017 for c++ style I/O

这适用于 VC++ 2017 的 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;

回答by etc597

For the original you could just use sync_with_stdio(1) example:

对于原版,您可以使用 sync_with_stdio(1) 示例:

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

回答by Charlie

Try this 2 liner:

试试这个 2 班轮:

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

回答by Will Dean

Raymond Martineau makes a good point about it being 'the first thing you do'.

雷蒙德·马蒂诺 (Raymond Martineau) 提出了一个很好的观点,即它是“你做的第一件事”。

I had a redirection problem, which I forget the details of now, where it turned out that very early in the execution of the app, the runtime makes some decisions about output directions which then last for the rest of the application.

我有一个重定向问题,我忘记了现在的细节,结果证明在应用程序执行的早期,运行时会做出一些关于输出方向的决定,然后持续到应用程序的其余部分。

After following this through the CRT source, I was able to subvert this mechanism by clearing a variable within the CRT, which made it take another look at things once I'd done my AllocConsole.

在通过 CRT 源跟踪此之后,我能够通过清除 CRT 中的一个变量来破坏这种机制,这使得它在我完成我的 AllocConsole 后重新审视事物。

Obviously this sort of stuff is not going to be portable, possibly even across toolchain versions, but it might help you out.

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

After your AllocConsole, step all the way down into the next cout output and find out where it's going and why.

在您的 AllocConsole 之后,一直向下进入下一个 cout 输出并找出它的去向以及原因。

回答by DarthPingu

The ios library has a function that lets you re-sync C++ IO to whatever the standard C IO is using: ios::sync_with_stdio().

ios 库有一个函数可以让你将 C++ IO 重新同步到标准 C IO 使用的任何东西:ios::sync_with_stdio()。

There's a nice explanation here: http://dslweb.nwnexus.com/~ast/dload/guicon.htm.

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

回答by Raymond Martineau

From what I can tell, your code should work with VC 2005, if it's your first activity with the console.

据我所知,如果这是您使用控制台的第一个活动,您的代码应该适用于 VC 2005。

After checking a few possibilities, you might be trying to write something before you allocate the console. Writing to std::cout or std::wcout at that point will fail and you need to clear the error flags before making further output.

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

回答by Lewis Kelsey

I don't know, but as to why this is happening, freopen("CONOUT$", "w", stdout);might not redirect the stdout handle in the process parameter block (NtCurrentPeb()->ProcessParameters->StandardOutput) to whatever the LPC call to CSRSS/Conhost returns in response to a request for the stdout handle for the attached console of the process (NtCurrentPeb()->ProcessParameters->ConsoleHandle). It might just make the LPC call and then assign the handle to the FILE * stdoutglobal variable. C++ cout doesn't use FILE * stdoutat all, and probably still might not sync with the PEB for the standard handles.

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