sys.stdout/sys.stderr 和 GetStdHandle 是否在 Windows 上同步?

问题描述

我知道 sys.stdoutPython object that wraps the output file handle,但我想知道这些文件句柄是否“同步”并且始终相同?

例如,说 sys.stdout.isatty() 为 True。我调用 GetStdHandle(-11) (-11 是 Windows 上的 STDOUT),然后调用一些失败的 Windows 控制台 API,发现错误的 errno 为 6(句柄无效)。 AFAIK,这意味着该句柄不是有效的控制台句柄。在这种情况下,它们不会“同步”。换句话说,是否可以在 GetStdHandle 返回的 STDOUT 句柄未重定向的情况下重定向 sys.stdout?我的代码使用 GetStdHandle,所以最终我应该测试 errno 6,但如果我能依赖 sys.stdout.isatty 就好了。

这是一个示例(我目前无法访问 Windows 机器,但希望代码是正确的)。在重定向和不重定向的情况下运行(或通常在调用 subprocess.check_output 时运行。

import sys
from ctypes import WinError,wintypes

STDOUT = -11
ERROR_INVALID_HANDLE = 6
kernel32 = ctypes.WinDLL('kernel32',use_errno=True,use_last_error=True)
handle = kernel32.GetStdHandle(STDOUT)

# Assume we set argtypes/restype for all win api functions here

if handle == wintypes.HANDLE(-1).value:
    raise WinError()

console_mode = wintypes.DWORD(0)

# We use GetConsoleMode here but it Could be any function that expects a
# valid console handle
retval = kernel32.GetConsoleMode(handle,ctypes.byref(console_mode))

# Are the following assertions always true?
if retval == 0:
    errno = ctypes.get_last_error()

    if errno == ERROR_INVALID_HANDLE:
        print('Invalid handle')
        assert not sys.stdout.isatty()
    else:
        # Another error happened
        raise WinError()
else:
    assert sys.stdout.isatty()

我试图搜索 cpython 源代码,但找不到任何可以证实或否认这一点的内容。也许对代码库更有经验的人可以为我指明正确的方向?

编辑:我知道 CONOUT$ + CreateFile API。我对在重定向获取输入或输出句柄不感兴趣,但对了解 Windows 控制台句柄 API 和 sys.stdout间的关系不感兴趣。

解决方法

是的,我可以在 C++ 中重现这个问题。

可以使用CreateFile获取控制台的输出句柄,然后在调用windows控制台apis时将该句柄作为参数使用。

CreateFile 函数使一个进程能够获得它的句柄 控制台的输入缓冲区和活动屏幕缓冲区,即使 STDIN 和 STDOUT 已被重定向。打开控制台输入的句柄 缓冲区,在调用 CreateFile 时指定 CONIN$ 值。指定 调用 CreateFile 以打开控制台句柄的 CONOUT$ 值 活动屏幕缓冲区。 CreateFile 使您能够指定读/写 访问它返回的句柄。

参考:Console Handles

在 C++ 中,它看起来像这样,

 HANDLE hConsole = CreateFile("CONOUT$",GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

效果很好,您可以根据需要将其转换为python代码。

更新:

import sys
import ctypes
from ctypes import WinError,wintypes

STDOUT = -11
sys.stdout = open('test.txt','w')
kernel32 = ctypes.WinDLL('kernel32',use_errno=True,use_last_error=True)
handle = kernel32.GetStdHandle(STDOUT)
if handle == wintypes.HANDLE(-1).value:
    raise WinError()

console_mode = wintypes.DWORD(0)
retval = kernel32.GetConsoleMode(handle,ctypes.byref(console_mode))
print(retval)

if sys.stdout.isatty():
    print('You are running in a real terminal')
else:
    print('You are being piped or redirected')

retval 返回 1。它们都将打印在 test.txt 中。

enter image description here

当您删除 sys.stdout = open('test.txt','w') 时。

enter image description here