redireccionamiento stdout con ctypes

Estoy intentando redirigir la salida de las funciones de printf a un archivo en Windows. Estoy usando ctypes con python3 para invocar las funciones. Mi código es:

import os, sys from ctypes import * if __name__ == '__main__': print("begin") saved_stdout=os.dup(1) test_file=open("TEST.TXT", "w") os.dup2(test_file.fileno(), 1) test_file.close() print("python print") cdll.msvcrt.printf(b"Printf function 1\n") cdll.msvcrt.printf(b"Printf function 2\n") cdll.msvcrt.printf(b"Printf function 3\n") os.dup2(saved_stdout, 1) print("end") 

Pero cuando ejecuto el código de Eclipse, aparece lo siguiente en la pantalla:

 begin end Printf function 1 Printf function 2 Printf function 3 

… y lo siguiente en el TEST.txt

 python print 

Cuando ejecuto esto desde cmd, esto es lo que está en la pantalla:

 begin end 

..y esto está en el TEST.txt:

 python print 

Cuando comento la segunda statement dup2() por ejemplo

 import os, sys from ctypes import * if __name__ == '__main__': print("begin") saved_stdout=os.dup(1) test_file=open("TEST.TXT", "w") os.dup2(test_file.fileno(), 1) test_file.close() print("python print") cdll.msvcrt.printf(b"Printf function 1\n") cdll.msvcrt.printf(b"Printf function 2\n") cdll.msvcrt.printf(b"Printf function 3\n") #os.dup2(saved_stdout, 1) print("end") 

Desde Eclipse, en la pantalla:

 begin 

… y en el archivo TEST.txt:

 python print end Printf function 1 Printf function 2 Printf function 3 

Desde cmd, en la pantalla:

 begin 

… y en el archivo TEST.txt:

 python print end 

Estoy totalmente confundido ahora. Leí todos los hilos de redireccionamiento aquí en StackOverflow y no puedo entender lo que está pasando. De todos modos, lo que he recostackdo es que las funciones de C acceden a la salida estándar que se enlaza directamente al descriptor de archivo, mientras que Python utiliza un objeto especial para eso: el objeto de archivo stdout. Así que el sys.stdout=*something* elemental sys.stdout=*something* no funciona con ctypes. Incluso probé os.fdopen(1) en la salida de dup2-ed y luego llamé a flush() después de cada sentencia printf , pero esto no está funcionando de nuevo. Estoy totalmente sin ideas ahora y apreciaría si alguien tuviera una solución para esto.

Utilice el mismo tiempo de ejecución de C que usa CPython 3.x (por ejemplo, msvcr100.dll para 3.3). También incluya una llamada a fflush(NULL) antes y después de redireccionar la salida stdout . Para una buena medida, redirija el manejador de salida StandardOutput Windows, en caso de que un progtwig use la API de Windows directamente.

Esto puede complicarse si la DLL utiliza un tiempo de ejecución C diferente, que tiene su propio conjunto de descriptores de archivo POSIX. Dicho esto, debería estar bien si se carga después de haber redirigido la salida StandardOutput Windows.

Editar:

He modificado el ejemplo para ejecutarlo en Python 3.5+. El nuevo “CRT universal” de VC ++ 14 hace que sea mucho más difícil usar la E / S estándar de C a través de 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") # restre POSIX and Windows os.dup2(fd_stdout, 1) kernel32.SetStdHandle(STD_OUTPUT_HANDLE, hStandardOutput) do_print("end")