3

I've got a Windows Win32/GUI application that sometimes prints interesting output to both stdout and stderr, so what I'd like to do is capture that output into a file for review after the application has exited.

The problem is, I can successfully call freopen_s() to capture stdout output into a file, or I can use it to capture stderr output into a file, but trying to do both at once yields only a truncated file with munged data.

Below is a simple program that reproduces the problem; if I comment out either one of the two freopen_s() calls, then I get the expected output in the blah.txt file that is created (i.e. one of the two text lines), but what I'd like is to end up with a blah.txt containing both text lines.

Is that possible under Windows? I could fall back to creating two different files (e.g. blah_stdout.txt and blah_stderr.txt) but I'd rather not, since then I'd have to manually reconstruct the relative order in which stdout and stderr output was generated.

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;
}
Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • 1
    Which CRT are you using? – Mgetz May 15 '18 at 19:30
  • So digging into the CRT source (it's in the windows SDK under source) it looks like not all windows applications have stdout. Only applications with a console have the pipes that function as stdout on windows. You can verify by calling `_osfhnd(1)` to get the internal `HANDLE` and see if it is `_NO_CONSOLE_FILENO`. This code is in `_file.cpp` – Mgetz May 15 '18 at 19:36
  • but you use the same file name *"blah.txt"* for both. use different names for stderr and stdout – RbMm May 15 '18 at 19:38
  • Do you need to do this from code? If not, just use the Command Prompt. See [Redirecting Error Messages from Command Prompt: STDERR/STDOUT](https://support.microsoft.com/en-us/help/110930/redirecting-error-messages-from-command-prompt-stderr-stdout). – IInspectable May 15 '18 at 19:49
  • @IInspectable I suspect they aren't running a console subsystem application. – Mgetz May 15 '18 at 19:50
  • You might want to change the winapi tag to windows or something MSVC related, this is not a winapi question. – Anders May 15 '18 at 19:54
  • @Anders debatable I'm not sure that `freopen_s` is safe on stdio handles either based on my CRT spelunking – Mgetz May 15 '18 at 19:56
  • @Mgetz The only thing MSDN says is "The standard stream handles that are associated with the console—stdin, stdout, and stderr—must be redirected before C run-time functions can use them in Windows 8.x Store apps" but that is not relevant here so I don't see why not. – Anders May 15 '18 at 19:58
  • @MGetz: I'm compiling with MSVC2013 or MSVC2015; I'm not sure how that translates into a CRT version exactly. – Jeremy Friesner May 15 '18 at 19:58
  • @IInspectable yes, I need to do it from within my program, since I often won't be running it from the command prompt. – Jeremy Friesner May 15 '18 at 19:59
  • @JeremyFriesner because IIRC VS2013 doesn't use the ucrt and 2015 does... – Mgetz May 15 '18 at 19:59
  • @JeremyFriesner That information is fine. We mostly care about VC6 vs VC7-12 vs VC14+ vs MinGW vs Cygwin. – Anders May 15 '18 at 20:01
  • @RbMm yes, using the same output file for both stderr and stdout is what I am attempting to accomplish. (see the last paragraph in my question) – Jeremy Friesner May 15 '18 at 20:14
  • @JeremyFriesner just confirming you're not running this as a subsystem console application are you? – Mgetz May 15 '18 at 20:30
  • I'm running as a Win32 GUI application (specifically it's a Qt GUI application) – Jeremy Friesner May 15 '18 at 20:31
  • @JeremyFriesner try launching it from a console... I have a hunch it'll work then – Mgetz May 15 '18 at 20:37
  • I am launching it from the MSVC 2015 Developer Command Prompt (i.e. by typing ".\MyProgram.exe"), with no luck. – Jeremy Friesner May 15 '18 at 20:39
  • @JeremyFriesner unfortunately the stdio handles are a bit of a lost cause for GUI apps, you can try calling `SetStdHandle` and passing in the result of a `CreateFile` call but I wouldn't count on it to work. – Mgetz May 15 '18 at 20:43
  • @JeremyFriesner It has nothing to do with where you compile or launch it from, GUI vs CUI is a property of your application and only CUI (Console UI) have a console created for them on Windows. – Anders May 15 '18 at 20:44
  • By "Console" do you mean a separate DOS-prompt style window which a user can use to view the stdout/stderr output of the program while it is running? If so, that's not what I'm looking for, I want the output to go to a file instead. (Or if that isn't what you meant, and you meant "console" as a software object instead; it's not clear how to allocate that -- AllocConsole() creates an on-screen window AFAICT. Also if a console is necessary, why does my test program work for stdout (or stderr) alone? – Jeremy Friesner May 15 '18 at 20:48
  • @JeremyFriesner Yes, I'm talking about "DOS window" but as a part of the conversation Mgetz is having. Without a console and without `freopen` stdout will have nowhere to go. – Anders May 15 '18 at 20:52
  • But `stdout`+`freopen` seems to redirect to a file just fine without a console, as long as I also don't try to redirect `stderr` to the same file? – Jeremy Friesner May 15 '18 at 21:12
  • @JeremyFriesner Yes, it is only stdout to console that is problematic when you create a GUI application, other files work as normal. – Anders May 15 '18 at 21:35

1 Answers1

2

The "w" open mode is documented as

Opens an empty file for writing. If the given file exists, its contents are destroyed.

but even "w+" and "a" fail.

If you are using a CRT that has dup2 you can do this:

#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

This code wrote both lines to the file when I tested it but it is evident from the comments that this might not always be the case. Older versions of Visual Studio are more likely to work.

If you are willing to throw out all portability you might also be able to access the struct behind the FILE objects (struct _iobuf) directly (_iob/__iob_func) and overwrite the members with the values from the other FILE. This is not possible in VS 2015 and later:

FILE Encapsulation: In previous versions, the FILE type was completely defined in <stdio.h>, so it was possible for user code to reach into a FILE and muck with its internals. We have refactored the stdio library to improve encapsulation of the library implementation details. As part of this, FILE as defined in is now an opaque type and its members are inaccessible from outside of the CRT itself.

Anders
  • 97,548
  • 12
  • 110
  • 164
  • Hmm, the _dup2 suggestion compiles okay (under MSVC2015), but when I run my example, the generated blah.txt contains only the "This text was printed to stdout" line. – Jeremy Friesner May 15 '18 at 20:13
  • So as it turns out this IS a WINAPI issue... because `GetStdHandle` will return `INVALID_HANDLE_VALUE` on an app that doesn't have redirected input. Moreover the posix replacement calls will stop anyone from closing or reopening those handles but the c runtime calls don't care. – Mgetz May 15 '18 at 20:35
  • @JeremyFriesner Does dup2 return 0 or -1? – Anders May 15 '18 at 20:36
  • @Mgetz How is the stdin related to any of this? We all know the MS CRT uses WinAPI internally but that should not really matter to us, this is a question about the C standard library and the MSVC extensions. – Anders May 15 '18 at 20:39
  • @Anders because if you look at the CRT initialization code you'd see it's calling `GetStdHandle` to get the values passed in from the `CreateProcess` call made by the shell or by `cmd.exe`. In other words the handle must be passed from the parent process to the child for it to exist at all. For stdio to be properly initialized those have to be set prior to CRT init. – Mgetz May 15 '18 at 20:41
  • @Mgetz I don't have the source in front of me so I can't see the internal details but the OP claims his code partially works so stdout must be valid after freopen? And even if it is not, all we care about is assigning a file to stdout and then dup'ing it to stderr, not whatever the console handler are because we don't need them. – Anders May 15 '18 at 20:47
  • @Anders as I mentioned before that's not possible because handles 0, 1, 2 are all protected. The POSIX functions such as `dup` and cousins `open` and `close` will just ignore those handles. The OP can try using `SetStdHandle` but I'm not sure that will work. – Mgetz May 15 '18 at 20:49
  • @Mgetz Well, that must depend on the version because my code worked for me in a older MSVC version. – Anders May 15 '18 at 20:50
  • @Anders per your question above, _dup2() returns 0. – Jeremy Friesner May 15 '18 at 23:03
  • @Anders I make no guarantees either way on vs2017 and ucrt versions. I did what I could debugging a GUI app I had and reading the source I would say I'm at best 60% confident in what I've said thus far. Honestly I'd be tempted to just do `SetStdHandle` to the same file for both stdout and stderr and see what happens. – Mgetz May 16 '18 at 12:28
  • 3
    @Anders, this is close, but for general compatibility you need to first get the file descriptors and streams in a working state. In a UCRT GUI app the standard FDs are initialized to the File handle -2 (i.e. as returned by `_get_osfhandle(1)`), so first `_close` FDs 0-2 to free them for reuse. The `_fileno` value of the standard streams is also -2, so you need to reopen `stdin`, `stdout`, and `stderr`, in that order, so they get assigned to FDs 0, 1, and 2, respectively. I use the `"\\\\.\\NUL"` device for this. Now you can reopen `stdout` to the file, and `_dup2(1, 2)` will work as expected. – Eryk Sun May 16 '18 at 16:29
  • Also, in a GUI app the CRT doesn't keep file descriptors 0-2 in sync with the WinAPI standard handles. If that matters you'll need to manually get and set the handles via `_get_osfhandle` and WinAPI `SetStdHandle`. – Eryk Sun May 16 '18 at 16:31
  • @ErykSun thanks for that info! The -2 was definitely unexpected and using the NUL device did the trick. – David K. Hess Oct 07 '21 at 23:57