Package: cpptypes
Generate ctypes wrappersOverviewThis script automatically creates the C++ and Python-side wrappers forctypesbindings.
Specifically, we fillrestypeandargtypesbased on the C++ function signature and we create wrappers to handle C++ exceptions.
We were inspired by theRcpp::compile()function, which does the same for C++ code in R packages.
The aim is to avoid errors from manual binding when developingctypes-based Python packages.Installcpptypesis published toPyPI:pipinstallcpptypesQuick startTo use, add an// [[export]]tag above the C++ function to be exported to Python.// [[export]]intmultiply(inta,doubleb){returna*b;}We assume that all C++ code is located within a single directorysrc.
We then run thecpptypescli provided by this package:cpptypessrc/--pybindings.py--cppbindings.cppDevelopers should addbindings.cppto theExtensionsources in theirsetup.py.
The exported function itself can then be used in Python code with:from.bindingsimport*ascxxcxx.multiply(1,2)Handling pointersPointers to base types (orvoid) are supported and will be bound with the appropriatectypespointer type.//[[export]]void*create_complex_object(int*my_int,double*my_dbl){returnreinterpret_cast<void*>(newSomething(my_int,my_dbl));}And then, in Python (assuming we called our Python bindings filebindings.py):importctypesx=ctypes.c_int(100)y=ctypes.c_double(200)from.bindingsimport*ascxxptr=cxx.create_complex_object(ctypes.byref(x),ctypes.pointer(y))Void pointers are represented as a (usually 64-bit) integer in Python that can be passed back to C++.
Remember to castvoid*back to the appropriate type before doing stuff with it!
(For simplicity, we do not support arbitrary pointer types as otherwise we would need to include the header definitions in thebindings.cppfile and that would be tedious to track.)If you want thectypesbindings to treat pointers to base types asvoid*, you can tag the argument withvoid_p.
This means that you can directly pass integer addresses tomy_intandmy_dblin Python rather than casting them to actypes.POINTERtype.//[[export]]void*create_complex_object2(int*my_int/** void_p */,double*my_dbl/** void_p */){returnreinterpret_cast<void*>(newSomething(my_int,my_dbl));}Note on tags:Arguments can be tagged with a/** xxx yyy zzz */comment, consisting of space-separated tags that determine how the type should be handled in the wrappers.
(The double**is important to distinguish from non-tag comments.)
The tag-containing comment can be inserted anywhere in or next to the argument, e.g., before the type, between the type and the name, after the name but before the comma/parenthesis.
The result type for the function can also be tagged in the same manner.Handling NumPy arraysIf we know a certain pointer is derived from a NumPy array, we can add thenumpytag to automate type checking and address extraction.//[[export]]void*create_complex_object3(int32_t*my_int/** numpy */,double*my_dbl/** numpy */){returnreinterpret_cast<void*>(newSomething(my_int,my_dbl));}Then, in Python, we can just pass the arrays directly to the bound function:importnumpyx=numpy.random.rand(1000).astype(numpy.int32)y=numpy.random.rand(1000).astype(numpy.double)cxx.create_complex_object3(x,y)This will check that the NumPy arrays correspond to the specified type before calling the C++ function.
It is best to use fixed-width integers rather than relying on machine-dependent aliases likeint,short, etc.The wrapper functions will also check that the arrays are contiguous in memory.
If you want to support non-contiguous arrays, add thenon_contigtag to the relevant arguments.Thenumpytag is only relevant to function arguments and not return values.
Use thenumpy.ctypeslib.as_array()function to convert actypespointer to anumpyarray of the relevant type.Known limitationsNot allctypestypes are actually supported, mostly out of laziness.