【问题标题】:How do I get console output in C++ with a Windows program?如何使用 Windows 程序在 C++ 中获取控制台输出?
【发布时间】:2010-09-16 13:13:22
【问题描述】:

如果我有一个原生 C++ windows 程序(即入口点是 WinMain),我如何查看 std::cout 等控制台函数的输出?

【问题讨论】:

  • 您是在尝试读取您的输出还是其他应用程序的输出?

标签: c++ windows console


【解决方案1】:

查看Adding Console I/O to a Win32 GUI App。这可以帮助你做你想做的事。

如果您没有或无法修改代码,请尝试通过here 找到的建议将控制台输出重定向到文件。


编辑:这里有一点线程死灵法。我第一次回答这个问题是在 9 年前,在 SO 的早期,在非仅链接答案的(好)政策生效之前。我将重新发布原始文章中的代码,以弥补我过去的罪过。

guicon.cpp -- 控制台重定向功能

#include <windows.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <iostream>
#include <fstream>
#ifndef _USE_OLD_IOSTREAMS
using namespace std;
#endif
// maximum mumber of lines the output console should have
static const WORD MAX_CONSOLE_LINES = 500;
#ifdef _DEBUG
void RedirectIOToConsole()
{
    int hConHandle;
    long lStdHandle;
    CONSOLE_SCREEN_BUFFER_INFO coninfo;
    FILE *fp;

    // allocate a console for this app
    AllocConsole();

    // set the screen buffer to be big enough to let us scroll text
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
    coninfo.dwSize.Y = MAX_CONSOLE_LINES;
    SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);

    // redirect unbuffered STDOUT to the console
    lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE);
    hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
    fp = _fdopen( hConHandle, "w" );
    *stdout = *fp;
    setvbuf( stdout, NULL, _IONBF, 0 );

    // redirect unbuffered STDIN to the console
    lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE);
    hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
    fp = _fdopen( hConHandle, "r" );
    *stdin = *fp;
    setvbuf( stdin, NULL, _IONBF, 0 );

    // redirect unbuffered STDERR to the console
    lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE);
    hConHandle = _open_osfhandle(lStdHandle, _O_TEXT);
    fp = _fdopen( hConHandle, "w" );
    *stderr = *fp;
    setvbuf( stderr, NULL, _IONBF, 0 );

    // make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog
    // point to console as well
    ios::sync_with_stdio();
}

#endif
//End of File

guicon.h -- 控制台重定向功能接口

#ifndef __GUICON_H__
#define __GUICON_H__
#ifdef _DEBUG

void RedirectIOToConsole();

#endif
#endif

// End of File

test.cpp -- 演示控制台重定向

#include <windows.h>
#include <iostream>
#include <fstream>
#include <conio.h>
#include <stdio.h>
#ifndef _USE_OLD_OSTREAMS
using namespace std;
#endif
#include "guicon.h"


#include <crtdbg.h>

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    #ifdef _DEBUG
    RedirectIOToConsole();
    #endif
    int iVar;

    // test stdio
    fprintf(stdout, "Test output to stdout\n");
    fprintf(stderr, "Test output to stderr\n");
    fprintf(stdout, "Enter an integer to test stdin: ");
    scanf("%d", &iVar);
    printf("You entered %d\n", iVar);

    //test iostreams
    cout << "Test output to cout" << endl;
    cerr << "Test output to cerr" << endl;
    clog << "Test output to clog" << endl;
    cout << "Enter an integer to test cin: ";
    cin >> iVar;
    cout << "You entered " << iVar << endl;
    #ifndef _USE_OLD_IOSTREAMS

    // test wide iostreams
    wcout << L"Test output to wcout" << endl;
    wcerr << L"Test output to wcerr" << endl;
    wclog << L"Test output to wclog" << endl;
    wcout << L"Enter an integer to test wcin: ";
    wcin >> iVar;
    wcout << L"You entered " << iVar << endl;
    #endif

    // test CrtDbg output
    _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
    _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR );
    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
    _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR);
    _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
    _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR);
    _RPT0(_CRT_WARN, "This is testing _CRT_WARN output\n");
    _RPT0(_CRT_ERROR, "This is testing _CRT_ERROR output\n");
    _ASSERT( 0 && "testing _ASSERT" );
    _ASSERTE( 0 && "testing _ASSERTE" );
    Sleep(2000);
    return 0;
}

//End of File

