在Windows下如何使用freopen()将stdout和stderr重定向到单个输出文件中

3
我有一个 Windows Win32/GUI应用程序,有时会将有趣的输出同时打印到 stdoutstderr 中,因此我想要做的就是在应用程序退出后将该输出捕获到文件中进行审查。
问题是,我可以成功地调用 freopen_s() 来将 stdout 输出捕获到文件中,或者 我可以使用它来将 stderr 输出捕获到文件中,但试图同时执行两个操作只会生成带有乱码数据的截断文件。
下面是一个简单的程序,用于重现这个问题;如果我注释掉其中一个 freopen_s() 调用,则会在创建的 blah.txt 文件中得到预期的输出(即其中一个文本行),但我想要的是最终得到一个包含两个文本行的 blah.txt
在 Windows 下是否有可能实现这一点?我可以退而求其次,创建两个不同的文件 (例如 blah_stdout.txtblah_stderr.txt),但我不想这样做,因为那样我就必须手动重构 stdout 和 stderr 输出所生成的相对顺序。
int main(int argc, char *argv[])
{
   const char * outFileName = "blah.txt";
   FILE * junk1 = NULL, junk2 = NULL;
   if (freopen_s(&junk1, outFileName, "w", stdout) != 0) abort();
   if (freopen_s(&junk2, outFileName , "w", stderr) != 0) abort();
   printf("This text was printed to stdout and should appear in blah.txt\n");
   fprintf(stderr, "This text was printed to stderr and should appear in blah.txt\n");
   return 0;
}

1
你使用的是哪个CRT? - Mgetz
所以深入研究CRT源代码(它在Windows SDK源代码下),看起来并非所有Windows应用程序都有stdout。只有具有控制台的应用程序才具有作为Windows上stdout功能的管道。您可以通过调用_osfhnd(1)来获取内部的HANDLE,并查看其是否为_NO_CONSOLE_FILENO进行验证。此代码位于_file.cpp中。 - Mgetz
但您应该为两者使用不同的文件名,而不是都使用相同的文件名“blah.txt”,并为stderr和stdout使用不同的名称。 - RbMm
@MGetz:我正在使用MSVC2013或MSVC2015进行编译;我不确定这如何准确地转换为CRT版本。 - Jeremy Friesner
@IInspectable 是的,我需要在我的程序内部执行它,因为我经常不会从命令提示符运行它。 - Jeremy Friesner
显示剩余18条评论
1个回答

2

"w"打开模式被记录为

打开一个空文件进行写入。如果给定的文件存在,则其内容将被销毁。

但即使使用"w+"和"a"也会失败。

如果您正在使用具有dup2的CRT,则可以执行以下操作:

#include <io.h> // MS CRT

...

FILE * junk1 = NULL;
if (freopen_s(&junk1, outFileName, "w", stdout) != 0) abort();
_dup2(1, 2); // Assign stderr to same file as stdout

当我测试这段代码时,它将两行都写入文件,但从注释中可以看出,这不总是情况。旧版本的Visual Studio更有可能工作。

如果您愿意放弃所有可移植性,您也可以直接访问FILE对象(struct _iobuf)后面的结构体(_iob/__iob_func),并用另一个FILE的值覆盖成员。但这在VS 2015及更高版本中不再可能

FILE封装:在以前的版本中,FILE类型完全在<stdio.h>中定义,因此用户代码可以进入FILE并混淆其内部。我们重构了stdio库以改进库实现细节的封装。作为其中的一部分,FILE在所定义的是现在是一个不透明类型,其成员无法从CRT本身之外访问。


嗯,_dup2建议编译没问题(在MSVC2015下),但是当我运行示例时,生成的blah.txt只包含“此文本已打印到标准输出”这一行。 - Jeremy Friesner
@JeremyFriesner dup2 函数返回 0 还是 -1? - Anders
@Mgetz 好吧,这可能取决于版本,因为我的代码在早期的MSVC版本中对我有用。 - Anders
3
@Anders,这很接近,但为了实现一般的兼容性,您需要首先将文件描述符和流设置为工作状态。在UCRT GUI应用程序中,标准FD被初始化为文件句柄-2(即由_get_osfhandle(1)返回的值),因此首先使用_close关闭FD 0-2以释放它们进行重用。标准流的_fileno值也为-2,因此您需要按顺序重新打开stdinstdoutstderr,以便它们分别被分配到FD 0、1和2。我使用"\\\\.\\NUL"设备来实现这一点。现在,您可以将stdout重新打开到文件,并且_dup2(1, 2)将按预期运行。 - Eryk Sun
@ErykSun 感谢您提供的信息!-2 绝对是出乎意料的,使用 NUL 设备解决了问题。 - David K. Hess
显示剩余8条评论

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