3

I have a Windows GUI app that uses third-party libraries that print debug/error information to stdout/stderr. I have found numerous solutions for redirecting them to my log file. But only 1.5 out of 4 work as expected. I am using VS 2008 SP1 on WinXP SP3 32-bit. I didn't include error handling, but no calls return errors.

// First one:
SetStdHandle(STD_OUTPUT_HANDLE, (HANDLE)_get_osfhandle(_fileno(log_file.get_FILE())));
SetStdHandle(STD_ERROR_HANDLE, (HANDLE)_get_osfhandle(_fileno(log_file.get_FILE())));

printf("%s", "1 Test printf to cout!\n");
fprintf(stderr, "%s", "1 Test printf to cerr!\n");

std::cout << "1 Test print to cout!\n";
std::cerr << "1 Test print to cerr!\n";

fflush(stdout);
fflush(stderr);


// Second one:
_dup2(_fileno(log_file.get_FILE()), _fileno(stdout));
_dup2(_fileno(log_file.get_FILE()), _fileno(stderr));

printf("%s", "2 Test printf to cout!\n");
fprintf(stderr, "%s", "2 Test printf to cerr!\n");

std::cout << "2 Test print to cout!\n";
std::cerr << "2 Test print to cerr!\n";

fflush(stdout);
fflush(stderr);


// Third one:
std::ofstream out_stream(log_file.get_FILE());
std::cout.rdbuf(out_stream.rdbuf());
std::cerr.rdbuf(out_stream.rdbuf());

printf("%s", "3 Test printf to cout!\n");
fprintf(stderr, "%s", "3 Test printf to cerr!\n");

std::cout << "3 Test print to cout!\n";
std::cerr << "3 Test print to cerr!\n";

fflush(stdout);
fflush(stderr);


// Fourth one:
*stdout = *log_file.get_FILE();
*stderr = *log_file.get_FILE();

printf("%s", "4 Test printf to cout!\n");
fprintf(stderr, "%s", "4 Test printf to cerr!\n");

std::cout << "4 Test print to cout!\n";
std::cerr << "4 Test print to cerr!\n";

fflush(stdout);
fflush(stderr);

After I tested them (separately, of course), I got these results:
3 Test print to cout!
3 Test print to cerr!
4 Test printf to cout!
4 Test print to cout!
4 Test printf to cerr!
4 Test print to cerr!

Why does only the last solution work fully? Is it safe to use it?

Update. XCode test results:
2 Test printf to cout!
2 Test print to cout!
2 Test printf to cerr!
2 Test print to cerr!
4 Test printf to cout!
4 Test print to cout!
4 Test printf to cerr!
4 Test print to cerr!
First one is obviously Windows only, and the third one fails cause there is no file stream constructor that takes FILE* parameter.

Sergi0
  • 1,084
  • 14
  • 28
  • 1
    I suspect: The first one sets the standard out/error handles, but after the standard library has already used the old ones to set up stdout/stderr. The second one probably doesn't work because windows doesn't use file descriptors. The third one only works for cout/cerr because you didn't actually do anything to stdout/stderr. The last one is an ugly non-portable hack. – user253751 Jun 17 '15 at 12:37
  • @immibis and the solution is...? Will test 3 of them on OSX in a moment. – Sergi0 Jun 17 '15 at 12:45
  • I don't have a solution, which is one reason I didn't post that as part of an answer. – user253751 Jun 17 '15 at 12:46
  • 1
    @immibis Why do you call the last an ugly non-portable hack? Ugly is a matter of opinion, I'd be more interested in the non-portable part. – Avi Ginsburg Jun 17 '15 at 12:51
  • None of these redirect stdout and stderr. – Shark Jun 17 '15 at 12:55
  • @Shark why the 4th one works, then? And what actually redirect them? – Sergi0 Jun 17 '15 at 12:57
  • @AviGinsburg `stdout` and `stderr` are allowed to be constants. – user253751 Jun 17 '15 at 12:57
  • 1
    @Shark The fourth one is exactly a redirect of stdout and stderr. Without saving the original pointers, the originals never get closed or restored (shameless [plug](http://stackoverflow.com/questions/29072499/how-to-get-rid-of-stdcout-quickly/29072916#29072916)), but they are redirected. – Avi Ginsburg Jun 17 '15 at 13:03
  • So it looks like despite being a "non-portable" hack, it's the most portable hack... ;) – Avi Ginsburg Jun 17 '15 at 13:39
  • @AviGinsburg yeah :) still don't understand why dup2 doesn't work on windows – Sergi0 Jun 17 '15 at 13:53
  • and yes, I have tried MSDN _dup2 example, no luck – Sergi0 Jun 17 '15 at 13:58
  • [This](http://stackoverflow.com/a/21376268/2899559) shows the use of _dup. Does that qualify/work for you? – Avi Ginsburg Jun 17 '15 at 14:44
  • @AviGinsburg it uses reopen, I can't use it, cause I already have file opened. – Sergi0 Jun 17 '15 at 14:47
  • Note that if a third party libraries use a different C runtime than the one you're using, none of these will work. – Harry Johnston Jun 17 '15 at 23:24
  • @HarryJohnston well, I use the source code, so this shouldn't be a problem – Sergi0 Jun 18 '15 at 02:05
  • @Sergi0: yes, that should be OK. It does also give you another option: fix the libraries. :-) If they're open source, you could even donate the fixes back to the project. (A library really shouldn't be writing to stdout/stderr without explicit permission from the application. At the very least, there should be a way to tell the library where to send the logging information, even if the default is to stdout/stderr.) – Harry Johnston Jun 18 '15 at 02:11
  • [`freopen`](https://msdn.microsoft.com/en-us/library/wk2h68td.aspx) is the portable way to reassign a `FILE` stream. Overwriting a dereferenced `FILE` stream is not portable and could cause a memory leak, or even file corruption in a multi-threaded program. – Eryk Sun Jun 18 '15 at 21:32
  • As to why `_dup2` doesn't work, the CRT sets the standard `FILE` streams to use file number `_NO_CONSOLE_FILENO` (-2) when the process standard handles are `INVALID_HANDLE_VALUE` or `NULL` or the file type is `FILE_TYPE_UNKNOWN`. Therefore duping to descriptors 1 and 2 has no effect on the existing `FILE` streams. – Eryk Sun Jun 18 '15 at 21:35
  • @eryksun I can't use reopen, cause I already have the file opened – Sergi0 Jun 19 '15 at 08:00
  • @eryksun not sure why this is a problem, MSDN has an example on using dup2 for stdout with existing FILE – Sergi0 Jun 19 '15 at 08:03
  • If you're not sure, then by all means check `fileno(stdout)` for a GUI program. It's -2. `_dup2(_fileno(log_file.get_FILE()), 1)` can't affect `stdout` if it's not actually using file descriptor 1 -- not unless you believe in magic or the power of positive thinking. – Eryk Sun Jun 19 '15 at 08:23
  • @eryksun well, that still doesn't explain why _dup2(_fileno(log_file.get_FILE()), _fileno(stdout)) doesn't work – Sergi0 Jun 19 '15 at 13:08
  • I assumed you would know that -2 is not a valid file descriptor, or at least know to check the return value (-1) and `errno` (9, `EBADF`). – Eryk Sun Jun 19 '15 at 16:21
  • FYI, assigning to `*stdout` doesn't work with the new C standard library that's used by VC 14. Standard streams are now a lot more opaque. For example, you can't do something evil like directly modify `stdout->_file`, which you could do with previous CRTs. – Eryk Sun Jun 19 '15 at 17:16
  • @eryksun Nor `_fileno(stdout)`, neither `_dup(_fileno(stdout))` return errors. – Sergi0 Jun 23 '15 at 10:44
  • I don't know how you concluded that `_dup` doesn't return an error. `_fileno(stdout)` is -2 in a GUI app, which is an invalid file descriptor. Predictably `_dup(_fileno(stdout))` does fail by returning -1 and sets `errno` to `EBADF`. I tested with VC++ versions 9, 10, and 14 (i.e. VS 2008, 2010, and 2015). – Eryk Sun Jun 26 '15 at 16:14
  • @eryksun that was supposed to be _dup2. anyway, this all doesn't answer the question what to use to forward stdout to FILE* – Sergi0 Jun 26 '15 at 16:25
  • I already answered the question. Use `freopen`. You just need to get a valid file descriptor assigned to `stdout`, so you can use `freopen("NUL", "w", stdout)`. Once `stdout` has a valid file descriptor (instead of -2), using `_dup2(_fileno(log_file.get_FILE()), _fileno(stdout))` will work fine. You may also need to call `SetStdHandle` to update the standard handles in the Windows process itself if a function bypasses the CRT (i.e. if it calls `GetStdHandle` and `WriteFile`). – Eryk Sun Jun 26 '15 at 17:09

1 Answers1

0

From the end:

  1. You're changing the actual FILE pointer to stdout and stderr. Obvious why it works. std::cout and std::cerr eventually send output to these pointers.

  1. std::ios.rdbuf changes the stream where the instance of std::ios (in this case std::cout and std::cerr) send it's stream.

  1. I never heard of it. I may do a bit of research and edit the answer.

  1. Same as 2.
Avi Ginsburg
  • 10,323
  • 3
  • 29
  • 56