8.1 Écriture d'une interface

Supposons qu'on dispose d'une librairie de fonctions externes écrites en C. La librairie contient par exemple la fonction geom qui effectue m*n tirages aléatoires selon une loi géométrique de paramètre p et stocke les résultats dans un tableau d'entiers y de taille m x n. Savoir ce que fait exactement geom n'est pas vraiment utile pour nous ici. Disons simplement que, étant donnés les entiers m et n et le paramètre p, cette fonction retourne le tableau y. Voilà le code ``C'' de la procédure geom (qui est fort simple), la seule chose importante ici étant sa liste d'appel :


#include <stdlib.h>

int geom(int m,int n,double p,int y[])
{
  int i;
  if ( p >= 1.0 ) 
    {
      cerro("p doit-etre < 1 "); return 1;
    }
  for (i = 0 ; i < m*n ; i++) 
    {
      double z = drand48();
      y[i] = 1;
      while ( z < 1.0-p ) { z = drand48(); y[i] ++; }
    }
  return 0;
}
On souhaite donc rajouter dans Scilab une fonction (une ``primitive'') de même nom geom dont la syntaxe d'appel est y=geom(m,n,p). La fonction geom est supposée effectuer m*n tirages de loi géométrique et renvoyer le résultat sous la forme d'une matrice Scilab de taille m x n.

Pour ce faire, il faut écrire une interface, c'est-à-dire un programme (disons à nouveau en C) qui va être chargé de faire les conversions et le passage des données entre l'interprète Scilab et la fonction C geom. Nous appellerons intgeom l'interface pour la fonction geom (il faut écrire une interface pour chaque fonction que l'on veut interfacer).

Il existe dans Scilab diverses façons d'écrire une interface. Nous allons utiliser ici la méthode qui nous semble la plus simple. De nombreux exemples d'écriture d'interfaces sont fournis dans le répertoire

SCI/examples.

Le répertoire SCI/examples/interface-tutorial-so/ donne aussi un exemple très simple d'interface, destinée aux utilisateurs débutants, proche de celle décrite ici.

Pour écrire cette interface particulière, on a utilisé des fonctions de la librairie d'interfaçage (CheckRhs, CheckLhs, GetRhsVar et CreateVar), des variables de la librairie d'interfaçage (LhsVar) et des fonctions internes Scilab (cerro, aussi utilisée dans geom). Pour utiliser les fonctions et les variables précédentes, il faut rajouter le fichier d'en-tête stack-c.h dans le fichier intgeom.c.

L'interface de la fonction geom s'écrit alors de la façon suivante :


#include "stack-c.h"

extern int geom(int m, int n, double p, int y[]); 

int intgeom(char *fname)
{
  int l1, m1, n1, l2, m2, n2, m3, n3, l3;
  int minlhs=1, maxlhs=1, minrhs=3, maxrhs=3;
  int m,n,y;  double p;
  
  /* 1 - Vérification du nombre d'arguments d'entrée et de sortie */
  CheckRhs(minrhs,maxrhs) ;
  CheckLhs(minlhs,maxlhs) ;

  /* 2 - Vérification du type des arguments et retour des pointeurs */
  GetRhsVar(1, "i", &m1, &n1, &l1); 
  GetRhsVar(2, "i", &m2, &n2, &l2);
  GetRhsVar(3, "d", &m3, &n3, &l3);
  
  if ( m1*n1 != 1 || m2*n2 != 1 || m3*n3 != 1) 
    {
      cerro("Erreur: les arguments de geom doivent etre scalaires");
      return 0;
    }
  /* 3 - Extraction des valeurs */
  m = *istk(l1); n = *istk(l2) ; p = *stk(l3); 

  /* 4 - Création de la variable de retour */
  CreateVar(4,"i",&m,&n,&y); 
  if ( geom(m,n,p,istk(y)) == 1 ) return 0;
  
  /* 5 - Spécification de la variable de retour */
  LhsVar(1) = 4;
  return 0;
}

A première vue, cette fonction peut paraître un peu compliquée. En fait on ne va pas taper tout ce code, mais plutôt partir d'un exemple tout fait qu'on va adapter à notre cas. Puisque tous les programmes d'interface sont bâtis sur le même moule, les modifications à faire sont très faciles à réaliser avec quelques retouches à l'éditeur. L'interface précédente pourrait par exemple être utilisée presque telle quelle pour n'importe quelle fonction C qui aurait une liste d'appel semblable à celle de geom.


