与ctypes的标准输出redirect

我试图将printf函数的输出redirect到Windows上的文件。 我使用python3的ctypes来调用函数。 我的代码是:

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") 

但是当我从Eclipse运行代码时,屏幕上出现以下内容:

 begin end Printf function 1 Printf function 2 Printf function 3 

…和TEST.txt中的以下内容

 python print 

当我从cmd运行这个,这是在屏幕上:

 begin end 

..这是在TEST.txt中:

 python print 

当我注释掉第二个dup2()语句,例如

 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") 

从Eclipse中,在屏幕上:

 begin 

…和TEST.txt文件中:

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

从cmd,在屏幕上:

 begin 

…和TEST.txt文件中:

 python print end 

我现在完全困惑。 我在StackOverflow上读取所有的redirect线程,我不明白发生了什么事情。 无论如何,我所收集的是C函数访问直接绑定到文件描述符的stdout,而python使用一个特殊的对象 – stdout File Object。 所以基本的sys.stdout=*something*不能和ctypes一起使用。 我甚至在dup2-ed输出上尝试了os.fdopen(1) ,然后在每个printf语句之后调用flush() ,但是这不再工作。 我现在完全没有想法,将不胜感激,如果有人有这个解决scheme。

使用CPython 3.x使用的相同的C运行时(例如msvcr100.dll for 3.3)。 还包括在重定向stdout之前和之后调用fflush(NULL) 。 为了更好地衡量,在程序直接使用Windows API的情况下,重定向Windows StandardOutput句柄。

如果DLL使用一个不同的C运行库,它有自己的一组POSIX文件描述符,这会变得复杂。 也就是说,如果在重定向Windows StandardOutput之后被加载,应该可以。

编辑:

我修改了这个例子在Python 3.5+中运行。 VC ++ 14新的“通用CRT”使得通过ctypes使用C标准I / O变得更加困难。

 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")