In python, how to capture the stdout from a c++ shared library to a variable

前端 未结 7 1924
别那么骄傲
别那么骄傲 2020-11-30 04:52

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.

7条回答
  •  鱼传尺愫
    2020-11-30 05:33

    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
    

提交回复
热议问题