For some other reasons, the c++ shared library I used outputs some texts to standard output. In python, I want to capture the output and save to a variable.
Python's sys.stdout object is simply a Python wrapper on top of the usual stdout file descriptor—changing it only affects the Python process, not the underlying file descriptor. Any non-Python code, whether it be another executable which was exec'ed or a C shared library which was loaded, won't understand that and will continue using the ordinary file descriptors for I/O.
So, in order for the shared library to output to a different location, you need to change the underlying file descriptor by opening a new file descriptor and then replacing stdout using os.dup2(). You could use a temporary file for the output, but it's a better idea to use a pipe created with os.pipe(). However, this has the danger for deadlock, if nothing is reading the pipe, so in order to prevent that we can use another thread to drain the pipe.
Below is a full working example which does not use temporary files and which is not susceptible to deadlock (tested on Mac OS X).
C shared library code:
// test.c
#include
void hello(void)
{
printf("Hello, world!\n");
}
Compiled as:
$ clang test.c -shared -fPIC -o libtest.dylib
Python driver:
import ctypes
import os
import sys
import threading
print 'Start'
liba = ctypes.cdll.LoadLibrary('libtest.dylib')
# Create pipe and dup2() the write end of it on top of stdout, saving a copy
# of the old stdout
stdout_fileno = sys.stdout.fileno()
stdout_save = os.dup(stdout_fileno)
stdout_pipe = os.pipe()
os.dup2(stdout_pipe[1], stdout_fileno)
os.close(stdout_pipe[1])
captured_stdout = ''
def drain_pipe():
global captured_stdout
while True:
data = os.read(stdout_pipe[0], 1024)
if not data:
break
captured_stdout += data
t = threading.Thread(target=drain_pipe)
t.start()
liba.hello() # Call into the shared library
# Close the write end of the pipe to unblock the reader thread and trigger it
# to exit
os.close(stdout_fileno)
t.join()
# Clean up the pipe and restore the original stdout
os.close(stdout_pipe[0])
os.dup2(stdout_save, stdout_fileno)
os.close(stdout_save)
print 'Captured stdout:\n%s' % captured_stdout