sometimes we need to call c function from python; one way to do this is to write a c extension module wrapping the c function; this requires a few lines of code; but once we understand the basic steps, this is straightforward; because this is c extension module, it only works with cpython;

the concept

the python api defines a set of functions, macros and variables that provide access to most aspects of the python run-time system; we can use the python api to unwrap python objects into c values, call c function with these c values, then wrap the result c value in a python object and return it;

to use the python api, include header file Python.h;

the c function

for brevity, we use this example c function:

static int add(int a, int b)
{
    return a + b;
}

the c extension module

for brevity, we name this c extension module foo and its filename foomodule.c; now we start writing this module:

  1. include python api header file:

    #include <Python.h>
    

    this header file defines functions, macros, variables etc. whose names start with Py or PY;

  2. put the c function we want to call in this module:

    static int add(int a, int b)
    {
        return a + b;
    }
    

    we use static to hide its name from global; this is not a requirement, but makes things clean; in fact, as we shall see, all functions in this file, except the init function, are static; again, this is not a requirement;

  3. write a python method wrapping this c function:

    static PyObject *
    foo_add(PyObject *self, PyObject *args)
    {
        int a, b;
        if (!PyArg_ParseTuple(args, "ii", &a, &b))
            return NULL;
        int c = add(a, b);
        return PyLong_FromLong(c);
    }
    

    this method parses arguments to get their c values, then call the c function on them, finally return the result as a python value;

  4. now we have got the method, but still need some boilerplates to finish the module; specifically, we need to put this method in a method table, then define the module using a data structure which contains this method table:

    static PyMethodDef FooMethods[] = {
        { "add", foo_add, METH_VARARGS, "Add two numbers." },
        { NULL,  NULL,    0,            NULL               }
    };
    

    here, this method table has only 1 entry, plus a sentinel; the entry fields are: method name, c function for this method, calling convention for the c function, method docstring;

    static struct PyModuleDef FooModule = {
        PyModuleDef_HEAD_INIT,
        "foo",
        "foo doc",
        -1,
        FooMethods
    };
    

    the module definition structure includes a head, module name, module docstring, per-interpreter state size, method table;

  5. we are almost done, but there is a final job: write an init function for this module:

    PyMODINIT_FUNC
    PyInit_foo(void)
    {
        return PyModule_Create(&FooModule);
    }
    

    this is the entrance to this module and must not be static; it creates a module object using the module definition structure;

put these snippets together to get the entire module file foomodule.c:

#include <Python.h>

//  c function: `add`
static int add(int a, int b)
{
    return a + b;
}

//  python method: `foo.add`
static PyObject *
foo_add(PyObject *self, PyObject *args)
{
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b))
        return NULL;
    int c = add(a, b);
    return PyLong_FromLong(c);
}

//  method table
static PyMethodDef FooMethods[] = {
    //  python method: `foo.add`
    { "add", foo_add, METH_VARARGS, "Add two numbers." },
    //  sentinel
    { NULL,  NULL,    0,            NULL               }
};

//  module definition structure
static struct PyModuleDef FooModule = {
    PyModuleDef_HEAD_INIT,
    //  module name
    "foo",
    //  module docstring
    "foo doc",
    //  size of per-interpreter state: -1 if the
    //  module keeps state in global variables
    -1,
    //  method table
    FooMethods
};

//  module initialization function
PyMODINIT_FUNC
PyInit_foo(void)
{
    return PyModule_Create(&FooModule);
}

build the module

we can build the extension module using setuptools, saving great time dealing with compilers and linkers by ourselves; to do so, we need to write a setup.py file like this:

from setuptools import Extension
from setuptools import setup

setup(
    name='foo',
    ext_modules=[Extension('foo',['foomodule.c'])],
)

then run this command:

python3 setup.py build_ext

this calls compiler and linker to build the extension module into a dynamically linked library (dll, .so on linux); this dll lives in the build dir; to use this dll, make sure its path can be founed by the python interpreter, then just import it using its name (foo):

import foo
print(foo.add(3, 4))

output:

7