How to redirect C-level streams in Python in Windows?
Question: Eli Bendersky has explained thoroughly how to "[Redirecting all kinds of
stdout in Python](http://eli.thegreenplace.net/2015/redirecting-all-kinds-of-
stdout-in-python/)", and specifically Redirecting C-level streams, e.g. stdout
of a shared library (dll). However, the example is in Linux and does not work
in windows, mainly due to the following lines:

    
    
    libc = ctypes.CDLL(None)
    c_stdout = ctypes.c_void_p.in_dll(libc = ctypes.CDLL(None), 'stdout')
    

How can we make it work in Windows?


Answer: I found the answer buried in [Drekin's
code](https://github.com/Drekin/readline-hooks/blob/master/readline_hooks.py).
Based on that, I made a small change to [Eli Bendersky's
example](http://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-
python/):

Update: This code has been tested on Python 3.4 64-bit on Windows and Python
3.5 64-bit on Linux. For Python 3.5 on Windows, please see eryksun's comment.

    
    
    from contextlib import contextmanager
    import ctypes
    import io
    import os
    import sys
    import tempfile
    
    import ctypes.util
    from ctypes import *
    import platform
    
    if platform.system() == "Linux":
        libc = ctypes.CDLL(None)
        c_stdout = ctypes.c_void_p.in_dll(libc, 'stdout')
    if platform.system() == "Windows":
        class FILE(ctypes.Structure):
            _fields_ = [
                ("_ptr", c_char_p),
                ("_cnt", c_int),
                ("_base", c_char_p),
                ("_flag", c_int),
                ("_file", c_int),
                ("_charbuf", c_int),
                ("_bufsize", c_int),
                ("_tmpfname", c_char_p),
            ]
    
        # Gives you the name of the library that you should really use (and then load through ctypes.CDLL
        msvcrt = CDLL(ctypes.util.find_msvcrt())
        libc = msvcrt # libc was used in the original example in _redirect_stdout()
        iob_func = msvcrt.__iob_func
        iob_func.restype = POINTER(FILE)
        iob_func.argtypes = []
    
        array = iob_func()
    
        s_stdin = addressof(array[0])
        c_stdout = addressof(array[1])
    
    
    @contextmanager
    def stdout_redirector(stream):
        # The original fd stdout points to. Usually 1 on POSIX systems.
        original_stdout_fd = sys.stdout.fileno()
    
        def _redirect_stdout(to_fd):
            """Redirect stdout to the given file descriptor."""
            # Flush the C-level buffer stdout
            libc.fflush(c_stdout)
            # Flush and close sys.stdout - also closes the file descriptor (fd)
            sys.stdout.close()
            # Make original_stdout_fd point to the same file as to_fd
            os.dup2(to_fd, original_stdout_fd)
            # Create a new sys.stdout that points to the redirected fd
            sys.stdout = io.TextIOWrapper(os.fdopen(original_stdout_fd, 'wb'))
    
        # Save a copy of the original stdout fd in saved_stdout_fd
        saved_stdout_fd = os.dup(original_stdout_fd)
        try:
            # Create a temporary file and redirect stdout to it
            tfile = tempfile.TemporaryFile(mode='w+b')
            _redirect_stdout(tfile.fileno())
            # Yield to caller, then redirect stdout back to the saved fd
            yield
            _redirect_stdout(saved_stdout_fd)
            # Copy contents of temporary file to the given stream
            tfile.flush()
            tfile.seek(0, io.SEEK_SET)
            stream.write(tfile.read())
        finally:
            tfile.close()
            os.close(saved_stdout_fd)
    
    if __name__ == '__main__':
        f = io.BytesIO()
        print('...')
    
        with stdout_redirector(f):
            print('foobar')
            print(12)
            libc.puts(b'this comes from C')
            os.system('echo and this is from echo')
    
        print('Got stdout:"\n{0}\n"'.format(f.getvalue().decode('utf-8')))
    
        print('Resuming normal operation...')
    

