You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Windows系统的Python中捕获DLL的stdout/stderr输出?

在Windows系统的Python中捕获DLL的stdout/stderr输出

问题场景

比如以下代码会直接把"hello"打印到stderr,我们需要将其捕获为字符串而非直接输出:

import ctypes
string = b'hello\n'
ctypes.cdll.msvcrt._write(2, string, len(string))

已尝试的无效方法

  • 临时替换sys.stderr为StringIO(或使用contextlib.redirect_stdout):无法捕获输出,因为内容来自C库函数而非Python打印语句,Linux环境下同样无效。
  • 使用os.dup2()配合线程读取管道:仅能抑制输出,无法获取具体内容。
  • 调用kernel32.GetStdHandle()和SetStdHandle():修改句柄后打印会触发错误OSError: [WinError 6] 句柄无效。
  • 依赖ctypes.util.find_msvcrt()的方案:Python 3.5+中该函数返回None(微软已从MSVCRT转向UCRT),即使改为直接调用ctypes.cdll.msvcrt,也仅能抑制输出,无法捕获内容。

有效解决方案

基于Windows管道API的重定向方案

通过创建匿名管道替换标准句柄,同时处理UCRT的句柄缓存问题,确保C库输出能被正确捕获。

代码示例:

import ctypes
from ctypes import wintypes

# 加载Windows核心库和UCRT库
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
ucrtbase = ctypes.WinDLL('ucrtbase', use_last_error=True)

# 定义Windows API常量
STD_ERROR_HANDLE = -12
GENERIC_READ = 0x80000000
FILE_SHARE_READ = 0x00000001
INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value

# 定义安全属性结构体
class SECURITY_ATTRIBUTES(ctypes.Structure):
    _fields_ = [
        ('nLength', wintypes.DWORD),
        ('lpSecurityDescriptor', wintypes.LPVOID),
        ('bInheritHandle', wintypes.BOOL)
    ]

# 绑定Windows API函数类型
kernel32.CreatePipe.restype = wintypes.BOOL
kernel32.CreatePipe.argtypes = [
    wintypes.PHANDLE, wintypes.PHANDLE,
    ctypes.POINTER(SECURITY_ATTRIBUTES), wintypes.DWORD
]

kernel32.ReadFile.restype = wintypes.BOOL
kernel32.ReadFile.argtypes = [
    wintypes.HANDLE, wintypes.LPVOID, wintypes.DWORD,
    wintypes.LPDWORD, wintypes.LPVOID
]

kernel32.CloseHandle.restype = wintypes.BOOL
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]

kernel32.GetStdHandle.restype = wintypes.HANDLE
kernel32.GetStdHandle.argtypes = [wintypes.DWORD]

kernel32.SetStdHandle.restype = wintypes.BOOL
kernel32.SetStdHandle.argtypes = [wintypes.DWORD, wintypes.HANDLE]

# 绑定UCRT的句柄缓存操作函数
ucrtbase.__acrt_iob_func.restype = ctypes.POINTER(ctypes.c_char * 2048)
ucrtbase.__acrt_iob_func.argtypes = [wintypes.INT]

def capture_dll_stderr():
    # 创建匿名管道
    sa = SECURITY_ATTRIBUTES()
    sa.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
    sa.bInheritHandle = True
    sa.lpSecurityDescriptor = None

    read_handle, write_handle = wintypes.HANDLE(), wintypes.HANDLE()
    if not kernel32.CreatePipe(ctypes.byref(read_handle), ctypes.byref(write_handle), ctypes.byref(sa), 0):
        raise ctypes.WinError(ctypes.get_last_error())

    # 保存原stderr句柄
    original_stderr = kernel32.GetStdHandle(STD_ERROR_HANDLE)

    try:
        # 设置新的stderr句柄
        if not kernel32.SetStdHandle(STD_ERROR_HANDLE, write_handle):
            raise ctypes.WinError(ctypes.get_last_error())
        
        # 更新UCRT的stderr句柄缓存,避免C库使用旧句柄
        stderr_iob = ucrtbase.__acrt_iob_func(2)  # 2对应stderr
        ctypes.cast(stderr_iob[0], ctypes.POINTER(wintypes.HANDLE))[0] = write_handle

        # 执行产生输出的DLL调用
        string = b'hello\n'
        ctypes.cdll.msvcrt._write(2, string, len(string))
        # 强制刷新C库输出缓冲区
        ucrtbase.fflush(stderr_iob)

        # 读取管道中的内容
        buffer = ctypes.create_string_buffer(4096)
        bytes_read = wintypes.DWORD()
        kernel32.ReadFile(read_handle, buffer, ctypes.sizeof(buffer), ctypes.byref(bytes_read), None)
        return buffer.value[:bytes_read.value].decode('utf-8')
    finally:
        # 恢复原stderr句柄
        kernel32.SetStdHandle(STD_ERROR_HANDLE, original_stderr)
        stderr_iob = ucrtbase.__acrt_iob_func(2)
        ctypes.cast(stderr_iob[0], ctypes.POINTER(wintypes.HANDLE))[0] = original_stderr
        # 关闭管道句柄
        kernel32.CloseHandle(read_handle)
        kernel32.CloseHandle(write_handle)

# 测试捕获结果
captured = capture_dll_stderr()
print(f"捕获的stderr内容:{repr(captured)}")

方案说明

  1. 利用CreatePipe创建匿名管道,将stderr重定向到管道写入端
  2. 通过__acrt_iob_func获取UCRT的标准流结构体,更新其中的句柄,避免C库缓存旧句柄导致输出丢失
  3. 调用fflush强制刷新C库缓冲区,确保内容写入管道
  4. 读取管道内容后恢复原句柄并清理资源

注意事项

  • 该方案兼容UCRT(Python3.5+默认使用)和MSVCRT
  • 如果DLL使用mingw等其他C运行时,需将__acrt_iob_func替换为对应运行时的句柄函数(如mingw的__iob_func)

内容的提问来源于stack exchange,提问作者1''

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 14:31:18