【讨论】:

  • 良好的链接。 gamedev.net 论坛一直为我提供丰富的信息。
  • 链接可能会有所帮助,但这个答案不包含比这些链接更多的信息。如果这些变得无法访问,则此答案将不再有用。
  • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
  • *stdout = *fp 不可移植,甚至可能不适用于 2015 年推出的通用 CRT。请改用 freopen 与“CONOUT$”和“CONIN$”。
【解决方案2】:

其他一些答案的问题是,它们不必要地创建了新的 FILE 实例,这些实例随后被泄露,并可能导致 CRT 清理代码中的调试断言。

freopen_s 就是我们真正需要的:

FILE* fp = nullptr;
freopen_s(&fp, "CONIN$", "r", stdin);
freopen_s(&fp, "CONOUT$", "w", stdout);
freopen_s(&fp, "CONOUT$", "w", stderr);

您可能还想进行一些错误检查和清理。以下是我目前使用的完整解决方案。

重定向控制台标准 IO:

bool RedirectConsoleIO()
{
    bool result = true;
    FILE* fp;

    // Redirect STDIN if the console has an input handle
    if (GetStdHandle(STD_INPUT_HANDLE) != INVALID_HANDLE_VALUE)
        if (freopen_s(&fp, "CONIN$", "r", stdin) != 0)
            result = false;
        else
            setvbuf(stdin, NULL, _IONBF, 0);

    // Redirect STDOUT if the console has an output handle
    if (GetStdHandle(STD_OUTPUT_HANDLE) != INVALID_HANDLE_VALUE)
        if (freopen_s(&fp, "CONOUT$", "w", stdout) != 0)
            result = false;
        else
            setvbuf(stdout, NULL, _IONBF, 0);

    // Redirect STDERR if the console has an error handle
    if (GetStdHandle(STD_ERROR_HANDLE) != INVALID_HANDLE_VALUE)
        if (freopen_s(&fp, "CONOUT$", "w", stderr) != 0)
            result = false;
        else
            setvbuf(stderr, NULL, _IONBF, 0);

    // Make C++ standard streams point to console as well.
    ios::sync_with_stdio(true);

    // Clear the error state for each of the C++ standard streams.
    std::wcout.clear();
    std::cout.clear();
    std::wcerr.clear();
    std::cerr.clear();
    std::wcin.clear();
    std::cin.clear();

    return result;
}

发布控制台:

bool ReleaseConsole()
{
    bool result = true;
    FILE* fp;

    // Just to be safe, redirect standard IO to NUL before releasing.

    // Redirect STDIN to NUL
    if (freopen_s(&fp, "NUL:", "r", stdin) != 0)
        result = false;
    else
        setvbuf(stdin, NULL, _IONBF, 0);

    // Redirect STDOUT to NUL
    if (freopen_s(&fp, "NUL:", "w", stdout) != 0)
        result = false;
    else
        setvbuf(stdout, NULL, _IONBF, 0);

    // Redirect STDERR to NUL
    if (freopen_s(&fp, "NUL:", "w", stderr) != 0)
        result = false;
    else
        setvbuf(stderr, NULL, _IONBF, 0);

    // Detach from console
    if (!FreeConsole())
        result = false;

    return result;
}

调整控制台缓冲区大小:

void AdjustConsoleBuffer(int16_t minLength)
{
    // Set the screen buffer to be big enough to scroll some text
    CONSOLE_SCREEN_BUFFER_INFO conInfo;
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &conInfo);
    if (conInfo.dwSize.Y < minLength)
        conInfo.dwSize.Y = minLength;
    SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), conInfo.dwSize);
}

分配新控制台:

bool CreateNewConsole(int16_t minLength)
{
    bool result = false;

    // Release any current console and redirect IO to NUL
    ReleaseConsole();

    // Attempt to create new console
    if (AllocConsole())
    {
        AdjustConsoleBuffer(minLength);
        result = RedirectConsoleIO();
    }

    return result;
}

附加到家长控制台:

bool AttachParentConsole(int16_t minLength)
{
    bool result = false;

    // Release any current console and redirect IO to NUL
    ReleaseConsole();

    // Attempt to attach to parent process's console
    if (AttachConsole(ATTACH_PARENT_PROCESS))
    {
        AdjustConsoleBuffer(minLength);
        result = RedirectConsoleIO();
    }

    return result;
}

从 WinMain 调用:

链接/SUBSYSTEM:Windows

