stdout redirection with ctypes

前端 未结 1 1181
故里飘歌
故里飘歌 2020-12-03 18:06

I\'m trying to redirect the output of printf functions to a file on Windows. I\'m using ctypes with python3 to invoke the functions. My code is:

import os, s         


        
相关标签:
1条回答
  • 2020-12-03 18:52

    Use the same C runtime that CPython 3.x uses (e.g. msvcr100.dll for 3.3). Also include a call to fflush(NULL) before and after redirecting stdout. For good measure, redirect the Windows StandardOutput handle, in case a program uses the Windows API directly.

    This can get complicated if the DLL uses a different C runtime, which has its own set of POSIX file descriptors. That said, it should be OK if it gets loaded after you've redirected Windows StandardOutput.

    Edit:

    I've modified the example to run in Python 3.5+. VC++ 14's new "Universal CRT" makes it much more difficult to use C standard I/O via ctypes.

    import os
    import sys
    import ctypes, ctypes.util
    
    kernel32 = ctypes.WinDLL('kernel32')
    
    STD_OUTPUT_HANDLE = -11
    
    if sys.version_info < (3, 5):
        libc = ctypes.CDLL(ctypes.util.find_library('c'))
    else:
        if hasattr(sys, 'gettotalrefcount'): # debug build
            libc = ctypes.CDLL('ucrtbased')
        else:
            libc = ctypes.CDLL('api-ms-win-crt-stdio-l1-1-0')
    
        # VC 14.0 doesn't implement printf dynamically, just
        # __stdio_common_vfprintf. This take a va_array arglist,
        # which I won't implement, so I escape format specificiers.
    
        class _FILE(ctypes.Structure):
            """opaque C FILE type"""
    
        libc.__acrt_iob_func.restype = ctypes.POINTER(_FILE)    
    
        def _vprintf(format, arglist_ignored):
            options = ctypes.c_longlong(0) # no legacy behavior
            stdout = libc.__acrt_iob_func(1)
            format = format.replace(b'%%', b'\0')
            format = format.replace(b'%', b'%%')
            format = format.replace(b'\0', b'%%')
            arglist = locale = None        
            return libc.__stdio_common_vfprintf(
                options, stdout, format, locale, arglist)
    
        def _printf(format, *args):
            return _vprintf(format, args)
    
        libc.vprintf = _vprintf
        libc.printf = _printf
    
    def do_print(label):
        print("%s: python print" % label)
        s = ("%s: libc _write\n" % label).encode('ascii')
        libc._write(1, s, len(s))
        s = ("%s: libc printf\n" % label).encode('ascii')
        libc.printf(s)
        libc.fflush(None) # flush all C streams
    
    if __name__ == '__main__':
        # save POSIX stdout and Windows StandardOutput
        fd_stdout = os.dup(1)
        hStandardOutput = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
    
        do_print("begin")
    
        # redirect POSIX and Windows
        with open("TEST.TXT", "w") as test:
            os.dup2(test.fileno(), 1)
            kernel32.SetStdHandle(STD_OUTPUT_HANDLE, libc._get_osfhandle(1))
    
        do_print("redirected")
    
        # restore POSIX and Windows
        os.dup2(fd_stdout, 1)
        kernel32.SetStdHandle(STD_OUTPUT_HANDLE, hStandardOutput)
    
        do_print("end")
    
    0 讨论(0)
提交回复
热议问题