Comment cette interface marche-t-elle ? Quand sous l'interprète Scilab, on tape la commande y=geom(m,n,p), les arguments sont évalués et leurs valeurs sont stockées dans un tableau (que l'on appellera pile d'appel) dans l'ordre où ils apparaissent dans la liste d'appel. D'autre part le nombre d'arguments de retour attendus (ici y) est connu. La fonction d'interfaçage intgeom doit tout d'abord contrôler que le nombre d'arguments transmis et attendus au retour sont corrects. Cela est fait en utilisant CheckLhs et CheckRhs ; si le nombre d'arguments ne correspond pas aux bornes spécifiées ces fonctions génèrent une erreur et provoquent la sortie de l'interface.

Dans l'interface, chaque variable Scilab est repérée par un numéro, d'abord les variables d'entrée puis les variables de sortie. Ici par exemple, la fonction Scilab étant y=geom(m,n,p), les variables d'entrée m,n,p ont les numéros 1,2,3 et la variable de sortie y a le numéro 4.

Ensuite, il faut vérifier que chaque élément présent dans la liste d'appel a le bon type (est-ce une matrice, une chaîne de caractères, une fonction ?) et la bonne taille. Enfin il faut récupérer un pointeur vers les données pour pouvoir les transmettre à la fonction interfacée ; c'est ce qui est fait dans la deuxième partie de la procédure intgeom.

La commande suivante :

 

  GetRhsVar(2, "i", &m2, &n2, &l2);

a pour effet de vérifier que le deuxième argument de la liste d'appel est bien de type numérique ("i") et de renvoyer dans m2 et n2 les dimensions de la matrice et dans l2 une adresse pour accéder aux données (une conversion des données en entiers est faite). Si le type ne convient pas une erreur est générée.

Le deuxième argument étant de type entier on obtient un pointeur d'entiers avec istk(l2). Le deuxième argument de geom qui doit être un entier est donc finalement récupéré dans l'interface par n= *istk(l2).

On notera qu'il est prudent de vérifier que m2*n2 vaut bien 1 car une utilisation de n = *istk(l2) dans un appel y=geom(10,[],2.5) donnerait n'importe quoi.

Le troisième argument est de type double ("d") et on y accède par p=*stk(l3). De manière générale, disposant des dimensions des arguments, on doit effectuer des vérifications et en cas d'erreur on peut utiliser la fonction cerro pour afficher un message puis faire un return (Scilab prend alors le relais). On notera d'ailleurs que dans la fonction geom, on a aussi utilisé la fonction cerro pour imprimer un message d'erreur.


Avant d'appeler la fonction geom, il faut créer la variable numéro 4, soit y, et réserver de la place pour la stocker. La variable y doit être une matrice d'entiers de taille m$ \times$n et la fonction geom veut un pointeur d'entiers. La commande : CreateVar(4,"i",&m,&n,&y) de la quatrième partie de intgeom se charge de créer dans la pile d'appel un nouvel objet Scilab avec le numéro 4 (une matrice m$ \times$n) et à nouveau on obtient une adresse pour accéder aux données y (les données sont entières et sont donc accessibles via istk(y))

La syntaxe d'appel de CreateVar est la même que celle de GetRhsVar sauf que les 4 premiers arguments sont des entrées et le dernier une sortie calculée par CreateVar. Après l'appel de geom, il ne reste plus qu'à renvoyer à Scilab le résultat ce que réalise la cinquième partie de la procédure intgeom. Le nombre de variables attendues par Scilab a déjà été contrôlé au début de l'interface (CheckRhs) et il ne reste plus qu'à indiquer par l'intermédiaire de LhsVar(i)=j les positions des variables que l'on veut renvoyer : l'instruction LhsVar(1)=4 signifie ``la première variable de sortie est la variable numéro 4''. C'est Scilab qui contrôle alors tout seul d'éventuelles conversions de données à effectuer.

Il n'est bien sûr pas question de décrire ici toutes les fonctions de la bibliothèque d'interfaçage. Le lecteur intéressé pourra se reporter au répertoire

SCI/examples/interface-tour-so

pour une description au travers d'exemples de toutes les possibilités de la librairie. La connaissance des 4 fonctions décrites ici, (CheckLhs, CheckRhs, GetRhsVar, CreateVar) permet d'interfacer la plupart des fonctions C.

Dans le répertoire SCI/examples/interface-tour-so on montre en particulier comment interfacer une fonction C qui a elle-même une fonction comme paramètre d'entrée, ou comment on peut appeler l'interprète Scilab à l'intérieur d'une interface. C'est évidemment un peu plus compliqué, mais des exemples simples sont donnés pour réaliser ce type d'interface.

Les utilisateurs familiers des mexfiles Matlab pourront se reporter au répertoire

SCI/examples/mex-examples

pour constater que leurs fichiers mex (librairie d'interfaçage de Matlab) marchent quasiment tels quels dans Scilab.