int APIENTRY WinMain(
    HINSTANCE /*hInstance*/,
    HINSTANCE /*hPrevInstance*/,
    LPTSTR    /*lpCmdLine*/,
    int       /*cmdShow*/)
{
    if (CreateNewConsole(1024))
    {
        int i;

        // test stdio
        fprintf(stdout, "Test output to stdout\n");
        fprintf(stderr, "Test output to stderr\n");
        fprintf(stdout, "Enter an integer to test stdin: ");
        scanf("%d", &i);
        printf("You entered %d\n", i);

        // test iostreams
        std::cout << "Test output to std::cout" << std::endl;
        std::cerr << "Test output to std::cerr" << std::endl;
        std::clog << "Test output to std::clog" << std::endl;
        std::cout << "Enter an integer to test std::cin: ";
        std::cin >> i;
        std::cout << "You entered " << i << std::endl;

        std::cout << endl << "Press any key to continue..." << endl;
        _getch();

        ReleaseConsole();
    }

    return 0;
};

【讨论】:

  • 可以将结果输出重定向到文件吗?
【解决方案3】:

您也可以重新打开 cout 和 cerr 流以输出到文件。以下应该适用于此:

#include <iostream>
#include <fstream>

int main ()
{
    std::ofstream file;
    file.open ("cout.txt");
    std::streambuf* sbuf = std::cout.rdbuf();
    std::cout.rdbuf(file.rdbuf());
    //cout is now pointing to a file
    return 0;
}

