Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Minimal example of wrapping C code with Cython- passing int and struct

The code that I show below works, but I am not exactly sure why.

I am using:

  • Mac OSX 10.8.5
  • brewed Python 2.7.5
  • Cython 0.20.2

This code is taken primarily from this video tutorial and git page, but unfortunately did not work for me 'out of the box'.

The goal of this wrapping to provide access through Python to a trivial C function, which accepts either integers or a structure of integers and adds them together.

There are 5 files needed for this, described below:

  • adder.c: C code of the two adder functions: add (scalar inputs) and pair_add (struct inputs)
  • adder.h: header file for adder.c
  • c_adder.pxd: a cython header file- essentially tells cython which parts of the main header file to pay attention to
  • cy_adder.pyx: the cython code used to define both functions in the python namespace
  • setup.py: a distutils file which performs the compiling of the cython code

and the process produces two files

  • c_adder.c: an intermediate cython c file
  • c_adder.so: the python module which can be imported into a namespace

The input files are below:

adder.c

#include <stdlib.h>
#include "adder.h"

int
pair_add(PAIR * ppair) {
    return ppair->x + ppair->y;
}

int
add(int x, int y) {
    return x + y;
}

adder.h

typedef struct {
    int x;
    int y;
} PAIR;

int pair_add(PAIR * ppair);
int add(int, int);

c_adder.pxd

cdef extern from "adder.h":

    ctypedef struct PAIR:
        int x
        int y


    int add(int x, int y)
    int pair_add(PAIR * ppair)

cy_adder.pyx

cimport c_adder

def add(x, y):
    return c_adder.add(x, y)

def pair_add(x, y):
    cdef c_adder.PAIR pair
    pair.x = x
    pair.y = y
    return c_adder.pair_add(&pair)

setup.py

from setuptools import setup
from setuptools.extension import Extension
from Cython.Distutils import build_ext
from Cython.Build import cythonize


ext_modules = cythonize([Extension("cy_adder", ["cy_adder.pyx",'adder.c'])])


setup(
  name = 'Hello world app',
  cmdclass = {'build_ext': build_ext},
  ext_modules = ext_modules
)

I was able to successfully create a .so file by running

$ python setup.py build_ext --inplace

In the same directory as the above files. It is then possible to load cy_adder into the namespace of a python interpreter.


The question

In the setup declaration, I have included adder.c as an auxiliary module.

If I don't do this I get the following error when I import the .so file:

ImportError: dlopen(./cy_adder.so, 2): Symbol not found: _add
  Referenced from: ./cy_adder.so
  Expected in: flat namespace
 in ./cy_adder.so

Is there a step that I am missing which would mean that I don't need to explicitly pass adder.c along with my setup command? Am I opening myself up to some potential instability by doing this?

like image 757
Solar_code Avatar asked Jul 08 '14 00:07

Solar_code


People also ask

What is Cimport Cython?

The cimport statement is used in a definition or implementation file to gain access to names declared in another definition file. Its syntax exactly parallels that of the normal Python import statement. When pure python syntax is used, the same effect can be done by importing from special cython.


1 Answers

The adder.c file needs to be referenced somewhere so it knows where to find the implementation of add. As you discovered, listing adder.c as an additional source file works. A more common way to use external (shared) C libraries is to compile it as libadder.so and link it via the libraries parameter in your Extension declaration.

For something this simple you could also just write

cdef extern from "adder.c":   # note the .c
    int add(int x, int y)
    ...

or put the entire implementation in the .h file.

like image 73
robertwb Avatar answered Oct 20 '22 00:10

robertwb