(1) Did I use extern "C" correctly?
Don't put extern ”C"
around the method definitions.
The purpose of extern "C"
is to tell the compiler that some function needs to be accessible as a C function—that is, called by C code, or by ctypes
or some other library that loads and calls functions out of shared objects.
The only function you want to call through ctypes
is printStuff
, so only that function should be extern "C"
.
Trying to extern "C"
the methods will probably harmlessly do nothing with g++
(they'll still end up named something like __ZN3foo3barEv
in the export table), but you may get a warning, or even a non-running library, with a different compiler.
(2) How should I compile these three files into a shared library? I use g++.
You probably want to write a Makefile
or use some other build system, but if you want to do it manually:
g++ -shared -o foo.so foo.cpp mainFunc.cpp
Depending on your platform, you may also need to manually specify -fPIC
or -fpic
.1
If you want to do the steps separately:
g++ -c foo.cpp
g++ -c mainFunc.cpp
g++ -shared -o foo.so foo.o mainFunc.o
In this case, if a PIC flag is needed, it goes on the first two lines.
And now, to use it from Python:
$ python3
>>> import ctypes
>>> foo = ctypes.cdll.LoadLibrary('foo.so')
>>> foo.printStuff(10)
Printing...
15
>>> foo.printStuff("abc")
Printing...
116480373
Of course it's generally better to set the argtypes
and restype
instead of making ctypes
guess. It guessed that the Python int
10 should be converted to the C int
10, which worked great, but it also guessed that the Python str
"abc" should be converted to a C const char *
, and you end up with the low 32 bits of the pointer to the string buffer being used as an int
. So:
>>> foo.printStuff.argtypes = [ctypes.c_int]
>>> foo.printStuff.restype = None
>>> foo.printStuff(10)
Printing...
15
>>> foo.printStuff("abc")
ArgumentError: argument 1: <class 'TypeError'>: wrong type
And you may want to write a foo.py
that wraps that up:
import ctypes
foo = ctypes.cdll.LoadLibrary('foo.so')
foo.printStuff.argtypes = [ctypes.c_int]
foo.printStuff.restype = None
printStuff = foo.printStuff
1. From quick tests, I didn't need it for x86_64 macOS, x86 macOS, or x86_64 Linux, but PowerPC Linux needs -fpic
and ARM64 Linux needs -fPIC
. However, I didn't actually run all of those. And, other than macOS (where I made sure to test with Homebrew gcc 8.2 and Apple Clang 9.1) I don't know which compiler version I had.