简体   繁体   English

使用 ctypes 模块从 python 调用的共享库中捕获打印输出

[英]Capturing print output from shared library called from python with ctypes module

I am working with a shared library that is being called through the ctypes module.我正在使用一个通过 ctypes 模块调用的共享库。 I would like to redirect the stdout associated with this module to a variable or a file that I can access in my program.我想将与此模块关联的标准输出重定向到我可以在我的程序中访问的变量或文件。 However ctypes uses a separate stdout from sys.stdout.但是 ctypes 使用与 sys.stdout 不同的标准输出。

I'll demonstrate the problem I am having with libc.我将演示我在使用 libc 时遇到的问题。 If anyone is copying and pasting the code they might have to change the filename on line 2.如果有人复制和粘贴代码,他们可能必须更改第 2 行的文件名。

import ctypes
libc = ctypes.CDLL('libc.so.6')

from cStringIO import StringIO
import sys
oldStdOut = sys.stdout
sys.stdout = myStdOut = StringIO()

print 'This text gets captured by myStdOut'
libc.printf('This text fails to be captured by myStdOut\n')

sys.stdout = oldStdOut
myStdOut.getvalue()

Is there any way I can capture the stdout that is associated with the ctypes loaded shared library?有什么方法可以捕获与 ctypes 加载的共享库关联的标准输出?

We can use os.dup2() and os.pipe() to replace the entire stdout file descriptor (fd 1) with a pipe we can read from ourselves.我们可以使用os.dup2()os.pipe()将整个 stdout 文件描述符 (fd 1) 替换为我们可以从自己读取的管道。 You can do the same thing with stderr (fd 2).你可以用 stderr (fd 2) 做同样的事情。

This example uses select.select() to see if the pipe (our fake stdout) has data waiting to be written, so we can print it safely without blocking execution of our script.此示例使用select.select()来查看管道(我们的假标准输出)是否有数据等待写入,因此我们可以安全地打印它而不会阻止脚本的执行。

As we are completely replacing the stdout file descriptor for this process and any subprocesses, this example can even capture output from child processes.由于我们正在完全替换此进程和任何子进程的 stdout 文件描述符,因此此示例甚至可以捕获子进程的输出。

import os, sys, select

# the pipe would fail for some reason if I didn't write to stdout at some point
# so I write a space, then backspace (will show as empty in a normal terminal)
sys.stdout.write(' \b')
pipe_out, pipe_in = os.pipe()
# save a copy of stdout
stdout = os.dup(1)
# replace stdout with our write pipe
os.dup2(pipe_in, 1)

# check if we have more to read from the pipe
def more_data():
        r, _, _ = select.select([pipe_out], [], [], 0)
        return bool(r)

# read the whole pipe
def read_pipe():
        out = ''
        while more_data():
                out += os.read(pipe_out, 1024)

        return out

# testing print methods
import ctypes
libc = ctypes.CDLL('libc.so.6')

print 'This text gets captured by myStdOut'
libc.printf('This text fails to be captured by myStdOut\n')

# put stdout back in place 
os.dup2(stdout, 1)
print 'Contents of our stdout pipe:'
print read_pipe()

Simplest example, because this question in google top.最简单的例子,因为这个问题在 google top 中。

import os
from ctypes import CDLL

libc = CDLL(None)
stdout = os.dup(1)
silent = os.open(os.devnull, os.O_WRONLY)
os.dup2(silent, 1)
libc.printf(b"Hate this text")
os.dup2(stdout, 1)

If the data the native process writes are large (larger than pipe buffer), the native program would block until you make some space in the pipe by reading it.如果本机进程写入的数据很大(大于管道缓冲区),本机程序将阻塞,直到您通过读取在管道中腾出一些空间。

The solution from lunixbochs, however, needs the native process to finish before it starts reading the pipe.然而,来自 lunixbochs 的解决方案需要本机进程在开始读取管道之前完成。 I improved the solution so that it reads the pipe in parallel from a separate thread.我改进了解决方案,使其从单独的线程并行读取管道。 This way you can capture output of any size.通过这种方式,您可以捕获任何大小的输出。

This solution is also inspired by https://stackoverflow.com/a/16571630/1076564 and captures both stdout and stderr:此解决方案也受到https://stackoverflow.com/a/16571630/1076564 的启发,并同时捕获 stdout 和 stderr:

class CtypesStdoutCapture(object):
    def __enter__(self):
        self._pipe_out, self._pipe_in = os.pipe()
        self._err_pipe_out, self._err_pipe_in = os.pipe()
        self._stdout = os.dup(1)
        self._stderr = os.dup(2)
        self.text = ""
        self.err = ""
        # replace stdout with our write pipe
        os.dup2(self._pipe_in, 1)
        os.dup2(self._err_pipe_in, 2)
        self._stop = False
        self._read_thread = threading.Thread(target=self._read, args=["text", self._pipe_out])
        self._read_err_thread = threading.Thread(target=self._read, args=["err", self._err_pipe_out])
        self._read_thread.start()
        self._read_err_thread.start()
        return self

    def __exit__(self, *args):
        self._stop = True
        self._read_thread.join()
        self._read_err_thread.join()
        # put stdout back in place
        os.dup2(self._stdout, 1)
        os.dup2(self._stderr, 2)
        self.text += self.read_pipe(self._pipe_out)
        self.err += self.read_pipe(self._err_pipe_out)

    # check if we have more to read from the pipe
    def more_data(self, pipe):
        r, _, _ = select.select([pipe], [], [], 0)
        return bool(r)

    # read the whole pipe
    def read_pipe(self, pipe):
        out = ''
        while self.more_data(pipe):
            out += os.read(pipe, 1024)

        return out

    def _read(self, type, pipe):
        while not self._stop:
            setattr(self, type, getattr(self, type) + self.read_pipe(pipe))
            sleep(0.001)

    def __str__(self):
        return self.text

# Usage:

with CtypesStdoutCapture as capture:
  lib.native_fn()

print(capture.text)
print(capture.err)

There is a Python project called Wurlitzer that very elegantly solves this problem.有一个名为 Wurlitzer 的 Python 项目非常优雅地解决了这个问题。 It's a work of art and deserves to be one of the top answers to this question.这是一件艺术品,值得成为这个问题的最佳答案之一。

https://github.com/minrk/wurlitzer https://github.com/minrk/wurlitzer

https://pypi.org/project/wurlitzer/ https://pypi.org/project/wurlitzer/

pip install wurlitzer
from wurlitzer import pipes

with pipes() as (out, err):
    call_some_c_function()

stdout = out.read()
from io import StringIO
from wurlitzer import pipes, STDOUT

out = StringIO()
with pipes(stdout=out, stderr=STDOUT):
    call_some_c_function()

stdout = out.getvalue()
from wurlitzer import sys_pipes

with sys_pipes():
    call_some_c_function()

And the most magical part: it supports Jupyter:最神奇的部分:它支持 Jupyter:

%load_ext wurlitzer

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM