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
libsnk.c
is the.pyx
file translated to C, not very usefullibsnk.h
is the corresponding header with thepublic
functions we have definedlibsnk.so
(or.dll
depending on your OS) is the dynamic library that can be used from C (but, as a matter of fact, from Python also)
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. :-)
-
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. ↩