2

I'm creating a Windows service, which cannot have an associated console. Therefore I want to redirect stdout and stderr to a (the same) file. Here is what I discovered so far:

  1. Redirecting cout and cerr in C++ can be done by changing the buffers, but this does not affect C I/O like puts or Windows I/O handles.
  2. Hence we can use freopen to reopen stdout or stderr as a file like here, but we cannot specify the same file twice.
  3. To still use the same file for both we can redirect stderr to stdout using dup2 like here.

So far so good, and when we run this code with /SUBSYSTEM:CONSOLE (project properties → Linker → System) everything works fine:

#include <Windows.h>
#include <io.h>
#include <fcntl.h>
#include <cstdio>
#include <iostream>

void doit()
{
    FILE *stream;
    if (_wfreopen_s(&stream, L"log.log", L"w", stdout)) __debugbreak();
    // Also works as service when uncommenting this line: if (_wfreopen_s(&stream, L"log2.log", L"w", stderr)) __debugbreak();
    if (_dup2(_fileno(stdout), _fileno(stderr)))
    {
        const auto err /*EBADF if service; hover over in debugger*/ = errno;
        __debugbreak();
    }

    // Seemingly can be left out for console applications
    if (!SetStdHandle(STD_OUTPUT_HANDLE, reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(stdout))))) __debugbreak();
    if (!SetStdHandle(STD_ERROR_HANDLE, reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(stderr))))) __debugbreak();

    if (_setmode(_fileno(stdout), _O_WTEXT) == -1) __debugbreak();
    if (_setmode(_fileno(stderr), _O_WTEXT) == -1) __debugbreak();

    std::wcout << L"1☺a" << std::endl;
    std::wcerr << L"1☺b" << std::endl;

    _putws(L"2☺a");
    fflush(stdout);
    fputws(L"2☺b\n", stderr);
    fflush(stderr);

    const std::wstring a3(L"3☺a\n"), b3(L"3☺b\n");
    if (!WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), a3.c_str(), a3.size() * sizeof(wchar_t), nullptr, nullptr))
        __debugbreak();
    if (!WriteFile(GetStdHandle(STD_ERROR_HANDLE), b3.c_str(), b3.size() * sizeof(wchar_t), nullptr, nullptr))
        __debugbreak();
}

int        main() { doit(); }
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) { return doit(), 0; }

This nicely writes the following text to log.log:

1☺a
1☺b
2☺a
2☺b
3☺a
3☺b

(Of course we want emoji, so we need some sort of unicode. In this case we use wide characters, which means we need to use setmode or else everything will mess up. You may also need to save the cpp file in an encoding that MSVC understands, e.g. UTF-8 with signature.)

But now back to the original problem: doing this as a service without console, or, equivalent but easier to debug, a GUI app (/SUBSYSTEM:WINDOWS). The problem is that in this case dup2 fails because fileno(stderr) is not a valid file descriptor, because the app initially has no associated streams. As mentioned here, fileno(stderr) == -2 in this case.

Note that when we first open stderr as another file using freopen, everything works fine, but we created a dummy empty file.

So now my question is: what is the best way to redirect both stdout and stderr to the same file in an application which initially has no streams?

Just to recap: the problem is that when stdout or stderr is not associated with an output stream, fileno returns -2, so we cannot pass it to dup2.

(I do not want to change the code used for the actual printing, because that might mean that some output produced by external functions will not be redirected.)

SWdV
  • 1,715
  • 1
  • 15
  • 36
  • 1
    What If you simply open a file with CreateFile, and use its handle to SetStdHandle()? – Michael Chourdakis Jul 08 '19 at 17:32
  • As I assume you want to log debug and event data in those files, I recommend using the windows events if you really want a nice behaving production grade windows service. That API is quite complex so I would further recommend Boost Log. You C code would have to call some C linked functions offered by your C++ code then. Otherwise, you will never leave the hackinsh world. – Superlokkus Jul 08 '19 at 17:34
  • @MichaelChourdakis You would say that it would be that easy, but this does not actually redirect 1☺ and 2☺, only 3☺. – SWdV Jul 08 '19 at 17:36
  • @Superlokkus You mean [Windows Event Log](https://learn.microsoft.com/windows/win32/wes/windows-event-log)? Yeah I wanted to do that later, but saw it was quite complex indeed. I might check out Boost Log (haven't worked with Boost at all yet though). Also I don't think this would work with unmodified printing code, right? And what do you mean with "You C code would have to call some C linked functions offered by your C++ code then"? – SWdV Jul 08 '19 at 17:44
  • You can replace the global `cout` and `cerr` streams with boost log ones at startup, but as you are right to fear is that would not cut it: For nice logging, the logging code should decide for an altert or log level, and not directly print it somewhere. It should make a function call with the level information and log line. Your C++ code could do this to boost log directly but, you want a nice own simple between layer. I assume you have much legacy code, with some C code, you can't compile as C++ translation units (compile .c s with the C++ compiler). But you can call your own simle layer. – Superlokkus Jul 08 '19 at 17:51
  • @Superlokkus Thanks for the information! I don't really have any legacy code (because this is just my own small project I'm working on), so I could replace most code to make it work, but I think it would be nice to have something to redirect all output (including output of CRT debugging functions etc.) – SWdV Jul 08 '19 at 19:32

2 Answers2

1

Here's an example of a program that creates a file for writing and then uses CreateProcess and setting stdout and stderr for the process to the HANDLE of the created file. This example just starts itself with a dummy argument to make it write a lot of things to stdout and stderr that will be written to output.txt.

// RedirectStd.cpp

#include <iostream>
#include <string_view>
#include <vector>

#include <Windows.h>

