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 :
-
code-octet :
ocamlc -custom -o vbc.exe progC.o libC.a progocaml.cmo
- natif :
ocamlopt progC.o -o vn.exe libC.a progocaml.cmx
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.