or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
xxxxxxxxxx
Importing a shared library
tags:
book
ctypes
The ctypes library is part of the Python standard libary and provides convenient access to C compatible data types and loading of shared libraries. C datatypes like
c_int
,c_double
,c_long
etc. are supported and libraries are loaded withCDLL
. A simple example on the use ofctypes
was given in the Introduction.Working with NumPy arrays
The most convenient way to work with arrays is through numpy.ctypeslib. In particular, it includes the functions
as_array
, which converts a C array to a NumPyndarray
, andas_ctypes
, which converts anndarray
to a C array. Here is an example passing a NumPy array to a Fortran subroutine that sums the column values:Note the explicit declaration of the datatypes for the Numpy arrays which avoids NumPy guessing the type from the input values. In accordance with C standard, arrays need to be passed by reference rather than by value. That is handled by the
byref
funciton ofctypes
. Here is the underlying Fortran module:Derived types
ctypes
interfaces with C structs via Python classes that subclassctypes.Structure
. In this example we create two two-dimensional points and pass them to the functionadd_points
which sums the x and y components:Notice that we needed to specify
Point
as the the return type of the function with the.restype
attribute. Here is the Fortran module:cffi
cffi is a C Foreign Function Interface for Python. It is more flexible than
ctypes
when writing more complex interfaces. Here is an example using themod_sum.so
shared library given above:First, a
FFI
object is created to handle all the interactions with the library. The library is opened withFFF.dlopen
. One key difference betweenctypes
andcffi
concerns the need to include C declarations usingcdef
. This method takes a C code string as it would be given in a C header file. In fact, if you already have a header file with the following contents,it can be conveniently fed directly into
cdef
:Another difference is that
FFI.cast
is used to cast Python types as C types. The first argument tocast
is a C code string describing the type. Pointers are described by using the<type> *
syntax, and there is therefore no need for a separatebyref
function as inctypes
.The
pycparser
module used to read the header file has some limitations, headers using#define
or#ifdef
preprocessor logic cannot be parsed directly and have to be preprocessed manually first.Working with NumPy arrays
A NumPy array can be converted to a C array in two different ways:
Converting a C array to a NumPy array is a bit more involved than with ctypes and is done in two steps.
First you create a buffer with
FFI.buffer
and then you read that buffer withnumpy.frombuffer
. Note that you need knowledge of the array shape to (1) calculate the size of the buffer, and (2) reshape the final NumPy array. This procedure is a bit cumbersome to write everytime it would be needed and is better made into a function.Derived types
A derived type is declared with
cdef
in the usual C way. Here an example for the Point struct above:In this example we are able to work directly with the resulting C Point object, but for more complicated use cases we should construct a wrapper class in Python:
We can now wrap our C Point as a Python Point:
Derived types, which cannot be made intercompatible with
bind(C)
attribute, can still be made accessible as opaque data pointer in Python.Which we can use in Python as
The
typedef
declares an opaque pointer to the Fortran data. While we cannot directly interact with the content of the container in Python anymore, it allows to export almost any data type available in Fortran. This can become especially useful to make class polymorphic objects with a well-defined API available in Python.Since we are using
pointer
attributes on the library side, we have to explicitly free the memory after we are done with the data. To create a more pythonic way to work with the container we would wrap it in a class which takes care of the details of the memory managementThis class allows us to use our object in a with context
Garbage collection
Resources allocated in the library have to be freed explicitly in the library as well. The
cffi
module provides a garbage collection mechanism to automatically associate a deconstructor with an object.We can now simply use the object like any other Python object and rely on the garbage collector to free the memory allocation.
Different cffi modes
Here we have been using
cffi
in the ABI mode by accessing the library at the binary level. In the API mode, we would instead have compiled C code to handle the access for us. We're also using the in-line mode, where everything is set up every time the Python code is imported, rather than the out-of-line mode, where a separate module is set up once and then can be imported. These differences are important for optimizing performance when building and packaging applications. More about that in […].Combining with setuptools
The
cffi
out-of-line API builder can be readily combined with setuptools. The ffibuilder is defined in a separatebuild.py
script and can be added withThe
build.py
script is used to define out-of-line API mode forcffi
. A simple FFI builder is given hereRunning the script outside of setup creates the C source code of the extension module and allows in principle to compile it yourself. However, it is easier to let setuptools take care of compiling and linking against your Python installation.
Finding a library with pkg-config
The pkg-conf dependency is a frequently used format to describe how to build against an existing project. The pc-file format is supported in Python with the
pkgconfig
module which allows us to easily import any library. Thepkgconfig
package will be asetup_requires
dependency in oursetup.py
orpyproject.toml
. A usual pc-file looks like thisThe pc-file contains the information required to compile and link our library from any other project. This information can be readily used in our FFI builder.
We also added a step here to preprocess the header file in case it contains
#define
or#include
preprocessor which cannot be handled by thepycparser
module.Cython
Cython is a Python extension that can compile Python code as faster C code, but it also has capabilities for loading shared libraries. Getting a working Cython interface isn't as easy as with
ctypes
orcffi
, but it can be used to build Python modules automatically with setuptools as we will investigate in the Building with setuptools chapter. To use Cython to load the shared library, we first need three files:.pxd
Cython declaration file.pyx
Cython source file.h
C declaration fileImportantly, the
.pxd
and.pyx
files need to have different names and be in the same directory. The.pxd
file resembles the C header file and contains declarations for the code that you want to access.Then we write the
.pyx
Cython source file that exposes a Python function:The syntax of Cython is very close to Python.
cimport
is used to import the.pxd
file andcdef
is used to define C variables. The&
operator is used to pass varibles by reference, and will be discussed more below.Building the Cython module
The next step is to build a Python module, and for that we will create a
setup.py
file:This tells setuptools to create a C extension module with the name
py_mod_sum
from the Cython source filemod_sum.pyx
. Themod_sum
library is used and setuptools will search for it in the regular paths as well as the directories inlibrary_dirs
. Here we direct setuptools to look for the library in the current directory (the same as weresetup.py
is). We now need to make sure that our shared library file is calledlibmod_sum.so
as the install process will automatically prepend "lib" to the name when searching. We then build the module with:That generates a file in the current directory with the name
py_mod_sum.cpython-39-darwin.so
or similar, depending on operating system and version of Python. We can now import this file as a module directly into Python and use it:Working with NumPy arrays
The recommended way of working with NumPy arrays in Cython is through memory views. A memory view is created via the following syntax:
As usual, arrays should be passed to C function by reference. In Cython this is achieved by using the
&
operator and the giving first element in the array:There are many more details on working with NumPy arrays in the Cython documentation.
Derived types
Starting from our point example, we write the
.pxd
file:which closely mirrors our C header file:
To work with the C point struct in Python, we construct a wrapper class and a Python function to work with this class:
Cython classes are called extension types and are defined with the
cdef class
syntax. We declare that this class should hold an attribute_point
which holds the C point struct, and the__cinit__
constructor method is used to initialize this object. We also define two properties which allows us to access the attributes of the C struct. Finally, theadd_points
function is a Python wrapper for our C function. As the C function returns a C struct, we need to convert that to the Python class before returning to the user.As before, we need to build with a
setup.py
:After building with
python setup.py build_ext --inplace
, we can now import the class and function and work with them in Python.Wrapping the Python C interface
It's often better to hide away the technicalities of the Python-Fortran interface behind regular Python function and classes. This is actually what we did with Cython above. The end user then does have to worry about converting datatypes, memory management etc. Here is an example with
ctypes
:This function (1) converts the input into a C contiguous NumPy array, (2) converts the Numpy array to a C array, (3) creates an empty array to hold the result of the calculation, (4) runs the subroutine to modify the result array that is then returned. The end user is completely oblivious that any Fortran code has been run behind the scenes.