Précédent Index Suivant

Communication entre C et Objective CAML

La communication entre parties, d'un même programme, écrites en C et en Objective CAML s'effectue en créant un exécutable (ou une nouvelle boucle d'interaction) contenant ces deux parties. Celles-ci ont pu être compilées séparément. Ce sera donc à l'édition de liens1 d'établir le lien entre les noms Objective CAML et les noms C, puis de créer l'exécutable. Pour cela le programme Objective CAML contiendra des déclarations externes assurant cette correspondance.

L'exemple de la figure 12.1 montre un programme composé d'une partie C et d'une partie Objective CAML.


Figure 12.1 : communication entre Objective CAML et C


On peut voir chaque partie comme contenant du code, des instructions correspondant aux définitions de fonctions et de calcul d'expressions pour Objective CAML, ainsi que leur zone d'allocation dynamique de mémoire. L'appel de la fonction f sur trois entiers d'Objective CAML entraîne le calcul de la fonction f_c de C. Le corps de la fonction convertit les trois entiers Objective CAML en entiers C, calcule leur somme et retourne le résultat converti en entier Objective CAML.

Nous présentons ci-dessous les premiers éléments d'interfaçage entre Objective CAML et C : les déclarations externes, les contraintes sur les définitions de fonctions C appelables à partir d'Objective CAML ainsi que les options d'édition de liens. Puis nous donnons un exemple d'utilisation des entrées-sorties.

Déclarations externes

La déclaration externe de fonctions en Objective CAML a pour but d'effectuer la correspondance entre une déclaration de fonction C et un nom Objective CAML, tout en indiquant le type de celle-ci.

La syntaxe est la suivante :

Syntaxe


external nom_caml : type = "nom_C"
Cette déclaration indique que l'appel en Objective CAML de la fonction nom_caml déclenchera l'appel à la fonction C nom_C avec ses arguments. L'exemple de la figure 12.1 déclare ainsi la fonction f dont l'appel correspondra à l'exécution de la fonction f_c.

Il est possible de déclarer une fonction externe dans une interface (i.e. dans un fichier .mli), soit en la déclarant explicitement externe, soit comme une valeur usuelle :

Syntaxe


external nom_caml : type = "nom_C"

val nom_caml : type
Dans le second cas, l'appel à la fonction C passe au préalable par le mécanisme général des fonctions Objective CAML. C'est un peu moins efficace mais cache la nature de l'implantation de la fonction.

Déclaration des fonctions C

Les fonctions C appelables à partir d'Objective CAML doivent posséder le nombre d'arguments décrit dans la déclaration externe. Ces arguments sont de type value qui est, en C, le type des valeurs d'Objective CAML. Comme celles-ci ont une représentation uniforme (9) un seul type C permet de coder toutes les valeurs Objective CAML. Nous exposerons page ?? l'utilisation des outils de codage-décodage des valeurs et l'illustrerons par une fonction d'exploration des valeurs d'Objective CAML.

L'exemple de la figure 12.1 est conforme à ces contraintes. La fonction f_c, liée à la fonction Objective CAML de type int -> int -> int -> int est bien une fonction à trois paramètres de type value qui retourne un résultat de type value.

Le mécanisme d'évaluation de l'interprète de code-octet d'Objective CAML diffère selon le nombre d'arguments2. Si le nombre d'arguments est inférieur ou égal à cinq, les arguments sont empilés dans la pile d'exécution de la machine. Si le nombre d'arguments est supérieur à cinq, c'est un tableau contenant tous les arguments qui est empilé ainsi que la taille de ce tableau. Comme les fonctions C peuvent être appelées à partir de la machine abstraite, il est nécessaire de distinguer ces deux cas. Par contre l'appel de fonction à partir du compilateur natif empile bien tous ses arguments. Ceux-ci pourront donc être passés directement à la fonction C.

Cas des fonctions à plus de cinq arguments

Il convient dans ce cas d'écrire deux fonctions C : l'une pour le code-octet et l'autre pour le code natif. La syntaxe des déclarations externes est donc étendue pour permettre une seule déclaration de fonction Objective CAML pour deux fonctions C :

Syntaxe


external nom_caml : type = "nom_C_byte_code" "nom_C_natif"
La fonction nom_C_byte_code prend deux arguments : un tableau de valeurs de type value (i.e. un pointeur de type *value) et un entier indiquant la taille de ce tableau.

Exemple

Le programme C suivant définit deux fonctions différentes pour l'addition de six entiers : plus_native et plus_bytecode qui seront utilisées respectivement par le compilateur natif et le compilateur de code-octet. Il est nécessaire d'inclure le fichier mlvalues.h contenant les définitions des types C des valeurs Objective CAML et les macros de conversion.

#include <stdio.h>
#include <caml/mlvalues.h>

value plus_native (value x1,value x2,value x3,value x4,value x5,value x6)
{
printf("<< PLUS NATIF >>\n") ; fflush(stdout) ;
return Val_long ( Long_val(x1) + Long_val(x2) + Long_val(x3)
+ Long_val(x4) + Long_val(x5) + Long_val(x6)) ;
}

value plus_bytecode (value * tab_val, int nb_val)
{
int i;
long res;
printf("<< PLUS BYTECODE >> : ") ; fflush(stdout) ;
for (i=0,res=0;i<nb_val;i++) res += Long_val(tab_val[i]) ;
return Val_long(res) ;
}

Le programme Objective CAML exOCAML.ml suivant utilise ces fonctions C.

external plus : int -> int -> int -> int -> int -> int -> int
= "plus_bytecode" "plus_native" ;;
print_int (plus 1 2 3 4 5 6) ;;
print_newline () ;;


On compile ces programmes avec les deux compilateurs Objective CAML, et un compilateur C que nous appelons cc. Il est nécessaire de lui indiquer le chemin d'accès au fichier mlvalues.h.
$ cc -c -I /usr/local/lib/ocaml  exC.c 

$ ocamlc -custom exC.o exOCAML.ml -o ex_byte_code.exe 
$ ex_byte_code.exe
<< PLUS BYTECODE >> : 21 

$ ocamlopt exC.o exOCAML.ml -o ex_native.exe 
$ ex_native.exe 
<< PLUS NATIF >> : 21 

Remarque


La façon la plus simple pour ne pas avoir à écrire deux fois une même fonction est d'utiliser la primitive native dans le code de la primitive pour le code-octet suivant le schéma :
value prim_nat (value x1, ..., value xn) { ... }
value prim_bc (value *tab, int n)
{ return prim_nat(tab[0],tab[1],...,tab[n-1]) ; }


Édition de liens avec C

L'édition de liens va créer un exécutable à partir de fichiers C et Objective CAML compilés respectivement par leur propre compilateur. Le résultat du compilateur natif est illustré à la figure 12.2.



Figure 12.2 : exécutable mixte


Les instructions machine provenant de la compilation des programmes C et Objective CAML sont rangées dans la zone d'allocation statique du programme. La zone d'allocation dynamique contient la pile d'exécution (où sont rangés les appels en cours) et les tas de C et d'Objective CAML.

Bibliothèque d'exécution

Les primitives C susceptibles d'être appelées par un programme n'utilisant que les bibliothèques standard sont contenues dans la bibliothèque d'exécution de la machine abstraite (voir la figure 7.3 page ??). Il n'est donc pas nécessaire de préciser quoi que ce soit. En revanche, lorsqu'on utilise les bibliothèques Graphics, Num ou Str il est nécessaire d'expliciter les bibliothèques correspondant à ces modules pour l'édition de lien. C'est la fonction de l'option -custom du compilateur. Il en va de même pour des fonctions C que nous souhaiterions utiliser, il faut ajouter le fichier objet contenant ces fonctions au moment de l'édition de lien. Nous allons détailler cela dans un exemple.

Différents cadres d'édition de liens

Il faut distinguer les cadres de compilation pour le compilateur natif, le compilateur de code-octet et la construction de boucles d'interaction. Les différentes options des différents modes de compilation sont décrites au chapitre 7.

Pour illustrer ces différents modes, on reprend l'exemple de la figure 12.1 en nommant le fichier Objective CAML progocaml.ml. Celui-ci utilise la fonction externe f_c définie dans le fichier C progC.c. Ce dernier utilise une bibliothèque C libC. Une fois ces fichiers compilés de manière indépendante, leur édition de liens s'effectue par les commandes suivantes : On obtient deux exécutables : vbc.exe pour la version en code-octet et vn.exe pour la version en code natif.

Construire une nouvelle machine abstraite

On peut aussi augmenter la bibliothèque d'exécution de la machine abstraite en intégrant de nouvelles fonctions C. Pour cela on utilisera les commandes suivantes :
ocamlc -make-runtime -o nouveau_ocamlrun progC.o libC.a
On pourra alors construire un exécutable non autonome, vbcnam.exe, utilisant la nouvelle machine abstraite :
ocamlc -o vcam.exe -use-runtime nouveau_ocamlrun progC.o \
       libC.a  progocaml.cmo
Il sera exécuté par la commande : nouveau_ocaml vbcam.exe ,
ou directement par : vbcam.exe

Remarque


L'édition de liens en mode -custom force le compilateur à parcourir les fichiers objets (.cmo) pour y répertorier les fonctions externes mentionnées. Le code-octet nécessaire à leur utilisation est engendré et ajouté au code-octet provenant des programmes Objective CAML.


Construire une boucle d'interaction

Pour pouvoir utiliser une fonction externe dans une boucle d'interaction, il faut en construire une nouvelle comportant d'une part le code C de la fonction et d'autre part un programme Objective CAML contenant sa déclaration.

On suppose compilé le fichier progC.c contenant la fonction f_c. On construit alors la boucle d'interaction ftop de la manière suivante :
ocamlmktop -custom -o ftop progC.o libC.a ex.ml
Le fichier ex.ml contient la déclaration externe de la fonction f. Le toplevel ftop connaît alors cette fonction et dispose du code C correspondant qui est donné dans progC.o.

Entrées-sorties des deux mondes

Les fonctions C et Objective CAML ne partagent pas leurs tampons d'échange avec les fichiers. Soit le programme C suivant :

#include <stdio.h>
#include <caml/mlvalues.h>
value hello_world (value v)
{ printf("Hello World !!"); fflush(stdout); return v; }

On note que les écritures sur la sortie standard doivent être forcées (fflush) si l'on souhaite qu'elles apparaissent dans le bon ordre.

# external caml_hello_world : unit -> unit = "hello_world"  ;;
external caml_hello_world : unit -> unit = "hello_world"
# print_string "<< " ;
caml_hello_world () ;
print_string " >>\n" ;
flush stdout ;;
Hello World !!<< >>
- : unit = ()


Cet affichage n'est pas satisfaisant. Il faut réécrire le programme Objective CAML de la manière suivante :

# print_string "<< " ; flush stdout ;
caml_hello_world () ;
print_string " >>\n" ; flush stdout ;;
<< Hello World !! >>
- : unit = ()
Le vidage des tampons qui suit systématiquement un ordre d'écriture permet de conserver l'ordre d'affichage entre les deux mondes.


Précédent Index Suivant