python - 使用 ctypes 进行标准输出重定向

标签 python windows ctypes

我正在尝试将 printf 函数的输出重定向到 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 上阅读了所有重定向线程,但我不明白发生了什么。 无论如何,我收集到的是 C 函数访问直接绑定(bind)到文件描述符的 stdout,而 python 为此使用一个特殊对象 - stdout 文件对象。所以基本的 sys.stdout=*something* 不适用于 ctypes。 我什至在 dup2-ed 输出上尝试了 os.fdopen(1),然后在每个 printf 语句之后调用 flush() 但是这个不再工作了。 我现在完全没有想法,如果有人对此有解决方案,我将不胜感激。

最佳答案

使用与 CPython 3.x 相同的 C 运行时(例如 msvcr100.dll for 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")

关于python - 使用 ctypes 进行标准输出重定向,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17942874/

相关文章:

python - 将 scikit-learn TfIdf 与 gensim LDA 结合使用

Python Splinter 浏览器 = Browser() 不工作

java - TCP套接字超时配置

windows - 批处理文件命令在新窗口中打开不同的命令

python - 在 python 程序中通过 ctypes 使用具有线程本地存储的共享库时发生内存泄漏

python - 从python内存中的MPEG(.ts)文件中提取音频,而不将MPEG写入文件

windows - 适用于 Apache 2.4 和 Windows Server 2008 r2 的 mod_pagespeed

Python ctypes 'segmentation fault: 11'

c - 如何将列表传递给 python 上的 ctypes 函数

python - 从 pandas 数据框中删除非零单元格并删除索引