【问题标题】:Redirecting cout to a console in windows将 cout 重定向到 Windows 中的控制台
【发布时间】:2023-03-20 06:07:01
【问题描述】:

我有一个相对较旧的应用程序。通过一些小的更改,它几乎可以完美地与 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);
}

编辑:我想到的一件事是,我可以制作一个自定义流缓冲区,其溢出方法使用 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 之外,任何人都有更好/更清洁的解决方案吗?

【问题讨论】:

  • Roger:非常非常感谢更新版本。它工作得非常顺利。我只是尝试了一下。我在架构中使用 [C# + CPP-managed + Native-CPP]。在调用您的函数之前,我尝试在我的 WPF C# 主程序中打开一个。似乎两个控制台似乎最终混合在一起,无论如何我更喜欢。感谢您的慷慨努力和贡献。干杯 ikonuk

标签: c++ winapi


【解决方案1】:

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 版本。

【讨论】:

  • @Zingam:尝试问题中显示的freopen 代码以及来自此答案的clear() 调用。
  • 感谢 cout.clear() 位。我有点绝望,把所有其他东西都准备好,但仍然没有打印任何东西。调用 clear() 修复它。
  • 我相信您的编辑 2 是错误的。 freopen_s 破坏了您精心设置的文件句柄。 “正确”的解决方案是您的编辑 1。我说“正确”是因为它有效地将标准错误发送到标准输出,因为没有 CONERR$,所以如果 STD_ERROR_HANDLE 以某种方式重定向到其他地方(2> 某处),那么该重定向就会丢失。
  • @AustinFrance 谢谢,那个编辑实际上不是我做的,其他人编辑了我的答案以提供他们自己的不同答案。我已恢复编辑。对于进行更改的用户:请随时发布单独的答案,或者如果您发现问题或有建议对此发表评论,但请不要从根本上更改以我的名义发布的解决方案。
【解决方案2】:

我以答案形式发布了一个便携式解决方案,以便可以接受。基本上,我将coutstreambuf 替换为使用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;
}

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      这适用于 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;
      

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

        【解决方案6】:

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

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

        【讨论】:

        • 不幸的是,我试过了(我的 WinMain 中的第一行),但似乎没有什么不同。只有 C 风格的 IO 被发送到控制台。
        • 你的意思是你有 ios::sync_with_stdio() 作为 WinMain 的第一行?我希望您需要在设置 C IO 后调用它。
        • 从我的回答移到评论:不幸的是,这个来源是错误的。使用 arg 为 true (默认值)调用此函数除了设置一个已经默认为启用的状态之外没有任何作用。对这个函数的调用碰巧触发的任何其他事情都是纯粹的意外副作用。
        【解决方案7】:

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

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

        【讨论】:

        • 我会调查的。请注意,我使用的是 2008,而不是 2005。
        • 对于 Windows API 或标准库,2005 与 2008 没有太大区别。但是 VC6 和 2003 之间存在差异(但这可能不足以成为问题。)跨度>
        • 不幸的是,他们在 2003 年和 2008 年之间改变了一些东西,使得 std::cout 不再重定向给定我使用的代码:(。
        【解决方案8】:

        Raymond Martineau 提出了一个很好的观点,即“你要做的第一件事”。

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

        在通过 CRT 源代码执行此操作后,我能够通过清除 CRT 中的一个变量来颠覆这种机制,这使它在我完成 AllocConsole 后重新审视事物。

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

        在 AllocConsole 之后,一路向下进入下一个 cout 输出,找出它的去向和原因。

        【讨论】:

          【解决方案9】:

          试试这个 2 班轮:

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

          【讨论】:

            【解决方案10】:

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

            【讨论】:

              【解决方案11】:

              我不确定我是否完全理解了这个问题,但是如果您希望能够简单地将数据吐出到控制台以进行诊断。为什么不尝试 System::Diagnostics::Process::Execute() 方法或该名称空间中的某些方法?

              如果不相关请提前道歉

              【讨论】:

              • 不使用 C# 或托管 C++。所以我没有“System::Diagnostics::Process::Execute”
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-11-14
              • 1970-01-01
              • 2011-09-03
              • 1970-01-01
              • 2011-10-15
              • 2021-12-12
              相关资源
              最近更新 更多