2014:02:10:TP:C:OCaml
Sommaire
Introduction
L'objectif de ce TP est de prendre en main les possibilités de liens entre C et OCaml.
Pour ça nous allons essayer de faire un stub pour un petit bout de code qui permet de faire des opérations de bases sur des fichiers bmp.
Voici les fichiers de base pour le TP (bmploader.c, bmploader.h et un fichier Makefile que vous pourrez utiliser.): media:20130311-TP-FichiersFournis.tar.gz
Notre objectif est de fournir un binding (ou stub) pour les fonctions décrites dans le fichier bmploader.h.
Premier pas
Nous allons commencer par la partie facile: appeler les fonctions de lecture et sauvegarde.
Nous devons faire un stub pour les fonctions suivantes:
bmp load_bmp(char *file); void dump_to_file(bmp img, char *file); void close_bmp(bmp img);
Vous allez avoir besoin d'écrire deux fichiers: stub.c et bmp_loader.ml
Le fichier stub.c va commencer comme ça:
/* A stub for bmploader */ #include <caml/alloc.h> #include <caml/mlvalues.h> #include <caml/memory.h> #include "bmploader.h"
Nous verrons bmp_loader.ml plus tard.
Chaîne de caractères
C'est la partie la plus facile, pour convertir une chaîne OCaml en chaîne C on peut (dans notre situation) utiliser la macro String_val().
Et notre type bmp ?
Et bien, vous savez quoi, on ne va pas le convertir !
L'astuce ? Le passer de manière opaque à l'univers OCaml sous-forme d'une valeur polymorphe !
Voici les définitions pour pouvoir utiliser votre stub, à mettre dans bmp_loader.ml:
external load_bmp : string -> 'a = "stub_load_bmp" external dump_to_file : 'a -> string -> unit = "stub_dump_to_file" external close_bmp : 'a -> unit = "stub_close_bmp"
Go !
Il ne vous reste plus qu'à écrire les fonctions suivantes (dans stub.c):
/* Take a file name (a string) and return a bmp pointer */ value stub_load_bmp(value ml_file); /* Take a bmp pointer and a file name and return unit () */ value stub_dump_to_file(value ml_img, value ml_file); /* Take a bmp pointer and return unit () */ value stub_close_bmp(value img);
Au passage, quand on vous disait que le rien, en OCaml c'était quelque chose: la valeur unit existe (c'est l'entier 0) et on l'obtient avec la macro Val_unit.
Créer des valeurs OCaml
Pour la suite, nous allons implémenter les fonctions du stub qui donnent accès au pixels !
Mais pour ça nous voulons utiliser des triplets pour les couleurs. Il va donc nous falloir manipuler des valeurs un peu plus évoluées.
Vivre en harmonie avec notre ami GC
GC, le Garbage Collector, est chargé de détruire tous les valeurs allouées qui n'ont pas de racines de persistance dans son univers. C'est pourquoi, lorsque nous allons créer un n-uplet, il faudra le déclarer auprès du GC.
Voyons un exemple, la fonction suivante construit un couple d'entier contenant les dimensions de l'image:
value stub_get_dims(value img) { // First declare ocaml param and local vars CAMLparam1(img); CAMLlocal1(dims); // Get the data we're looking for uint32_t w = ((bmp)img)->dib.width, h = ((bmp)img)->dib.height; // build the pair dims = alloc_tuple(2); // Fill the pair Field(dims, 0) = Val_long(w); Field(dims, 1) = Val_long(h); // Return it CAMLreturn(dims); }
Vous noterez en particulier les CAMLparam1, CAMLlocal1 et le CAMLreturn. C'est macros ajoutent les déclarations nécessaires pour le GC. Pour les paramètres et les variables locales, vous avez des macros sous la forme: CAMLparam0, CAMLparam1(a), CAMLparam2(a,b) … CAMLparam5(a,b,c,d,e) et lorsque vous en avez plus de 5: CAMLxparam1(f) …
Si vous voulez en savoir plus, je vous recommande ce lien [1]
Let's do it !
C'est maintenant à vous. Voici notre module bmp_loader.ml définitif:
external load_bmp : string -> 'a = "stub_load_bmp" external dump_to_file : 'a -> string -> unit = "stub_dump_to_file" external close_bmp : 'a -> unit = "stub_close_bmp" external put_pixel : 'a -> int -> int -> int * int * int -> unit = "stub_put_pixel" external get_pixel : 'a -> int -> int -> int * int * int = "stub_get_pixel" external get_dims : 'a -> int * int = "stub_get_dims"
Il nous manque donc les fonctions suivantes dans notre fichier stub.c:
/* take a bmp, x and y and (r,g,b) */ value stub_put_pixel(value img, value mlx, value mly, value col); /* take a bmp, x and y and return (r,g,b) */ value stub_get_pixel(value img, value mlx, value mly);
Testons !
Pour finir, nous allons tester tout ça un petit bout de code simple, voici le fichier demo.ml:
(* Demo using stub *) let to_grey img = let (w,h) = Bmp_loader.get_dims img in for j=0 to h-1 do for i=0 to w-1 do let (r,g,b) = Bmp_loader.get_pixel img i j in let grey = (11 * r + 59 * g + 30 * b)/100 in Bmp_loader.put_pixel img i j (grey,grey,grey); done done let main () = begin if Array.length Sys.argv < 2 then begin Printf.eprintf "%s: need a file name\n%!" Sys.argv.(0); exit 2 end; let img = Bmp_loader.load_bmp Sys.argv.(1) in to_grey img; Bmp_loader.dump_to_file img ("out-"^Sys.argv.(1)); Bmp_loader.close_bmp(img); exit 0; end let _ = main ()
Normalement, le Makefile fourni devrait vous compiler tout ça, charge à vous de le lire et de le comprendre !