Introduction
Suppose I have this C code:
#include <stdio.h>
// Of course, these functions are simplified for the purposes of this question.
// The actual functions are more complex and may receive additional arguments.
void printout() {
puts("Hello");
}
void printhere(FILE* f) {
fputs("Hello\n", f);
}
That I'm compiling as a shared object (DLL): gcc -Wall -std=c99 -fPIC -shared example.c -o example.so
And then I'm importing it into Python 3.x running inside Jupyter or IPython notebook:
import ctypes
example = ctypes.cdll.LoadLibrary('./example.so')
printout = example.printout
printout.argtypes = ()
printout.restype = None
printhere = example.printhere
printhere.argtypes = (ctypes.c_void_p) # Should have been FILE* instead
printhere.restype = None
Question
How can I execute both printout()
and printhere()
C functions (through ctypes
) and get the output printed inside the Jupyter/IPython notebook?
If possible, I want to avoid writing more C code. I would prefer a pure-Python solution.
I also would prefer to avoid writing to a temporary file. Writing to a pipe/socket might be reasonable, though.
The the expected state, the current state
If I type the following code in one Notebook cell:
print("Hi") # Python-style print
printout() # C-style print
printhere(something) # C-style print
print("Bye") # Python-style print
I want to get this output:
Hi
Hello
Hello
Bye
But, instead, I only get the Python-style output results inside the notebook. The C-style output gets printed to the terminal that started the notebook process.
Research
As far as I know, inside Jupyter/IPython notebook, the sys.stdout
is not a wrapper to any file:
import sys
sys.stdout
# Output in command-line Python/IPython shell:
<_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
# Output in IPython Notebook:
<IPython.kernel.zmq.iostream.OutStream at 0x7f39c6930438>
# Output in Jupyter:
<ipykernel.iostream.OutStream at 0x7f6dc8f2de80>
sys.stdout.fileno()
# Output in command-line Python/IPython shell:
1
# Output in command-line Jupyter and IPython notebook:
UnsupportedOperation: IOStream has no fileno.
Related questions and links:
- Python ctypes: Python file object <-> C FILE *
- Python 3 replacement for PyFile_AsFile
- Using fopen, fwrite and fclose through ctypes
- Python ctypes DLL stdout
- Python: StringIO for Popen - Workaround for the lack of
fileno()
inStringIO
, but only applies tosubprocess.Popen
.
The following two links use similar solutions that involve creating a temporary file. However, care must be taken when implementing such solution to make sure both Python-style output and C-style output gets printed in the correct order.
- How do I prevent a C shared library to print on stdout in python?
- Redirecting all kinds of stdout in Python
Is it possible to avoid a temporary file?
I tried finding a solution using C open_memstream()
and assigning the returned FILE*
to stdout
, but it did not work because stdout
cannot be assigned.
Then I tried getting the fileno()
of the stream returned by open_memstream()
, but I can't because it has no file descriptor.
Then I looked at freopen()
, but its API requires passing a filename.
Then I looked at Python's standard library and found tempfile.SpooledTemporaryFile()
, which is a temporary file-like object in memory. However, it gets written to the disk as soon as fileno()
is called.
So far, I couldn't find any memory-only solution. Most likely, we will need to use a temporary file anyway. (Which is not a big deal, but just some extra overhead and extra cleanup that I'd prefer to avoid.)
It may be possible to use os.pipe()
, but that seems difficult to do without forking.