How to call a C function from Python.

Software Engineering c python

Need to speed up things by calling a C function from your Python script? Check this out.

Andrea Bonvini https://github.com/andreabonvini
03-03-2021

This will be a really short blogpost which mainly serves as a personal reminder on how to call a C function from Python… the title is pretty self-explanatory.

I will show how to call some really simple c functions wich return a double or an array (which will be a np.array in Python).

Here we have our simple c file cfunctions.c:

#include<stdlib.h>

long factorial(int n){
    // 
    long res = 1;
    if (n <= 0) {
        return -1;
    }
    else {
        for (long i = 1; i <= n; i++) {
           res *= i;
        }
    }
    return res;
}

double dotproduct(int dim, double a[dim], double b[dim]){
    // Compute the dot product between two vectors...
    // e.g. a = [1,2,3,4] , b = [4,3,2,1]
    // res = 1*4 + 2*3 + 3*2 + 4*1 = 4 + 6 + 6 + 4 = 20
    double res = 0;   
    for(int i = 0; i < dim; i++){
        res = res + a[i]*b[i];
    }
    return res;
}

double * elementwiseproduct(int dim, double a[dim], double b[dim]){
    // Compute the elementiwise product between two vectors...
    // e.g. a = [1,2,3,4] , b = [4,3,2,1]
    // res will point to an array such as [1*4, 2*3, 3*2, 4*1] = [4,6,6,4]
    double * res = (double *) malloc(sizeof(double) * dim);
    for(int i = 0; i < dim; i++){
        res[i] = a[i]*b[i];
    }    
    return res;
}

We then create a shared object witht the following command:

cc -fPIC -shared -o cfunctions.so cfunctions.c

Then we can write a Python script py_cfunctions.py such as:

import ctypes
import numpy as np

def factorial(num: int):
    py_cfunctions.factorial.restype = ctypes.c_long
    return py_cfunctions.factorial(num)

def dotproduct(dim: int, a: np.array, b: np.array):
    # Convert np.array to ctype doubles
    a_data = a.astype(np.double)
    a_data_p = a_data.ctypes.data_as(c_double_p)
    b_data = b.astype(np.double)
    b_data_p = b_data.ctypes.data_as(c_double_p)
    py_cfunctions.dotproduct.restype = ctypes.c_double
    # Compute result...
    return py_cfunctions.dotproduct(dim,a_data_p,b_data_p)
    
def elementwiseproduct(dim: int, a: np.array, b: np.array):
    # Convert np.array to ctype doubles
    a_data = a.astype(np.double)
    a_data_p = a_data.ctypes.data_as(c_double_p)
    b_data = b.astype(np.double)
    b_data_p = b_data.ctypes.data_as(c_double_p)

    py_cfunctions.elementwiseproduct.restype = np.ctypeslib.ndpointer(dtype=ctypes.c_double,shape=(dim,))
    # Compute result...
    return py_cfunctions.elementwiseproduct(dim,a_data_p,b_data_p)

# so_file genreated with:
# cc -fPIC -shared -o cfunctions.so cfunctions.c

so_file = 'MY_PATH/cfunctions.so'
py_cfunctions = ctypes.CDLL(so_file)
c_double_p = ctypes.POINTER(ctypes.c_double)
py_cfunctions.factorial.argtypes = [ctypes.c_int] 
py_cfunctions.elementwiseproduct.argtypes = [ctypes.c_int, c_double_p, c_double_p]
py_cfunctions.dotproduct.argtypes= [ctypes.c_int, c_double_p, c_double_p]

And that’s it! You now can import py_cfunctions and call factorial(), dotproduct() and elementwiseproduct().

Citation

For attribution, please cite this work as

Bonvini (2021, March 3). Last Week's Potatoes: How to call a C function from Python.. Retrieved from https://lastweekspotatoes.com/posts/2021-07-21-how-to-call-a-c-function-from-python/

BibTeX citation

@misc{bonvini2021how,
  author = {Bonvini, Andrea},
  title = {Last Week's Potatoes: How to call a C function from Python.},
  url = {https://lastweekspotatoes.com/posts/2021-07-21-how-to-call-a-c-function-from-python/},
  year = {2021}
}