Monday 25 June 2007
How to write Caml extensions for Python
By Maxime Biais, Monday 25 June 2007 at 10:14 :: Python
I wanted to write a simple function in Caml, compile it and load it as a Python module, but it was a bit more complex that I thought. I tried to use pycaml or py2caml to avoid writing C code; without success. Then I read the Extending Python With C chapter in the Python doc and the Interfacing C with Objective Caml chapter from OCaml manual.
There is 4 main steps to write this example module:
- Write the Caml function you want to use in your Python program (in the example file mop.ml)
- Write the C code to wrap the Caml code (mop_mlwrapper.c)
- Write the C code to declare the Python module (mop_module.c)
- Compile all and link everything together (not the easiest when you're not familiar with Caml compilation process)
Caml code:
let square a = a * a;; (* declare square function as name "square" to call it from C code *) let _ = Callback.register "square" square;;
C code to wrap the Caml function:
#include "caml/callback.h" void initcaml(void) { char *dummy[] = {0}; // Initialize the Caml interpreter caml_startup(dummy); } int mlmop(int a) { int res; static value *f = NULL; value r_caml; // Get a pointer on the caml code "square" f = caml_named_value("square"); if (f == NULL) return -1; // Run the Caml function pointed by f r_caml = callback(*f, Val_int(a)); // Convert the return value to an integer res = Int_val(r_caml); return res; }
C code, Python extension:
#include "Python.h" extern int mlmop(int); extern void initcaml(); static PyObject * testmop(PyObject *self, PyObject *args) { int arg, res; if (!PyArg_ParseTuple(args, "i", &arg)) return NULL; res = mlmop(arg); // convert to a Python int return Py_BuildValue("i", res); } static PyMethodDef MopMethods[] = { {"testmop", testmop, METH_VARARGS, "test function calling caml code"}, {NULL, NULL, 0, NULL} /* Sentinel */ }; // function needed to initialize the module and call the caml init PyMODINIT_FUNC initmop(void) { (void) Py_InitModule("mop", MopMethods); initcaml(); }
The Makefile:
- Compile the ml file in object file (-output-obj option)
- Compile both C files in object files
- Link all object files in a shared library. The library must be linked with dependency libraries : libcamlrun, libunix, libm, libcurses
PY_PREFIX=/usr PY_VERSION=2.4 LIBS = -L/usr/lib/ocaml/3.09.2/ -L$(PY_PREFIX)/lib/python$(PY_VERSION)/config -lpython$(PY_VERSION) -lunix -lcamlrun -lm -lcurses INCS = -I$(PY_PREFIX)/include/python$(PY_VERSION) -I/usr/lib/ocaml/3.09.2/ all: mop.so mop_module.o: mop_module.c gcc -ggdb -Wall -c mop_module.c $(INCS) -o mop_module.o mop_mlwrapper.o: mop_mlwrapper.c ocamlc -ccopt -Wall -c mop_mlwrapper.c mop.o: mop.ml ocamlc -o mop.o -output-obj -ccopt -fPIC mop.ml # Note: under MacOS X replace -shared by -dynlib mop.so: mop_module.o mop_mlwrapper.o mop.o gcc -shared --whole-archive mop_mlwrapper.o mop.o mop_module.o $(LIBS) -o mop.so test: mop.so python$(PY_VERSION) test.py test2: mop.so python$(PY_VERSION) -c "import mop; print 'square(2) =', mop.testmop(2)" clean: rm -rf *o *so *cmi *cmo
After the compilation process, when mop.so is built, you can run import it as a Python module:
import mop for i in range(15): print mop.testmop(i)
Here is the archive of the Caml, C and Python code and Makefile.



