如何在Windows中重定向Python中的C级流?

时间:2016-02-10 00:39:56

标签: python windows redirect stdout

Eli Bendersky已经详细解释了如何“Redirecting all kinds of stdout in Python”,特别是重定向C级流,例如stdout共享库(dll)。但是,该示例在Linux中并不适用于Windows,主要是由于以下几行:

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

我们如何让它在Windows中运行?

1 个答案:

答案 0 :(得分:3)

我发现答案埋没在Drekin's code中。基于此,我对Eli Bendersky's example进行了一些小改动:

更新:此代码已经在Windows上的64位64位和Linux上的Python 3.5 64位上进行了测试。对于Windows上的Python 3.5,请参阅eryksun的评论。

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...')