Library interfaces
After loading a shared library, its procedures must be interfaced to make
them available from the Python side. Shared libraries are loaded using the
load() function, which returns a SharedLibrary instance.
Procedures can be interfaced using the interface() function.
Now follows a minimal example of C code that we can interface from Python, with arguments chosen to cover typical cases. Function and class documentation just below.
// Sample structure that stores seismic station information
typedef struct {
double longitude;
double latitude;
double elevation;
char network[2];
char station[5];
_Bool operational; // OCD safety (it would get padded anyways)
} t_Station;
// Sample function prototype that will be interfaced
int do_something (t_Station *station, double *data, unsigned int nsamp);
When building an interface, we will assume that data is a NumPy array.
import fancytypes as ft
# This ctypes struct is equivalent to the one above
@ft.cstruct
class Station:
longitude : ft.real64
latitude : ft.real64
elevation : ft.real64
network : ft.character[2]
station : ft.character[5]
operational : ft.logical
# We load the library first
library = ft.load(<path_to_library>)
# Now we can interface any procedure we need
library.do_something = ft.interface(ft.pointer(Station),
ft.pointer(ft.real64),
ft.uint32,
returns=ft.int32)
# Create some empty dummy variables
import numpy as np
nsamp = 100
data = np.zeros(100, dtype=np.double)
station = Station()
# Call the procedure
res = library.do_something(station, ft.real64.array(data), nsamp)
# If "data" is always a NumPy array, we can rely on their API instead
library.do_something = ft.interface(ft.pointer(Station),
ft.nparray(ft.real64), # nparray instead of pointer
ft.uint32,
returns=ft.int32)
# The call will now look cleaner but less explicit
res = library.do_something(station, data, nsamp)
It is important to note that ctypes will always try to make any necessary
type conversions, such as passing a pointer even if we make the call using
the variable. This happens in the example above when we pass station
instead of ft.cpointer(station). NumPy arrays require us to go through
the array method of the corresponding type class unless we rely on the
numpy.ctypeslib API.
Class to store loaded shared libraries and their interfaces. Use the
load()function to load them. Any procedures from the library must first be explicitly interfaced to make them callable from Python. Interfaces can be built by assigning a two-tuple containing argument and return types to an attribute with the same name as the procedure.<library>.<procedure> = ((<arg_type_1>, ..., <arg_type_n>), <res_type>)
Use the
interface()function to make the right side more readable. These assigned attributes are callable and will run the procedure afterctypesdoes the corresponding checks and attempts to make any necessary conversions.<res> = <library>.<procedure>(<arg_1>, ..., <arg_n>)
Procedures can have
voidreturns ifNoneis assigned as return type on the interface.Warning
Procedure interfaces are not guaranteed to match on both sides, and there is no way to check for it. Users are responsible of ensuring the arguments declared are correct for proper functionality. Wrong interfaces will result in unintended behaviour at best and are likely to crash the Python interpreter.
Return a
SharedLibraryinstance that stores a loaded shared library.- Parameters:
path (
strorpathlib.Path) – Path to shared library- Returns:
Loaded shared library
- Return type:
Helper function to declare procedure interfaces on instances of
SharedLibraryin a more readable way.((<arg_type_1>, ..., <arg_type_n>), <res_type>) | V interface(<arg_type_1>, ..., <arg_type_2>, returns=<res_type>)
- Parameters:
args – Argument types
returns – Return type, default is
None
- class LibraryError[source]
Exception raised by the package for issues related to shared libraries.