5

Eli Bendersky has explained thoroughly how to "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?

1 Answer 1

3

I found the answer buried in Drekin's code. Based on that, I made a small change to Eli Bendersky's example:

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...')
Sign up to request clarification or add additional context in comments.

6 Comments

For Python 2 get_file_pointers_Python2, which calls PyFile_AsFile is more reliable because CDLL(ctypes.util.find_msvcrt()) may fail in embedding contexts that lack the SxS activation context to load msvcr90.dll. Also, the above code won't work in 3.5+, which uses the new CRT that doesn't have __iob_func and find_msvcrt returns None anyway. For 3.5+, use the code in get_file_pointers_ucrtbase that calls __acrt_iob_func.
FYI, find_msvcrt returns None in 3.5+ (and Steve Dower is pretty much committed to making it stay that way) because the new CRT is not a drop-in replacement for the old CRT and shouldn't be used with code that expects as much. For example, ucrtbase.dll doesn't export printf. It has __stdio_common_vfprintf which takes 5 parameters and requires building a va_list. Most code that relied on hacking the old CRT's private functions and structures, such as __iob_func and the FILE structure, no longer works because it was all reimplemented in C++.
Having issues making it work on Windows, can you please specify what lines need to be changed?
And Is there a way to monitor the stdout constantly, and not only specific functions?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.