如何将标准输出重定向到Visual Studio的输出窗口

22

是否可以将标准输出重定向到Visual Studio的输出窗口?

我在我的程序中使用OutputDebugString,但我使用了一些具有printf或cout输出调试消息的库。


1
可能重复:https://dev59.com/lHVD5IYBdhLWcg3wI4CM - wimh
可能是在Visual Studio 2005输出窗口中捕获cout?的重复问题。 - mr NAE
4个回答

13

来自将cerr和clog重定向到OutputDebugString()

#include <ostream>
#include <Windows.h>

/// \brief This class is derives from basic_stringbuf which will output
/// all the written data using the OutputDebugString function
template<typename TChar, typename TTraits = std::char_traits<TChar>>
class OutputDebugStringBuf : public std::basic_stringbuf<TChar,TTraits> {
public:
    explicit OutputDebugStringBuf() : _buffer(256) {
        setg(nullptr, nullptr, nullptr);
        setp(_buffer.data(), _buffer.data(), _buffer.data() + _buffer.size());
    }

    ~OutputDebugStringBuf() {
    }

    static_assert(std::is_same<TChar,char>::value ||
                    std::is_same<TChar,wchar_t>::value,
                  "OutputDebugStringBuf only supports char and wchar_t types");

    int sync() try {
        MessageOutputer<TChar,TTraits>()(pbase(), pptr());
        setp(_buffer.data(), _buffer.data(), _buffer.data() + _buffer.size());
        return 0;
    }
    catch(...) {
        return -1;
    }

    int_type overflow(int_type c = TTraits::eof()) {
        auto syncRet = sync();
        if (c != TTraits::eof()) {
            _buffer[0] = c;
            setp(_buffer.data(), _buffer.data() + 1, _buffer.data() + _buffer.size());
        }
        return syncRet == -1 ? TTraits::eof() : 0;
    }


private:
    std::vector<TChar> _buffer;

    template<typename TChar, typename TTraits>
    struct MessageOutputer;

    template<>
    struct MessageOutputer<char,std::char_traits<char>> {
        template<typename TIterator>
        void operator()(TIterator begin, TIterator end) const {
            std::string s(begin, end);
            OutputDebugStringA(s.c_str());
        }
    };

    template<>
    struct MessageOutputer<wchar_t,std::char_traits<wchar_t>> {
        template<typename TIterator>
        void operator()(TIterator begin, TIterator end) const {
            std::wstring s(begin, end);
            OutputDebugStringW(s.c_str());
        }
    };
};

那么:

int main() {
    #ifndef NDEBUG
        #ifdef _WIN32
            static OutputDebugStringBuf<char> charDebugOutput;
            std::cerr.rdbuf(&charDebugOutput);
            std::clog.rdbuf(&charDebugOutput);

            static OutputDebugStringBuf<wchar_t> wcharDebugOutput;
            std::wcerr.rdbuf(&wcharDebugOutput);
            std::wclog.rdbuf(&wcharDebugOutput);
        #endif
    #endif

    ...

    // Will be displayed in the debugger
    std::cerr << "Error: something bad happened" << std::endl;

    ...
}

你可能想要将其与

IsDebuggerPresent()

一起使用,以便在不从Visual Studio调试器运行时仍然输出到控制台。


在链接的网站评论中提到的一些更改应用后,它可以很好地工作。(VS2012:添加所有必要的包含文件) - St0fF
哦,非常抱歉,我只记得有一些坏的包含文件,还有一些很容易解决的编译错误。 - St0fF
1
@Goz:某些评论仍可在 Wayback Machine(https://web.archive.org/web/20140825203329/http://blog.tomaka17.com/2011/07/redirecting-cerr-and-clog-to-outputdebugstring/)上找到。以下是复制粘贴的内容:在第 1 和第 2 行之间添加了 "#include"; 在第 8 行开头删除了 "explicit"(explicit 仅适用于只有一个参数的构造函数);在第 17 行开头添加了 "protected";在第 18 和 26 行开头添加了 "virtual";在第 27 行开头用“int”替换了“auto”(auto 是默认存储类型);第 29 行在“=”和“c”之间添加了“(TChar)”(消除可能的截断警告)。 - Ozirus
@Ozirus:不错啊。总是忘记检查archive.org :) - Goz
在主文件中添加 #include <sstream> - Oneiros
请更新你的回答。故意留下有问题的代码作为答案是无法接受的。链接是不够的。 - Rebs

3
直接的标准输出重定向无法使用,因为没有与OutputDebugString相应的句柄。然而,有一种方法:
可以通过将标准输出重定向到管道,然后创建一个线程来读取管道并使用OutputDebugString打印任何从中读取的内容来完成。
注意:我很久以前就考虑过实现这个功能,因为我也遇到了与您类似的问题(一些库使用printf或fprintf(stderr...))。但是,我从未真正实现过这个功能。我总是最终修改了库,因此我没有可用的实现,但我认为原则上应该是可行的。

