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:

  1. Write the Caml function you want to use in your Python program (in the example file mop.ml)
  2. Write the C code to wrap the Caml code (mop_mlwrapper.c)
  3. Write the C code to declare the Python module (mop_module.c)
  4. 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:

  1. Compile the ml file in object file (-output-obj option)
  2. Compile both C files in object files
  3. 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.

Next step: the same example with pycaml or py2caml.