使用 ctypes 进行标准输出重定向
Posted
技术标签:
【中文标题】使用 ctypes 进行标准输出重定向【英文标题】:stdout redirection with ctypes 【发布时间】:2013-07-30 09:12:56 【问题描述】:我正在尝试将 printf 函数的输出重定向到 Windows 上的文件。我使用 ctypes 和 python3 来调用函数。我的代码是:
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
我现在完全糊涂了。我在 *** 上阅读了所有重定向线程,但我不明白发生了什么。
无论如何,我收集到的是 C 函数访问直接绑定到文件描述符的标准输出,而 python 使用一个特殊的对象 - 标准输出文件对象。所以基本的sys.stdout=*something*
不适用于ctypes。
我什至在 dup2-ed 输出上尝试了os.fdopen(1)
,然后在每个printf
语句之后调用flush()
,但这不再有效。
我现在完全没有想法,如果有人对此有解决方案,我将不胜感激。
【问题讨论】:
你为什么用ctypes
来打印东西?
相关:How do I prevent a C shared library to print on stdout in python?(使用你的文件而不是os.devnull
)
我已经阅读了这个帖子,但它没有帮助。我的代码与示例的代码几乎相同。我需要 ctypes,因为稍后我将使用它来对包含 printfs 的 C 库进行单元测试。
我认为如果有办法刷新标准输出的FILE指针,问题就会解决,但我不知道如何从Python中访问它。
这是……至少可以说是一种奇怪的单元测试方式。或者更确切地说:如果您的库依赖于全局状态并且不允许您明确指定输出,那么它听起来不是很可测试。似乎将精力花在重构库上会更好。
【参考方案1】:
使用与 CPython 3.x 相同的 C 运行时(例如 msvcr100.dll 用于 3.3)。还包括在重定向stdout
之前和之后对fflush(NULL)
的调用。为了更好地衡量,重定向 Windows StandardOutput
句柄,以防程序直接使用 Windows API。
如果 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")
【讨论】:
谢谢!我稍后试试! 是的,是的,它有效!经过 4 天的阅读和尝试,你救了我! :) 顺便说一句,如果您使用 cdll.LoadLibrary 加载其他 dll 并希望重定向此 dll 标准输出,则必须在加载 dll 之前调用重定向方法,否则在控制台中运行时可能无法正常工作。 @lengxuehx,在 Windows 上,DLL 动态或静态链接到不同的 CRT 并不少见,它有自己的低标准 I/O 实现。在这种情况下,重要的重定向是通过GetStdHandle
和SetStdHandle
的Windows 标准句柄。正如我所说,它应该是“如果它在你重定向 Windows StandardOutput
后被加载就可以了”。顺便说一句,cdll.LoadLibrary
毫无意义;它只是调用CDLL
,但不允许您传递任何选项,例如use_errno
、use_last_error
和handle
。
@eryksun 对不起,我错了。我再次测试,发现没有必要“在你的dll加载之前调用重定向方法”。以上是关于使用 ctypes 进行标准输出重定向的主要内容,如果未能解决你的问题,请参考以下文章