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 SharedLibrary[source]

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 after ctypes does the corresponding checks and attempts to make any necessary conversions.

<res> = <library>.<procedure>(<arg_1>, ..., <arg_n>)

Procedures can have void returns if None is 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.

load(path)[source]

Return a SharedLibrary instance that stores a loaded shared library.

Parameters:

path (str or pathlib.Path) – Path to shared library

Returns:

Loaded shared library

Return type:

SharedLibrary

interface(*args, returns=None)[source]

Helper function to declare procedure interfaces on instances of SharedLibrary in 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.