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

时间:2012-02-28 19:42:25

标签: python

我正在使用通过ctypes模块调用的共享库。我想将与此模块关联的stdout重定向到我可以在程序中访问的变量或文件。但是,ctypes使用sys.stdout中的单独stdout。

我将用libc演示我遇到的问题。如果有人复制并粘贴代码,他们可能需要更改第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()

有没有什么方法可以捕获与ctypes加载的共享库相关联的stdout?

3 个答案:

答案 0 :(得分:5)

我们可以使用os.dup2()os.pipe()用我们可以自己读取的管道替换整个stdout文件描述符(fd 1)。你可以用stderr(fd 2)做同样的事情。

此示例使用select.select()查看管道(我们的假stdout)是否有等待写入的数据,因此我们可以安全地打印它而不会阻止执行我们的脚本。

由于我们正在完全替换此进程和任何子进程的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()

答案 1 :(得分:2)

最简单的例子,因为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)

答案 2 :(得分:0)

如果本机进程写入的数据很大(大于管道缓冲区),本机程序将阻塞,直到您通过读取在管道中腾出一些空间。

然而,来自 lunixbochs 的解决方案需要本机进程在开始读取管道之前完成。我改进了解决方案,使其从单独的线程并行读取管道。这样您就可以捕获任何大小的输出。

这个解决方案也受到 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)