struct SecAttrs_t : public SECURITY_ATTRIBUTES {
    SecAttrs_t() : SECURITY_ATTRIBUTES{ 0 } {
        nLength = sizeof(SECURITY_ATTRIBUTES);
        bInheritHandle = TRUE;
    }
    operator SECURITY_ATTRIBUTES* () { return this;  }
};

struct StartupInfo_t : public STARTUPINFO {
    StartupInfo_t(HANDLE output) : STARTUPINFO{ 0 } {
        cb = sizeof(STARTUPINFO);
        dwFlags = STARTF_USESTDHANDLES;
        hStdOutput = output;
        hStdError = output;
    }
    operator STARTUPINFO* () { return this; }
};

int cppmain(const std::string_view program, std::vector<std::string_view> args) {
    if (args.size() == 0) {
        // no arguments, create a file and start a new process
        SecAttrs_t sa;

        HANDLE hFile = CreateFile(L"output.txt",
            GENERIC_WRITE,
            FILE_SHARE_READ,
            sa, // lpSecurityAttributes
            CREATE_ALWAYS, // dwCreationDisposition
            FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes
            NULL // dwFlagsAndAttributesparameter
        );
        if (hFile == INVALID_HANDLE_VALUE) return 1;

        StartupInfo_t su(hFile); // set output handles to hFile
        PROCESS_INFORMATION pi;

        std::wstring commandline = L"RedirectStd.exe dummy";
        BOOL bCreated = CreateProcess(
            NULL,
            commandline.data(),
            NULL, // lpProcessAttributes
            NULL, // lpThreadAttributes
            TRUE, // bInheritHandles
            0, // dwCreationFlags
            NULL, // lpEnvironment
            NULL, // lpCurrentDirectory
            su, // lpStartupInfo
            &pi
        );
        if (bCreated == 0) return 2;
        CloseHandle(pi.hThread); // no need for this

        WaitForSingleObject(pi.hProcess, INFINITE); // wait for the process to finish         
        CloseHandle(pi.hProcess);
        CloseHandle(hFile);
    }
    else {
        // called with an argument, output stuff to stdout and stderr 
        for (int i = 0; i < 1024; ++i) {
            std::cout << "stdout\n";
            std::cerr << "stderr\n";
        }
    }

    return 0;
}

int main(int argc, char* argv[]) {
    return cppmain(argv[0], { argv + 1, argv + argc });
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Thanks for your answer! Unfortunately I don't think this will work in my case, because as a service you have to call `StartServiceCtrlDispatcher` (and I don't think this will work in a child process). Anyway, creating a new process seems a bit unnecessary, but this solution might be enough for some cases. – SWdV Jul 08 '19 at 19:36
  • @SWdV Ok, i see what you mean. And a simple `if (_fileno(stdout) < 0) AllocConsole();` won't help I guess? – Ted Lyngmo Jul 08 '19 at 21:06
  • It does not, because normally services cannot have consoles (and I wouldn't really want one here) – SWdV Jul 08 '19 at 22:17
  • I know they don't have std handles per default, but `AllocConsole` could be a way to create those handles so you can use `SetStdHandle` on `STD_OUTPUT_HANDLE` and `STD_ERROR_HANDLE` and put your file handle there instead. . – Ted Lyngmo Jul 08 '19 at 22:27
  • Did the `AllocConsole` call succeed and did the two `SetStdHandle` calls succeed? – Ted Lyngmo Jul 08 '19 at 22:50
  • AllocConsole succeeded, strangely enough, but what should I give to SetStdHandle? -2 to a handle won't work – SWdV Jul 08 '19 at 22:57
  • You give it the handle of the file you want to write to, `hFile` in my example. – Ted Lyngmo Jul 09 '19 at 06:42
  • I'm sorry, but I don't really see how that would work. Would you mind posting a full snippet? – SWdV Jul 09 '19 at 11:09
  • @SWdV I was thinking something along these lines: https://godbolt.org/z/govKF- It does not work when run as a normal program though, but perhaps it'll work when starting without a console associated with it. – Ted Lyngmo Jul 09 '19 at 11:30
  • Unfortunately this also only redirects 3☺, like when you do [this](https://stackoverflow.com/questions/56939802/win32-gui-c-app-redirect-both-stdout-and-stderr-to-the-same-file-on-disk#comment100420060_56939802). – SWdV Jul 09 '19 at 20:25
  • @SWdV What is 3? – Ted Lyngmo Jul 10 '19 at 05:51
  • Ok, I wonder if you shouldn't separate the OS interaction layer from the code doing all the services work (and print logs) by creating a separate process after all. At least that way, you'll be able to easily provide the sub process with stdout/stderr handles as I showed in the answer. Your parent process will only need to wait for service events and to start/stop the sub process. – Ted Lyngmo Jul 10 '19 at 09:24
1

I found a solution that works and does not create a temporary file log2.log. Instead of this file, we can open NUL (Windows's /dev/null), so the code becomes:

FILE *stream;
if (_wfreopen_s(&stream, L"log.log", L"w", stdout)) __debugbreak();
if (freopen_s(&stream, "NUL", "w", stderr)) __debugbreak();
if (_dup2(_fileno(stdout), _fileno(stderr))) __debugbreak();

if (!SetStdHandle(STD_OUTPUT_HANDLE, reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(stdout))))) __debugbreak();
if (!SetStdHandle(STD_ERROR_HANDLE, reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(stderr))))) __debugbreak();

if (_setmode(_fileno(stdout), _O_WTEXT) == -1) __debugbreak();
if (_setmode(_fileno(stderr), _O_WTEXT) == -1) __debugbreak();

This makes sure that _fileno(stderr) is not -2 anymore so we can use dup2.

There might be a more elegant solution (not sure), but this works and does not create a dummy empty file (also not one named NUL).

SWdV
  • 1,715
  • 1
  • 15
  • 36