【讨论】:

    【解决方案4】:

    如果您将程序的输出发送到文件或管道,例如

    myprogram.exe > file.txt
    myprogram.exe | anotherprogram.exe
    

    或者您正在从另一个程序调用您的程序并通过管道捕获其输出,那么您不需要更改任何内容。即使入口点是WinMain,它也能正常工作。

    但是,如果您在控制台或 Visual Studio 中运行程序,则输出不会出现在控制台或 Visual Studio 的“输出”窗口中。如果您想查看“实时”输出,请尝试其他答案之一。

    基本上,这意味着标准输出与控制台应用程序一样工作,但它没有连接到您正在运行应用程序的控制台,而且似乎没有简单的方法可以做到这一点(所有其他解决方案这里展示的将输出连接到一个新的控制台窗口,当您运行您的应用程序时会弹出该窗口,即使是从另一个控制台)。

    【讨论】:

      【解决方案5】:

      实际上,有一个比迄今为止提出的任何解决方案都简单得多的解决方案。您的 Windows 程序将有一个 WinMain 函数,所以只需添加这个“虚拟”主函数

      int main()
      {
         return WinMain(GetModuleHandle(NULL), NULL, GetCommandLineA(), SW_SHOWNORMAL);
      }
      

      您现在可以像这样使用 MSVC 进行编译

      cl /nologo /c /EHsc myprog.c
      link /nologo /out:myprog.exe /subsystem:console myprog.obj user32.lib gdi32.lib
      

      (您可能需要添加更多库链接)

      当您运行程序时,任何printf 都会被写入命令提示符。

      如果您使用 gcc (mingw) 为 Windows 编译,则不需要虚拟 main 函数,只需这样做

      gcc -o myprog.exe myprog.c -luser32 -lgdi32
      

      (即避免使用-mwindows 标志,这将阻止写入控制台。该标志将在您创建最终的 GUI 版本时很有用)同样,如果使用更多 Windows 功能,您可能需要指定更多库)

      【讨论】:

        【解决方案6】:

        结合使用 luke's answerRoger's answer here 在我的 Windows 桌面应用程序项目中为我工作。

        void RedirectIOToConsole() {
        
            //Create a console for this application
            AllocConsole();
        
            // Get STDOUT handle
            HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
            int SystemOutput = _open_osfhandle(intptr_t(ConsoleOutput), _O_TEXT);
            FILE *COutputHandle = _fdopen(SystemOutput, "w");
        
            // Get STDERR handle
            HANDLE ConsoleError = GetStdHandle(STD_ERROR_HANDLE);
            int SystemError = _open_osfhandle(intptr_t(ConsoleError), _O_TEXT);
            FILE *CErrorHandle = _fdopen(SystemError, "w");
        
            // Get STDIN handle
            HANDLE ConsoleInput = GetStdHandle(STD_INPUT_HANDLE);
            int SystemInput = _open_osfhandle(intptr_t(ConsoleInput), _O_TEXT);
            FILE *CInputHandle = _fdopen(SystemInput, "r");
        
            //make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog point to console as well
            ios::sync_with_stdio(true);
        
            // Redirect the CRT standard input, output, and error handles to the console
            freopen_s(&CInputHandle, "CONIN$", "r", stdin);
            freopen_s(&COutputHandle, "CONOUT$", "w", stdout);
            freopen_s(&CErrorHandle, "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();
        
        }
        

        【讨论】:

        • 优秀的解决方案。
        • ios::sync_with_stdio 上面的块不是必需的。此外,它会导致分配有 _fdopen 调用的三个 FILE 实例泄漏,因为它是 freopen_s输出 参数。
        • @Angstrom freopen_s 的文档说它“关闭当前与流关联的文件并将流重新分配给路径指定的文件”,其中 stream 是第一个参数到freopen_s。所以也许@Sev 试图关闭AllocConsole() 设置的std 句柄?我同意这似乎被误导了;不妨使用CloseHandle()。或者使用它们而不是关闭它们然后重新打开它们。
        【解决方案7】:

        创建管道,执行程序控制台 CreateProcess() 并使用 ReadFile() 读取或写入控制台 WriteFile()

            HANDLE hRead ; // ConsoleStdInput
            HANDLE hWrite; // ConsoleStdOutput and ConsoleStdError
        
            STARTUPINFO           stiConsole;
            SECURITY_ATTRIBUTES   segConsole;
            PROCESS_INFORMATION   priConsole;
        
            segConsole.nLength = sizeof(segConsole);
            segConsole.lpSecurityDescriptor = NULL;
            segConsole.bInheritHandle = TRUE;
        
        if(CreatePipe(&hRead,&hWrite,&segConsole,0) )
        {
        
            FillMemory(&stiConsole,sizeof(stiConsole),0);
            stiConsole.cb = sizeof(stiConsole);
        GetStartupInfo(&stiConsole);
        stiConsole.hStdOutput = hWrite;
        stiConsole.hStdError  = hWrite;
        stiConsole.dwFlags    = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
        stiConsole.wShowWindow = SW_HIDE; // execute hide 
        
            if(CreateProcess(NULL, "c:\\teste.exe",NULL,NULL,TRUE,NULL,
              NULL,NULL,&stiConsole,&priConsole) == TRUE)
            {
                //readfile and/or writefile
        }    
        

        }

        【讨论】:

          【解决方案8】:

          转到 Project>Project Properties>Linker>System 并在右侧窗格中,将 SubSystems 选项设置为 Console(/SUBSYSTEM:CONSOLE)

          然后编译你的程序并从控制台运行它,看看你的命令提示符是否显示你的输出。

          【讨论】:

            【解决方案9】:

            请不要引用我的话,但 Win32 console API 可能是您正在寻找的。但是,如果您只是出于调试目的这样做,您可能对运行 DebugView 和调用 DbgPrint 函数更感兴趣。

            这当然假设您的应用程序要发送控制台输出,而不是从另一个应用程序读取它。在这种情况下,管道可能是您的朋友。

            【讨论】:

            • 根据链接到DbgPrint,它认为Only kernel-mode drivers can call the DbgPrint routine.。所以可能不是大多数人想要的
            • 嗯。好吧,我知道有一个用户模式函数用于记录将出现在 DebugView 中的消息。我以为是 DbgPrint,但显然不是。
            • 在 DebugView 中转储文本的用户模式函数是 OutputDebugString
            【解决方案10】:

            therethere 所述,最简单的解决方案是使用您的项目属性页在CONSOLEWINDOWS 子系统之间来回切换,以随意启用或禁用控制台输出。

            您的程序只需要mainWinMain 入口点来确保两个配置都在编译。 main 函数只需调用WinMain 如下所示:

            int main()
            {
            cout << "Output standard\n";
            cerr << "Output error\n";
            
            return WinMain(GetModuleHandle(NULL), NULL, GetCommandLineA(), SW_SHOWNORMAL);
            }
            

            【讨论】:

            • 如果我想在不在 DEBUG 时隐藏它怎么办。
            • 我认为应该可以。只需将 SubSystem 设置为 Console,仅用于您的 Debug 配置。对于您的发布配置,请使用 Windows 子系统。
            【解决方案11】:

            由于没有控制台窗口,这不可能很难。 (每天学习新东西——我从来不知道控制台功能!)

            您可以替换输出调用吗?我会经常使用 TRACE 或 OutputDebugString 将信息发送到 Visual Studio 输出窗口。

            【讨论】:

            • 实际上,使用 WinMain() 入口点而不是 main() 获取程序的控制台窗口是可能的,但很棘手。
            • @ChrisCharabaruk:打电话给AllocConsole 并不难。这在creation of a console 下进行了解释。此外,用户提供的入口点的名称不控制控制台的创建。这是子系统的 PE 标头条目(WINDOWSCONSOLE)。 Visual Studio 使用该链接器设置来控制 CRT 的启动代码调用到哪个用户提供的入口点。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2010-10-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多