但我已经做到了这一点...我使用我发布的链接中的代码将stdout重定向到控制台。 - BeeBand
是的,这是可行且有用的,但这个问题涉及到另一个功能:OutputDebugString(它会被重定向到Visual Studio的输出/调试窗口)与控制台没有关系,它是完全不同的东西。 - Suma
我把这个东西给拼凑起来了:https://dev59.com/hWEi5IYBdhLWcg3wWLES#36115332 <- 把标准输出转发到OutputDebugString。 - masterxilo

1
我正在使用Visual Studio 2012,想在调试脚本、C++程序或MSTest DLL时重定向标准输出和标准错误输出,而不必更改源代码本身。最终我采用的方法是使用一种中间程序来捕获输出。

创建C# Windows控制台应用程序

请使用以下C#代码创建/编译一个Windows C# .NET控制台应用程序:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;

namespace OutputDebugStringConsole
{
    class OutputDebugStringConsole
    {
        private static void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
        {
            if (null != outLine.Data)
            {
                Trace.WriteLine(outLine.Data);
                Trace.Flush();
                Console.WriteLine(outLine.Data);
            }
        }

        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                return;
            }

            try
            {
                Process p = new Process();

                p.StartInfo.FileName = args[0];
                p.StartInfo.Arguments = String.Join(" ", args, 1, args.Length - 1);
                Trace.WriteLine("Calling " + p.StartInfo.FileName + " " + p.StartInfo.Arguments);
                p.StartInfo.WorkingDirectory = Directory.GetCurrentDirectory();
                p.StartInfo.CreateNoWindow = true;
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.RedirectStandardError = true;
                p.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
                p.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler);
                p.Start();
                p.BeginOutputReadLine();
                p.BeginErrorReadLine();
                p.WaitForExit();
                // Call WaitForExit() AFTER I know the process has already exited by a successful return from WaitForExit(timeout).
                // This causes the code that reads all remaining pending async data to be executed.
                // see https://groups.google.com/d/msg/microsoft.public.dotnet.framework.sdk/jEen9Hin9hY/FQtEhjdKLmoJ
                Thread.Sleep(100);
                p.WaitForExit();
                p.Close();
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.ToString());
                Console.WriteLine("{0} Exception caught.", e);
            }
        }
    }
}

我使用Trace.WriteLine()代替Debug.WriteLine(),因为这样可以在上述代码的发布版本中也能正常工作。

在Visual Studio项目调试中使用应用程序

注意: 如果您选择了.NET 4/4.5并且正在捕获非托管代码的输出,则需要在项目设置中选择Mixed作为您的调试/调试器类型。否则(使用Auto)可能会出现未处理的KernelBase.dll异常

现在,您可以通过放置新创建的

OutputDebugStringConsole.exe

将文本翻译为中文:

进入调试/命令属性并

"$(TargetPath)" [ARGS ...]

例如,如果它是MSTest DLL文件:

"$(DevEnvDir)CommonExtensions\Microsoft\TestWindow\vstest.console.exe" /Platform:x86 $(TargetPath)

将其输入到您要调试的应用程序的调试/参数中。 命令参数中的引号是必要的,以处理应用程序路径中的空格。

请将此仅视为应用程序用途的示例。 我知道Visual Studio 2012 Test Explorer提供了一种非常好的方法来运行MSTest DLL文件并以结构化方式获取输出。


1
是的。我假设你正在开发一个Win32 GUI应用程序。
你的C实现定义了三个句柄,分别用于标准输入、标准输出和标准错误。Win32定义了等效的句柄,用于定义实际物理输入/输出将出现的位置。如'printf'等C函数使用这些Win32句柄执行I/O操作。基本上,你需要创建一个控制台用于输出,然后重新定向Win32标准输出的指向位置。接着获取C标准输出的句柄,并将其与Win32标准输出相关联。 此链接 包含更多关于如何完成这项工作的信息:
你需要向你的应用程序中添加两个新文件(该链接包含列表)。

我认为这还不够。你想要做的是将OutputDebugString的HANDLE设置为stdout。但是你无法获取OutputDebugString的HANDLE,因为它是一种共享内存结构。你需要创建自己的HANDLE或管道,并将输入端连接到stdout,将输出端连接到一个自己的实现中,以便将所有内容重定向到OutputDebugString。 - mkaes
@mkaes,你看过我发的那个链接里的代码清单吗?一个控制台被分配了,Win32句柄与标准输出(stdout)相关联。然后c stdout句柄与win32句柄相关联。我猜OutputDebugString()使用Win32 stdout句柄,关键是OP必须将这个Win32 stdout重定向到他/她需要创建的控制台上。('AllocConsole();') - BeeBand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接