Using Cython, it is quite easy to build a C1 library that allows to call SNAKES from any other programming language that can interface with C. However, there are many technical issues on the way, so here is an simple example that can be generalised. First of all, note that there is no one SNAKES library that we could want to export to C, but actually what we can export depends on the set of plugins we want to use. So, let's assume we want to build a C binding to create and draw place/transition nets.

The first file we need is a Python module that loads the plugins and declares a storage for our nets. Loading the plugins is not easy directly in Cython because it does not handle very well the dynamic nature of plugins loading, by putting this into a Python module, we force Cython to rely on the Python interpreter for this task. Storing the nets is also a work around potential problems: if we send reference to net objects out of the Python runtime, we must ensure that they will not be garage collected. So, our solution here is to store them explicitly and provide a handler that is simply the name of the nets. So, here comes file _libsnk.py:

import snakes.plugins
snakes.plugins.load("gv", "snakes.nets", "snk")
from snk import *
nets = {}

Then, we need another file that is the Cython wrapper for this module. Because we have targeted a specific use case, we can create a binding that exposes a specific API, suited to the need. This is made in file libsnk.pyx, that simply imports everything from _libsnk and defines a set of functions to implement every aspect of our specialised API. We use cdef and declare types for this functions so that they are implemented as C functions by Cython. Moreover, we use public to have them exported into a file libsnk.h that is generated by Cython along with the C file. Note finally that we use very basic error handling by simply returning 0 when there is a problem and 1 otherwise.

from _libsnk import *

cdef public int newnet (char *name) :
    if name in nets :
        return 0
    nets[name] = PetriNet(name)
    return 1

cdef public int delnet (char *name) :
    try :
        del nets[name]
        return 1
    except :
        return 0

cdef public int addplace (char *net, char *name, int tokens) :
    try :
        nets[net].add_place(Place(name, [dot] * tokens))
        return 1
    except :
        return 0

cdef public int addtrans (char *net, char *name) :
    try :
        nets[net].add_transition(Transition(name))
        return 1
    except :
        return 0

cdef public int addarc (char *net, char *src, char *dst, int weight) :
    try :
        if weight <= 0 :
            return 0
        elif weight == 1 :
            arc = Value(dot)
        else :
            arc = MultiArc([Value(dot) for i in range(weight)])
        if nets[net].has_place(src) :
            nets[net].add_input(src, dst, arc)
        else :
            nets[net].add_output(dst, src, arc)
        return 1
    except :
        return 0

cdef public int drawnet (char *name, char *target) :
    try :
        nets[name].draw(target)
        return 1
    except :
        return 0

To compile this source code, we write make.py as follows:

from distutils.core import setup
from Cython.Build import cythonize

setup(name        = 'My SNAKES binding',
      ext_modules = cythonize("libsnk.pyx"))

Then, running python make.py build_ext --inplace invokes the Cython compiler allowing to generate

Finally, we can use our library from C or any language that can call C functions. Here is a simple C program, with no error check, just for the sake of demonstration:

#include "Python.h"
#include "libsnk.h"

int main (int argc, char *argv[]) {
  Py_Initialize();
  initlibsnk();
  newnet("test");
  addplace("test", "A", 3);
  addplace("test", "B", 0);
  addtrans("test", "T");
  addarc("test", "A", "T", 1);
  addarc("test", "T", "B", 2);
  drawnet("test", "test.eps");
  Py_Finalize();
}

The first #include imports functions Py_Initialize and Py_Finalize that allow respectively to start and stop the Python runtime. The second #include imports all the other functions, those we have created in the .pyx file and also initlibsnk that initialises the Python part in libsnk; here, it performs the from _libsnk import *. This code can be compiled with GCC as follows (may need to be adapted on your system) invoking gcc -I/usr/include/python2.7 -L. -o snktest snktest.c -lsnk -lpython2.7. Note that -lsnk is the reason why we have called our binding libsnk: the linker adds lib automatically.

A more complete binding would include better handling of errors, allowing the C code to get error messages from the exceptions trapped in Python. But basically, we have already all the elements we need. If you need to use the C binding from another language, perhaps you will find Swig useful to automatically generate a binding of the binding. :-)


  1. Cython allows to generate C++ also, but we won't use the object features here, so even if you generate C++, it'll be very C-ish.