28/08/2010

Communication Ocaml et C

Ce billet a pour but de montrer comment récupérer un tableau alloué dynamiquement en Ocaml dans un programme écrit en C. En regardant la doc de l'INRIA sur l'interfaçage entre du C et du Ocaml, on ne retrouvait des exemples qu'avec des types (Int, Char, String, ...) mais pour ma part, il fallait que je passe du type Char Array Array (Ocaml) à char ** (C). C'est en regardant du côté du module BigArray, que l'on peut voir qu'il existe un module Bigarray.Array2 permettant la manipulation des tableaux à 2 dimensions facilement.

Les BigArray peuvemt manipuler du type Char :

val char : (char, int8_unsigned_elt) kind

Et un layout permettant l'interportabilité avec le C :

type c_layout 

A partir de tous ces éléments j'étais en mesure d'écrire un programme de test :

Code Ocaml :

(* Function just for Allocating our 2D table *)
let create_tab n m init =
  let tab = Array.make n (Array.make m init) in
    for i = 1 to n - 1 do
      tab.(i) <- Array.make m init
    done;
    tab

(* Function for creating the table and returning it *)
let create_tab () =
  Random.self_init();
  let tab = create_tab 20 30 'X' in
    init_tab tab;
    Bigarray.Array2.of_array Bigarray.char Bigarray.c_layout tab

(* Export "create_tab" function to C *)
let _ = Callback.register "create_tab callback" create_tab

Code C :

#include <stdio.h>
#include <stdlib.h>
#include <caml/mlvalues.h>
#include <caml/callback.h>
#include <caml/bigarray.h>

/* I return my Char array array allocated in ocaml like this
   Bigarray.Array2.of_array Bigarray.char Bigarray.c_layout tab
*/
int
test()
{
  /* 2d table */
  char **tab;
  /* simpli value for counting */
  int i,j,k=0;
  /* Widht , height */
  int w,h;
  /* pointer to data part */
  char *p;
  /* pointer to the value registered under a name */
  static value *f = NULL;

  f = caml_named_value("create_tab callback");
  /* Applies the functional value f and return the value returned by f */
  value ret = caml_callback(*f,Val_unit);
  /* i-th dimension Get the Width & Height */
  w = Bigarray_val(ret)->dim[0];
  h = Bigarray_val(ret)->dim[1];
  /* We look is everything ok ? */
  printf("\nw = %d\n",w);
  printf("\nh = %d\n",h);
  /* Get a pointer to the data part of the array */
  p = Data_bigarray_val(ret);
  /* Ok now we alloc enough place */
  tab = (char**)malloc(sizeof(char*)*w);
  if(!tab)
    return 1;
  for(i=0;i<w;i++)
    {
      tab[i] = (char*)malloc(sizeof(char)*h);
      if(!tab)
        return 1;
    }
  /* Here we copy everyhting in a table of 2 dimension
     More easier to manipulate it no ? */
  for(i=0;i<w;i++)
    for(j=0;j<h;j++)
      {
        tab[i][j] = p[k];
        k++;
      }
  /* And now we watch if everything is ok */
  for(i=0;i<w;i++)
    {
      for(j=0;j<h;j++)
        printf("%c | ",tab[i][j]);
      printf("\n");
    }
}

int
main(int argc,char **argv)
{
  caml_startup(argv);
  test();
  return 0;
}

Vous êtes en mesure maitenant de faire communiquer vos programmes Ocaml avec des programmes en C tout en manipulant des tableaux à X dimensions. X dimensions, Oui, car si vous lisez bien les pages du manuel que je vous ai linké, vous verrez que cela est possible.