perlxs - Manuel de référence du langage XS
XS est un langage utilisé pour réaliser une interface d'extension entre Perl et une librairie C qu'on souhaite utiliser depuis Perl. L'interface XS est combinée avec la librairie pour produire une nouvelle librairie susceptible d'être liée avec Perl. Une XSUB est une fonction écrite dans le langage XS ; c'est le composant central de l'interface de l'application Perl.
Le compilateur XS s'appelle xsubpp. Ce compilateur insère les constructions nécessaires pour permettre à une XSUB, qui n'est autre qu'une fonction C déguisée, de manipuler des valeurs Perl, et crée la colle nécessaire pour permettre à Perl d'accéder à la XSUB. Le compilateur utilise des typemaps pour faire la correspondance entre les variables et les paramètres de fonction C d'une part, et les valeurs Perl de l'autre. Le typemap par défaut gère de nombreux types C parmi les plus courants. Un typemap supplémentaire doit être créé pour traiter les structures et les types spécifiques à la librairie que l'on veut lier dans Perl.
Consultez la page de manuel perlxstut pour un guide d'apprentissage du processus de création d'extensions dans son ensemble.
Note : pour beaucoup d'extensions, le système SWIG de Dave Beazley fournit un mécanisme nettement plus pratique pour réaliser le code de liaison XS. Voyez http://www.cs.utah.edu/~beazley/SWIG pour plus d'information.
La plupart des exemples qui suivent portent sur la création d'une
interface entre Perl et les fonctions de la librairie de connexion
ONC+ RPC. La fonction rpcb_gettime()
sera utilisée pour illustrer de
nombreuses caractéristiques du langage XS. Cette fonction accepte deux
paramètres ; le premier est une donnée en entrée et le second une
donnée en sortie. La fonction renvoie aussi une valeur d'état.
bool_t rpcb_gettime(const char *host, time_t *timep);
Depuis C, cette fonction est appelée avec les instructions suivantes.
#include <rpc/rpc.h> bool_t status; time_t timep; status = rpcb_gettime( "localhost", &timep );
Si on réalise une XSUB offrant une traduction directe entre cette fonction et Perl, cette XSUB sera utilisée depuis Perl avec le code suivant. Les variables $status et $timep contiendront la sortie de cette fonction.
use RPC; $status = rpcb_gettime( "localhost", $timep );
Le fichier XS suivant présente une sous-routine XS, ou XSUB, proposant
un exemple d'interface avec la fonction rpcb_gettime(). Cette XSUB
représente une traduction directe entre C et Perl et préserve donc
l'interface même depuis Perl. Cette XSUB sera appelée depuis Perl de
la même manière que ci-dessus. Notez que les trois premières
instructions #include, pour EXTERN.h
, perl.h
et XSUB.h
,
seront tout le temps présentes au début d'un fichier XS. On élargira
plus tard cette approche, et on en présentera d'autres.
#include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include <rpc/rpc.h>
MODULE = RPC PACKAGE = RPC
bool_t rpcb_gettime(host,timep) char *host time_t &timep OUTPUT: timep
Toutes les extensions à Perl, y compris celles qui contiennent des
XSUB, doivent être accompagnées d'un module Perl d'amorçage
(angl. bootstrap) qui assure l'intégration de l'extension dans
Perl. Ce module exporte les fonctions et les variables de l'extension
vers le programme Perl et entraîne la liaison entre les XSUB de
l'extension et Perl. Le module qui suit sera utilisé dans la plupart
des exemples de ce document ; il doit être utilisé depuis Perl avec la
commande use
comme on l'a vu précédemment. On approfondira l'étude
des modules Perl plus loin dans ce document.
package RPC;
require Exporter; require DynaLoader; @ISA = qw(Exporter DynaLoader); @EXPORT = qw( rpcb_gettime );
bootstrap RPC; 1;
Tout au long de ce document, diverses interfaces à la XSUB
rpcb_gettime()
seront explorées. L'ordre ou le nombre des paramètres
pris par les XSUB variera. Dans chaque cas, la XSUB est une
abstraction entre Perl et la vraie fonction C rpcb_gettime(), et la
XSUB doit toujours s'assurer que la vraie fonction rpcb_gettime()
est
appelée avec les bons paramètres. Cette abstraction permettra au
programmeur de réaliser une interface avec la fonction C plus proche
de Perl.
La XSUB qui suit permet à un programme Perl d'accéder à une fonction de librairie C dont le nom est sin(). La XSUB fait comme la fonction C, qui prend un seul paramètre et renvoie une valeur unique.
double sin(x) double x
Lorsqu'on utilise des pointeurs C, l'opérateur d'indirection *
devrait être considéré comme une partie du type et l'opérateur
d'addressage &
comme une partie de la variable, comme on l'a fait
dans la fonction rpcb_gettime()
ci-dessus. Consultez la section sur
les typemaps pour avoir plus de détails sur l'utilisation des
qualificateurs et des opérateurs unaires dans les types C.
Le nom de la fonction et le type de retour doivent être placés sur des lignes séparées.
INCORRECT CORRECT
double sin(x) double double x sin(x) double x
Le corps de la fonction peut être mis en retrait ou ajusté à gauche. L'exemple suivant montre une fonction dont le corps est ajusté à gauche. On utilisera un retrait dans la plupart des exemples de ce document.
CORRECT
double sin(x) double
La pile des arguments est utilisée pour stocker les valeurs qui sont envoyées à la XSUB en tant que paramètres, ainsi que la valeur de retour de la XSUB. En fait, toutes les fonctions Perl mettent leurs valeurs sur cette pile en même temps, chacune étant limitée à son propre intervalle de positions sur la pile. Dans ce document, la première position dans la pile appartenant à la fonction active sera désignée comme la position 0 pour cette fonction.
Les XSUB se réfèrent à leurs arguments de la pile avec la macro ST(x), où x désigne une position dans l'intervalle appartenant à la XSUB sur la pile. La position 0 pour cette fonction est connue de la XSUB comme ST(0). Les paramètres en entrée et les valeurs de retour de la XSUB commencent toujours à ST(0). Dans de nombreux cas simples, le compilateur xsubpp générera le code nécessaire à la manipulation de la pile des arguments en insérant des morceaux de code trouvés dans les typemaps. Dans les cas plus complexes, le programmeur devra fournir le code.
La variable RETVAL est une variable magique qui est toujours du type
retourné par la fonction de la librairie C. Le compilateur xsubpp
fournit cette variable dans chaque XSUB et l'utilise par défaut pour y
mettre la valeur de retour de la fonction C appelée. Dans les cas
simples, la valeur de RETVAL sera mise dans ST(0)
sur la pile
d'arguments, où Perl le recevra comme valeur de retour de la XSUB.
Si la XSUB a un type de retour égal à void
, le compilateur ne
fournira pas de variable RETVAL pour cette fonction. Lorsqu'on utilise
la directive PPCODE:, la variable RETVAL n'est plus nécessaire, sauf
si on l'utilise explicitement.
Si la directive PPCODE: n'est pas utilisée, la valeur de retour
void
devrait être utilisée uniquement pour des sous-routines qui ne
renvoient pas de valeur, même si la directive CODE: est utilisée
pour positionner ST(0)
explicitement.
Dans des versions plus anciennes de ce document, on conseillait
d'utiliser la valeur de retour void
dans de tels cas. On a
découvert que cela pouvait mener à des segfaults lorsque la XSUB était
vraiment void
. Cette pratique est maintenant désapprouvée, et
risque de ne plus être supportée dans une version future. Utilisez la
valeur de retour SV *
dans ces cas-là (xsubpp
contient
actuellement du code heuristique qui tente de faire la différence
entre les fonctions ``vraiment-void'' et celles qui sont
``déclarées-void-suivant-l'ancienne-pratique'' ; votre code est donc à
la merci de l'heuristique si vous n'utilisez pas SV *
comme valeur
de retour).
Le mot-clé MODULE est utilisé pour marquer le début du code XS et spécifier un paquetage pour les fonctions que l'on est en train de définir. Tout le texte qui précède le premier mot-clé MODULE est considéré comme du code C et sera transféré dans la sortie du compilateur sans modification. Tout module XS aura une fonction d'initialisation utilisée pour brancher la XSUB dans Perl. Le nom du paquetage de cette fonction d'initialisation correspondra à la valeur de la dernière instruction MODULE dans les fichiers source XS. La valeur de MODULE devrait toujours rester constante à l'intérieur d'un même fichier XS, même si cela n'est pas obligatoire.
L'exemple suivant démarre le code XS et place toutes les fonctions dans un paquetage nommé RPC.
MODULE = RPC
Lorsque les fonctions, à l'intérieur d'un fichier source XS, doivent être réparties dans des paquetages, il faut utiliser le mot-clé PACKAGE. Ce mot-clé est utilisé avec le mot-clé MODULE et doit être spécifié immédiatement après lui.
MODULE = RPC PACKAGE = RPC
[ code XS dans le paquetage RPC ]
MODULE = RPC PACKAGE = RPCB
[ code XS dans le paquetage RPCB ]
MODULE = RPC PACKAGE = RPC
[ code XS dans le paquetage RPC ]
Bien que ce mot-clé soit optionnel et qu'il fournisse des informations redondantes dans certains cas, il devrait toujours être utilisé. Il permet de garantir que la XSUB apparaîtra dans le paquetage désiré.
Le mot-clé PREFIX désigne des préfixes à supprimer dans les noms de
fonction Perl. Si la fonction C est rpcb_gettime()
et que la valeur
de PREFIX est rpcb_
, Perl devra voir cette fonction comme
gettime()
.
Ce mot-clé suit le mot-clé PACKAGE lorsqu'il est utilisé. Si PACKAGE n'est pas utilisé, alors PREFIX suit le mot-clé MODULE.
MODULE = RPC PREFIX = rpc_
MODULE = RPC PACKAGE = RPCB PREFIX = rpcb_
Le mot-clé OUTPUT: indique qu'il faut mettre à jour certains paramètres
de fonction (en rendant les nouvelles valeurs accessibles à Perl)
lorsque la XSUB se termine, ou que certaines valeurs doivent être
renvoyées à la fonction Perl. Pour des fonctions simples, comme la
fonction sin()
ci-dessus, la variable RETVAL est désignée
automatiquement comme une valeur en sortie. Dans des fonctions plus
complexes, le compilateur xsubpp aura besoin d'aide pour déterminer
quelles variables sont des variables en sortie.
Ce mot-clé sera utilisé normalement en complément du mot-clé CODE:. La variable RETVAL n'est pas reconnue comme une variable en sortie lorsque le mot-clé CODE: est présent. Le mot-clé OUTPUT: est utilisé dans cette situation pour dire au compilateur que RETVAL est réellement une variable en sortie.
Le mot-clé OUTPUT: peut aussi être utilisé pour indiquer que des paramètres de fonction sont des variables en sortie. Cela peut être nécessaire lorsqu'un paramètre a été modifié à l'intérieur d'une fonction et que le programmeur veut que cette modification soit visible depuis Perl.
bool_t rpcb_gettime(host,timep) char *host time_t &timep OUTPUT: timep
Le mot-clé OUTPUT: permet aussi à un paramètre en sortie d'être relié à un morceau de code correspondant plutôt qu'à un typemap.
bool_t rpcb_gettime(host,timep) char *host time_t &timep OUTPUT: timep sv_setnv(ST(1), (double)timep);
xsubpp rajoute automatiquement un SvSETMAGIC()
pour tous les
paramètres de la section OUTPUT de la XSUB, sauf pour RETVAL. C'est le
comportement désiré la plupart du temps, car il se charge d'invoquer
convenablement l'effet 'set' (angl. ``set magic'') sur les données en
sortie (ce qui est nécessaire pour les éléments de tableau simple ou
de tableau associatif passés en paramètre, qui doivent être créés
s'ils n'existent pas). Si, pour une raison donnée, ce comportement
n'est pas désiré, la section OUTPUT peut contenir une ligne
SETMAGIC: DISABLE
qui le désactive pour le restant des paramètres
de la section OUTPUT:. De même, SETMAGIC: ENABLE
peut être utilisé
pour le réactiver pour le restant de la section OUTPUT. Voyez
perlguts pour plus de détails sur l'effet 'set'.
Ce mot-clé est utilisé dans des XSUB plus complexes qui nécessitent un traitement spécial pour la fonction C. La variable RETVAL est disponible, mais elle ne sera pas retournée, sauf si elle est spécifiée sous le mot-clé OUTPUT:.
La XSUB qui suit correspond à une fonction C qui requiert un traitement spécial de ses paramètres. L'utilisation depuis Perl est donnée en premier.
$status = rpcb_gettime( "localhost", $timep );
Voici la XSUB.
bool_t rpcb_gettime(host,timep) char *host time_t timep CODE: RETVAL = rpcb_gettime( host, &timep ); OUTPUT: timep RETVAL
Le mot-clé INIT: permet d'insérer du code d'initialisation dans la XSUB avant que le compilateur ne génère l'appel à la fonction C. Contrairement au mot-clé CODE: ci-dessus, celui-ci n'affecte pas la manière dont le compilateur traite RETVAL.
bool_t rpcb_gettime(host,timep) char *host time_t &timep INIT: printf("# l'hote est %s\n", host ); OUTPUT: timep
Le mot-clé NO-INIT sert à indiquer qu'on utilise un paramètre de la fonction uniquement comme valeur en sortie. Le compilateur xsubpp génère normalement du code pour lire les valeurs de tous les paramètres de la fonction sur la pile des arguments, et pour les assigner à des variables C lors de l'entrée dans la fonction. NO_INIT dit au compilateur que certains paramètres seront utilisés en sortie plutôt qu'en entrée, et qu'ils seront traités avant la fin de la fonction.
L'exemple suivant présente une variante de la fonction rpcb_gettime(). Cette fonction utilise la variable timep uniquement comme variable en sortie, et ne se préoccupe pas de son contenu initial.
bool_t rpcb_gettime(host,timep) char *host time_t &timep = NO_INIT OUTPUT: timep
Normalement, les paramètres de fonction sont initialisés avec les valeurs qu'ils ont dans la pile des arguments. Les typemaps contiennent les portions de code utilisées pour transférer les valeurs Perl dans des paramètres C. Le programmeur, toutefois, est autorisé à surcharger les typemaps et à fournir un code d'initialisation différent (ou supplémentaire).
Le code qui suit montre comment fournir du code d'initialisation pour
les paramètres de fonction. Le code d'initialisation est évalué entre
des guillemets par le compilateur (en utilisant eval()), avant d'être
inséré dans le code généré, de sorte que toute expression devant être
interprétée littéralement (essentiellement $
, @
ou \
) doit
être protégée par des \
. Les variables $var
, $arg
et $type
peuvent être utilisées comme dans les typemaps.
bool_t rpcb_gettime(host,timep) char *host = (char *)SvPV($arg,PL_na); time_t &timep = 0; OUTPUT: timep
Ce procédé ne devrait pas être utilisé pour fournir des valeurs par défaut aux paramètres. Le cas normal d'utilisation est celui où un paramètre de fonction doit être traité par une autre fonction de la librairie avant d'être utilisé. Les paramètres par défaut font l'objet de la prochaine section.
Si l'initialisation commence par =
, elle sera insérée sur la même
ligne que la déclaration de la variable. Si elle commence par ;
ou
+
, elle sera insérée une fois que toutes les variables en entrée
auront été déclarées. Les cas =
et ;
remplacent l'initialisation
fournie normalement par le typemap. Dans le cas +
, l'initialisation
du typemap précédera le code d'initialisation qui suit le +
. Une
variable globale, %v
, est disponible pour le cas vraiment rare où
une initialisation a besoin d'une information venant d'une autre
initialisation.
bool_t rpcb_gettime(host,timep) time_t &timep ; /*\$v{time}=@{[$v{time}=$arg]}*/ char *host + SvOK($v{time}) ? SvPV($arg,PL_na) : NULL; OUTPUT: timep
On peut donner des valeurs par défaut à des paramètres de fonction en rajoutant une instruction d'affectation dans la liste des paramètres. La valeur par défaut peut être un nombre ou une chaîne de caractères. Les valeurs par défaut doivent toujours être utilisées uniquement pour les paramètres les plus à droite.
Afin de permettre à la XSUB de rpcb_gettime()
d'avoir un hôte par
défaut, les paramètres de la XSUB pourraient être réordonnés. La XSUB
appellera alors la vraie fonction rpcb_gettime()
avec les paramètres
placés dans le bon ordre. Perl appellera cette XSUB avec l'une des
instructions suivantes.
$status = rpcb_gettime( $timep, $host );
$status = rpcb_gettime( $timep );
La XSUB ressemblera au code qui suit. Un bloc CODE: est utilisé pour
appeler la vraie fonction rpcb_gettime()
avec les paramètres remis
dans l'ordre correct pour cette fonction.
bool_t rpcb_gettime(timep,host="localhost") char *host time_t timep = NO_INIT CODE: RETVAL = rpcb_gettime( host, &timep ); OUTPUT: timep RETVAL
Le mot-clé PREINIT: permet de déclarer des variables supplémentaires avant que les typemaps soient utilisés. Si une variable est déclarée dans un bloc CODE:, elle suivra tout code généré par le typemap. Cela peut provoquer des fautes de syntaxe en C. Pour forcer la déclaration de la variable avant le code du typemap, placez-la dans un bloc PREINIT:. Le mot-clé PREINIT: peut être utilisé une ou plusieurs fois à l'intérieur d'une XSUB.
Les exemples qui suivent sont équivalents, mais, si le code utilise des typemaps complexes, le premier exemple sera plus sûr.
bool_t rpcb_gettime(timep) time_t timep = NO_INIT PREINIT: char *host = "localhost"; CODE: RETVAL = rpcb_gettime( host, &timep ); OUTPUT: timep RETVAL
Un exemple correct, mais avec des risques d'erreur.
bool_t rpcb_gettime(timep) time_t timep = NO_INIT CODE: char *host = "localhost"; RETVAL = rpcb_gettime( host, &timep ); OUTPUT: timep RETVAL
Le mot-clé SCOPE: permet d'activer un niveau spécial de visibilité (angl. scoping) pour une XSUB donnée. Dans ce cas-là, la XSUB invoquera ENTER et LEAVE automatiquement (Note du Traducteur : les macros ENTER et LEAVE sont décrites dans perlcall).
Afin de supporter des correspondances de type complexes, ce niveau
spécial sera activé automatiquement pour une XSUB si elle utilise une
entrée de typemap contenant le commentaire /*scope*/
.
Pour activer ce niveau spécial :
SCOPE: ENABLE
Pour le désactiver :
SCOPE: DISABLE
Habituellement, les paramètres de XSUB sont évalués juste après l'entrée dans la XSUB. Le mot-clé INPUT: peut être utilisé pour forcer ces paramètres à être évalués un peu plus tard. On peut utiliser le mot-clé INPUT: plusieurs fois dans une XSUB, et ceci pour une ou plusieurs variables en entrée. Ce mot-clé accompagne le mot-clé PREINIT:.
L'exemple suivant montre comment l'évaluation du paramètre en entrée
timep
peut être retardée, après un PREINIT.
bool_t rpcb_gettime(host,timep) char *host PREINIT: time_t tt; INPUT: time_t timep CODE: RETVAL = rpcb_gettime( host, &tt ); timep = tt; OUTPUT: timep RETVAL
Dans cet autre exemple, chacun des paramètres en entrée voit son évaluation retardée.
bool_t rpcb_gettime(host,timep) PREINIT: time_t tt; INPUT: char *host PREINIT: char *h; INPUT: time_t timep CODE: h = host; RETVAL = rpcb_gettime( h, &tt ); timep = tt; OUTPUT: timep RETVAL
Les XSUB peuvent recevoir des listes de paramètres de longueur
variable en spécifiant une ellipse ...
dans la liste des
paramètres. Cette utilisation de l'ellipse est similaire à celle que
l'on trouve en C ANSI. Le programmeur peut déterminer le nombre
d'arguments passés à la XSUB en examinant la variable items
que le
compilateur xsubpp fournit pour chaque XSUB. Grâce à ce mécanisme,
on peut réaliser une XSUB qui accepte une liste de paramètres de
longueur indéterminée.
Le paramètre host de la XSUB rpcb_gettime()
peut être optionnel, de
manière à ce qu'on puisse utiliser l'ellipse pour indiquer que la XSUB
prendra un nombre variable de paramètres. Perl devrait pouvoir appeler
cette XSUB avec chacune des instructions suivantes.
$status = rpcb_gettime( $timep, $host );
$status = rpcb_gettime( $timep );
Voici le code XS, avec l'ellipse :
bool_t rpcb_gettime(timep, ...) time_t timep = NO_INIT PREINIT: char *host = "localhost"; CODE: if( items > 1 ) host = (char *)SvPV(ST(1), PL_na); RETVAL = rpcb_gettime( host, &timep ); OUTPUT: timep RETVAL
Le mot-clé C_ARGS permet de réaliser des XSUB que l'on n'appelle pas de la même manière depuis Perl que depuis C, sans qu'il soit nécessaire d'écrire une section CODE: ou PPCODE:. Le contenu du paragraphe C_ARGS est passé comme argument à la fonction C, sans aucun changement.
Supposons par exemple que la fonction C soit déclarée ainsi :
symbolic nth_derivative(int n, symbolic function, int flags);
et que la valeur par défaut de flags soit contenue dans la variable C default_flags. Supposons que vous souhaitiez réaliser une interface que l'on appellera de la manière suivante :
$second_deriv = $function->nth_derivative(2);
Pour cela, déclarez la XSUB ainsi :
symbolic nth_derivative(function, n) symbolic function int n C_ARGS: n, function, default_flags
Le mot-clé PPCODE: est une variante du mot-clé CODE: qui est utilisée pour indiquer au compilateur xsubpp que le programmeur fournit le code contrôlant la pile des arguments pour les valeurs de retour des XSUB. On souhaite parfois que la XSUB renvoie une liste de valeurs et non une valeur unique. Dans ce cas-là, il faut utiliser PPCODE: et rajouter de manière explicite la liste des valeurs sur la pile. Les mots-clés PPCODE: et CODE: ne sont pas utilisés simultanément dans la même XSUB.
La XSUB suivante appelle la fonction C rpcb_gettime()
et renvoie à Perl
ses deux valeurs de sortie, timep et status, comme une seule liste.
void rpcb_gettime(host) char *host PREINIT: time_t timep; bool_t status; PPCODE: status = rpcb_gettime( host, &timep ); EXTEND(SP, 2); PUSHs(sv_2mortal(newSViv(status))); PUSHs(sv_2mortal(newSViv(timep)));
Remarquez que le programmeur doit fournir le code C assurant l'appel de la vraie fonction rpcb_gettime(), ainsi que le placement correct des valeurs de retour sur la pile des arguments.
Le type de retour void
pour cette fonction indique au compilateur
xsubpp que la variable RETVAL n'est pas nécessaire, qu'elle n'est
pas utlisée, et qu'elle ne devrait pas être créée. Dans la plupart des
cas, il faut utiliser le type de retour void avec l'instruction
PPCODE:.
On utilise la macro EXTEND()
afin de dégager de la place sur la pile
des arguments pour les 2 valeurs de retour. L'instruction PPCODE: fait
en sorte que le compilateur xsubpp crée un pointeur vers la pile
dans la variable SP
; c'est ce pointeur qui est utilisé dans la
macro EXTEND(). Les valeurs sont ensuite rajoutées sur la pile avec
les macros PUSHs().
A présent, la fonction rpcb_gettime()
peut être utilisée depuis Perl
avec l'instruction suivante.
($status, $timep) = rpcb_gettime("localhost");
Lorsque vous travaillez sur les paramètres en sortie avec une section PPCODE:, assurez-vous de traiter l'effet 'set' convenablement. Consultez perlguts pour plus de détails sur cet effet 'set'.
Le programmeur voudra parfois renvoyer simplement undef
ou une
liste vide si la fonction échoue, plutôt qu'une valeur d'état à
part. La fonction rpcb_gettime()
présente justement cette
situation. Nous aimerions que la fonction retourne l'heure si elle
réussit, ou undef si elle échoue. Dans le code Perl suivant, la valeur
de $timep sera soit undef, soit une heure valide;
$timep = rpcb_gettime( "localhost" );
La XSUB suivante n'utilise le type de retour SV *
qu'à titre
d'aide-mémoire, et il utilise un bloc CODE: pour indiquer au
compilateur que le programmeur a fourni tout le code
nécessaire. L'appel de sv_newmortal()
initialise la valeur de retour à
undef, ce qui en fait la valeur de retour par défaut.
SV * rpcb_gettime(host) char * host PREINIT: time_t timep; bool_t x; CODE: ST(0) = sv_newmortal(); if( rpcb_gettime( host, &timep ) ) sv_setnv( ST(0), (double)timep);
Cet autre exemple montre comment on peut mettre un undef explicite dans la valeur de retour, au cas où le besoin s'en ferait ressentir.
SV * rpcb_gettime(host) char * host PREINIT: time_t timep; bool_t x; CODE: ST(0) = sv_newmortal(); if( rpcb_gettime( host, &timep ) ){ sv_setnv( ST(0), (double)timep); } else{ ST(0) = &PL_sv_undef; }
Pour renvoyer une liste vide, il faut utiliser un bloc PPCODE: et ne pas rajouter de valeur de retour sur la pile.
void rpcb_gettime(host) char *host PREINIT: time_t timep; PPCODE: if( rpcb_gettime( host, &timep ) ) PUSHs(sv_2mortal(newSViv(timep))); else{ /* Rien n'est remis sur la pile, donc une */ /* liste vide est renvoyee implicitement */ }
Certaines personnes préfèrent rajouter un return
explicite dans la
XSUB ci-dessus au lieu de laisser l'exécution se poursuivre jusqu'au
bout. Dans ce cas-là, il convient plutôt d'utiliser XSRETURN_EMPTY
,
ce qui garantira que la pile XSUB est ajustée correctement. D'autres
macros sont décrites dans perlguts.
Le mot-clé REQUIRE: sert à indiquer le numéro de version minimal du compilateur xsubpp requis pour compiler le module XS. Un module XS contenant l'instruction suivante ne pourra être compilé qu'avec une version de xsubpp égale ou supérieure à 1.922 :
REQUIRE: 1.922
Ce mot-clé peut être utilisé quand une XSUB doit exécuter des procédures de nettoyage spéciales avant de se terminer. Quand le mot-clé CLEANUP: est utilisé, il doit suivre tout bloc CODE:, PPCODE: ou OUTPUT: présent dans la XSUB. Les instructions spécifiées dans le bloc de nettoyage seront les dernières instructions de la XSUB.
Le mot-clé BOOT: permet de rajouter du code dans la fonction d'amorçage de l'extension. La fonction d'amorçage est générée par le compilateur xsubpp et contient normalement les instructions nécessaires pour enregistrer toute XSUB auprès de Perl. Avec le mot-clé BOOT:, le programmeur peut dire au compilateur de rajouter des instructions supplémentaires dans la fonction d'amorçage.
Ce mot-clé peut être utilisé n'importe quand après le premier mot-clé MODULE, et doit être tout seul sur une ligne. La première ligne blanche après le mot-clé terminera le bloc de code.
BOOT: # Le message suivant sera affiche lors de l'execution de la # fonction d'amorcage. printf("bonjour !\n");
Le mot-clé VERSIONCHECK: correspond aux options -versioncheck
et
-noversioncheck
de xsubpp. Ce mot-clé prime sur les options de
la ligne de commande. La vérification de version est activée par
défaut. Lorsqu'elle est activée, le module XS essaie de vérifier que
son numéro de version est compatible avec celui du module PM.
Pour activer la vérification de version :
VERSIONCHECK: ENABLE
Pour la désactiver :
VERSIONCHECK: DISABLE
Le mot-clé PROTOTYPES: correspond aux options -prototypes
et
-noprototypes
de xsubpp. Ce mot-clé prime sur les options de la
ligne de commande. Les prototypes sont activés par défaut. Lorsqu'ils
sont activés, les XSUB reçoivent des prototypes Perl. Ce mot-clé peut
être utilisé plusieurs fois dans un module XS pour activer et
désactiver les prototypes dans différentes parties du module.
Pour activer les prototypes :
PROTOTYPES: ENABLE
Pour les désactiver :
PROTOTYPES: DISABLE
Ce mot-clé est proche du mot-clé PROTOTYPES: ci-dessus, mais peut être utilisé pour forcer xsubpp à utiliser un prototype donné pour la XSUB. Ce mot-clé prime sur toute autre option ou mot-clé relatif au prototypage, mais n'affecte que la XSUB courante. Consultez « Prototypes » dans la page de manuel perlsub pour plus d'informations sur les prototypes Perl.
bool_t rpcb_gettime(timep, ...) time_t timep = NO_INIT PROTOTYPE: $;$ PREINIT: char *host = "localhost"; CODE: if( items > 1 ) host = (char *)SvPV(ST(1), PL_na); RETVAL = rpcb_gettime( host, &timep ); OUTPUT: timep RETVAL
Le mot-clé ALIAS: permet à une XSUB de recevoir plusieurs noms en
Perl, et de déterminer lequel de ces noms a été utilisé pour
l'invoquer. Les noms en Perl peuvent être complets avec le nom du
paquetage. Chaque alias reçoit un numéro. Le compilateur crée une
variable nommée ix
qui contient le numéro de l'alias
utilisé. Lorsque le nom utilisé pour invoquer la XSUB est celui avec
lequel il a été déclaré, ix
est égal à 0.
L'exemple suivant crée des alias FOO::gettime()
et BAR::gettit()
pour cette fonction.
bool_t rpcb_gettime(host,timep) char *host time_t &timep ALIAS: FOO::gettime = 1 BAR::getit = 2 INIT: printf("# ix = %d\n", ix ); OUTPUT: timep
Ce mot-clé déclare que la XSUB courante sert à garder en réserve la signature de fonction indiquée. S'il est suivi par du texte, ce texte est considéré comme une liste de fonctions qui ont cette signature, et qui doivent être attachées à des XSUB.
Par exemple, si vous avez 4 fonctions multiply(), divide(), add()
et
subtract(), toutes avec la signature
symbolic f(symbolic, symbolic);
vous pouvez les implémenter toutes à la fois avec la XSUB
symbolic interface_s_ss(arg1, arg2) symbolic arg1 symbolic arg2 INTERFACE: multiply divide add subtract
L'avantage de cette approche par rapport au mot-clé ALIAS: est que
l'on peut attacher une fonction supplémentaire remainder()
lors de
l'exécution en utilisant
CV *mycv = newXSproto("Symbolic::remainder", XS_Symbolic_interface_s_ss, __FILE__, "$$"); XSINTERFACE_FUNC_SET(mycv, remainder);
(on suppose dans cet exemple qu'il n'y a pas de section
INTERFACE_MACRO:, car il faut alors utiliser autre chose que
XSINTERFACE_FUNC_SET
)
Ce mot-clé permet de définir une INTERFACE qui procède d'une manière
différente pour récupérer dans la XSUB un pointeur vers la fonction
C. Le texte qui suit ce mot-clé doit indiquer des noms de macros à
utiliser pour récupérer le pointeur de fonction ou pour l'initialiser
dans la XSUB. La macro de récupération reçoit le type de retour,
CV*
, et XSANY.any_dptr
pour ce CV*
. La macro d'initialisation
reçoit cv et le pointeur de fonction.
Les valeurs par défaut de ces macros sont XSINTERFACE_FUNC
et
XSINTERFACE_FUNC_SET
. Le mot-clé INTERFACE avec une liste vide de
fonctions peut être omis si INTERFACE_MACRO est utilisé.
Supposons que, dans l'exemple qui précède, des pointeurs de fonction
pour multiply(), divide(), add(), et subtract()
soient conservés dans
un tableau C global fp[]
, les positions de ces pointeurs par
rapport au début du tableau étant contenues dans des variables nommées
multiply_off
, divide_off
, add_off
, subtract_off
. On peut
alors utiliser
#define XSINTERFACE_FUNC_BYOFFSET(ret,cv,f) \ ((XSINTERFACE_CVT(ret,))fp[CvXSUBANY(cv).any_i32]) #define XSINTERFACE_FUNC_BYOFFSET_set(cv,f) \ CvXSUBANY(cv).any_i32 = CAT2( f, _off )
dans la section C, et
symbolic interface_s_ss(arg1, arg2) symbolic arg1 symbolic arg2 INTERFACE_MACRO: XSINTERFACE_FUNC_BYOFFSET XSINTERFACE_FUNC_BYOFFSET_set INTERFACE: multiply divide add subtract
dans la section XSUB.
Ce mot-clé peut être utilisé pour insérer d'autres fichiers dans le module XS. Les autres fichiers peuvent contenir du code XS. INCLUDE: peut aussi être utilisé pour exécuter une commande générant du code XS à insérer dans le module.
Le fichier Rpcb1.xsh contient notre fonction rpcb_gettime()
:
bool_t rpcb_gettime(host,timep) char *host time_t &timep OUTPUT: timep
Le module XS peut utiliser INCLUDE: pour insérer ce fichier.
INCLUDE: Rpcb1.xsh
Si les paramètres du mot-clé INCLUDE: sont suivis d'un trait vertical
(|
), alors le compilateur interprétera les paramètres comme une
commande.
INCLUDE: cat Rpcb1.xsh |
Le mot-clé CASE permet à une XSUB de se subdiviser en plusieurs parties distinctes, chacune se comportant comme une XSUB virtuelle. CASE: est global à la XSUB : quand on l'utilise, il faut mettre tous les autres mots-clés XS à l'intérieur d'un CASE:. Cela signifie que rien ne peut précéder le premier CASE: dans la XSUB, et que tout code suivant le dernier CASE: y est incorporé.
Un CASE: peut être associé à une condition sur un paramètre de la XSUB
en utilisant la variable-alias ix
(voir « Le mot-clé ALIAS: »), ou
éventuellement la variable items
(voir « Listes de paramètres de longueur variable »). Le dernier CASE correspond au mot-clé default
en C s'il n'est associé à aucune condition. L'exemple suivant montre
un CASE dépendant de ix
dans la fonction rpcb_gettime()
,
laquelle a pour alias x_gettime()
. Lorsque la fonction est invoquée
sous le nom rpcb_gettime()
, elle reçoit les paramètres habituels
(char *host, time_t *timep)
, tandis que, lorsque elle est invoquée
en tant que x_gettime()
, ses paramètres sont inversés, (time_t
*timep, char *host)
.
long rpcb_gettime(a,b) CASE: ix == 1 ALIAS: x_gettime = 1 INPUT: # 'a' est timep, 'b' est host char *b time_t a = NO_INIT CODE: RETVAL = rpcb_gettime( b, &a ); OUTPUT: a RETVAL CASE: # 'a' est host, 'b' est timep char *a time_t &b = NO_INIT OUTPUT: b RETVAL
Cette fonction peut être appelée avec chacune des instructions suivantes. Notez la différence dans l'ordre des arguments.
$status = rpcb_gettime( $host, $timep );
$status = x_gettime( $timep, $host );
L'opérateur unaire & est utilisé pour dire au compilateur qu'il doit
déréférencer l'objet lors de l'appel à la fonction C. On utilise ce
procédé lorsqu'on n'a pas mis de bloc CODE: et que l'objet n'est pas
de type pointeur (l'objet est un int
ou un long
, mais pas un
int*
ni un long*
).
La XSUB suivante génère du code C incorrect. Le code généré par
xsubpp appellera rpcb_gettime()
avec les paramètres (char
*host, time_t timep)
, mais le vrai rpcb_gettime()
attend un
paramètre timep
de type time_t *
et non time_t
.
bool_t rpcb_gettime(host,timep) char *host time_t timep OUTPUT: timep
On résout ce problème en utilisant l'opérateur &
. Le compilateur
xsubpp, à présent, traduira la XSUB en code qui appellera
rpcb_gettime()
de manière correcte, avec les paramètres (char
*host, time_t *timep)
. Il le fait en conservant le &
tel quel, de
sorte que l'appel de fonction est rpcb_gettime(host, &timep)
.
bool_t rpcb_gettime(host,timep) char *host time_t &timep OUTPUT: timep
Des directives de pré-processeur C peuvent prendre place à l'intérieur des blocs BOOT:, PREINIT:, INIT:, CODE:, PPCODE: et CLEANUP:, de même qu'en dehors des fonctions. Les commentaires sont autorisés partout après le mot-clé MODULE. Le compilateur transmet les directives de pré-processeur sans modification et supprime les lignes commentées.
On peut rajouter des commentaires dans les XSUB en mettant un #
sur
une ligne comme premier caractère non blanc. Attention, le commentaire
ne doit pas ressembler à une directive de pré-processeur C, sinon il
sera interprété comme tel. Le moyen le plus simple d'éviter cela
consiste à mettre des caractères blancs devant le #
.
Si vous utilisez des directives de pré-processeur pour choisir entre deux versions d'une fonction, utilisez
#if ... version1 #else /* ... version2 */ #endif
et non pas
#if ... version1 #endif #if ... version2 #endif
car, dans le second cas, xsubpp croira que vous avez défini la
fonction deux fois. Par ailleurs, insérez une ligne blanche avant le
#else
et le #endif
afin qu'ils ne soient pas considérés comme une
partie intégrante du corps de la fonction.
Si une fonction est définie comme une méthode C++, alors elle
considère son premier argument comme un pointeur vers un objet. Le
pointeur vers l'objet est stocké dans une variable nommée
THIS. L'objet doit avoir été créé en C++ avec la fonction new(), et il
doit être béni par Perl (comme avec la fonction bless()
en Perl pur)
avec la macro sv_setref_pv(). La bénédiction d'un objet par Perl
peut être effectuée par le typemap. Un exemple de typemap est donné à
la fin de cette section.
Si la méthode est définie comme statique, elle appellera la fonction C++ en utilisant la syntaxe classe::methode(). Si la méthode n'est pas statique, la fonction sera appelée avec la syntaxe THIS->method().
Les exemples qui suivent utiliseront la classe C++ que voici :
class color { public: color(); ~color(); int blue(); void set_blue( int );
private: int c_blue; };
Les XSUB pour les méthodes blue()
et set_blue()
sont définies avec le
nom de la classe, mais le paramètre pour l'objet (THIS, ou ``self'') est
implicite et il n'est pas mentionné dans la liste.
int color::blue()
void color::set_blue( val ) int val
Les deux fonctions attendent un objet en premier paramètre. Le
compilateur xsubpp donne à cet objet le nom THIS
, et l'utilise pour
appeler la méthode spécifiée. Ainsi, dans le code C++ généré, les
méthodes blue()
et set_blue()
seront appelées de la façon suivante :
RETVAL = THIS->blue();
THIS->set_blue( val );
Si le nom de la fonction est DESTROY, alors la fonction C++
delete
sera appelée avec THIS
comme paramètre.
void color::DESTROY()
Le code C++ appellera delete
.
delete THIS;
Si le nom de la fonction est new, la fonction C++ new
sera
appelée pour créer un objet C++ dynamique. La XSUB s'attendra à
recevoir comme premier argument le nom de la classe, qui sera stocké
dans une variable nommée CLASS
.
color * color::new()
Le code C++ appellera new
:
RETVAL = new color();
Voici maintenant un exemple de typemap qui pourrait être utilisé dans cet exemple C++.
TYPEMAP color * O_OBJECT
OUTPUT # L'objet Perl est beni dans 'CLASS', qui devrait etre un char* # contenant le nom du paquetage servant a le benir. O_OBJECT sv_setref_pv( $arg, CLASS, (void*)$var );
INPUT O_OBJECT if( sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG) ) $var = ($type)SvIV((SV*)SvRV( $arg )); else{ warn( \"${Package}::$func_name() -- $var n'est pas une reference de SV benie\" ); XSRETURN_UNDEF; }
Lorsque vous concevez une interface entre Perl et une librairie C, dans de nombreux cas une traduction directe de C vers XS est suffisante. L'interface sera souvent très proche de C et parfois non intuitive, surtout lorsque la fonction C modifie l'un de ses paramètres. Si le programmeur souhaite réaliser une interface de style plus proche de Perl, la méthode suivante peut l'aider à repérer les parties les plus importantes de l'interface.
Repérez les fonctions C qui modifient leurs paramètres. Les XSUB de ces fonctions peuvent retourner à Perl des listes, ou bien, en cas d'échec, undef ou une liste vide.
Repérez les valeurs qui sont utilisées uniquement par les fonctions C et les XSUB. Si le code Perl lui-même n'a pas besoin d'accéder au contenu d'une valeur, alors il n'est peut-être pas nécessaire de fournir une traduction de cette valeur de C en Perl.
Repérez les pointeurs dans la liste de paramètres de la fonction C et dans les valeurs de retour. Certains pointeurs peuvent être traités en XS avec l'opérateur unaire & sur le nom de la variable, tandis que d'autres nécessitent l'utilisation de l'opérateur * sur le nom du type. En général, il est plus facile de travailler avec l'opérateur &.
Repérez les structures utilisées par les fonctions C. On peut souvent utiliser le typemap T_PTROBJ pour faire en sorte que ces structures puissent être manipulées par Perl comme des objets bénis.
Lorsqu'on a affaire à des structures C, il faut choisir soit T_PTROBJ, soit T_PTRREF pour le type XS. Ces deux types sont conçus pour manipuler des pointeurs vers des objets complexes. Le type T_PTRREF peut être utilisé avec un objet Perl non béni, tandis que le type T_PTROBJ impose que l'objet soit béni. En utilisant T_PTROBJ, on peut obtenir une sorte de vérification de type, car la XSUB essaiera de vérifier que l'objet Perl possède le type attendu.
Le code XS qui suit montre la fonction getnetconfigent()
utilisée avec
ONC+ TIRPC. Cette fonction retourne un pointeur vers une structure C,
et son prototype C est donné plus bas. Cet exemple montrera comment le
pointeur C devient une référence Perl. Perl considérera cette
référence comme un pointeur sur l'objet béni, et cherchera à appeler
un destructeur pour cet objet. Un destructeur sera fourni dans le code
XS pour libérer la mémoire utilisée par getnetconfigent(). En
XS, les destructeurs peuvent être créés en spécifiant une fonction
XSUB dont le nom se termine avec le mot DESTROY. Les destructeurs
XS peuvent être utilisés pour libérer de la mémoire qui aurait été
allouée par malloc()
dans une autre XSUB.
struct netconfig *getnetconfigent(const char *netid);
Un typedef
peut être créé pour struct netconfig
. L'objet Perl
sera béni dans une classe dont le nom correspondra au type C, avec un
suffixe Ptr
; le nom ne doit pas contenir de blanc s'il doit
devenir un nom de paquetage Perl. Le destructeur de l'objet sera mis
dans une classe correspondant à la classe de l'objet, et le mot-clé
PREFIX sera utilisé pour réduire le nom au seul mot DESTROY, comme
prévu dans Perl.
typedef struct netconfig Netconfig;
MODULE = RPC PACKAGE = RPC
Netconfig * getnetconfigent(netid) char *netid
MODULE = RPC PACKAGE = NetconfigPtr PREFIX = rpcb_
void rpcb_DESTROY(netconf) Netconfig *netconf CODE: printf("Maintenant dans NetconfigPtr::DESTROY\n"); free( netconf );
Cet exemple requiert l'entrée de typemap suivante. Consultez la section sur les typemaps pour plus d'informations sur l'ajout de nouveaux typemaps dans une extension.
TYPEMAP Netconfig * T_PTROBJ
Cet exemple sera utilisé avec l'instruction Perl suivante.
use RPC; $netconf = getnetconfigent("udp");
Lorsque Perl détruit l'objet référencé par $netconf, il envoie l'objet
à la fonction XSUB DESTROY fournie. Perl ne peut pas déterminer, et
ça ne l'intéresse pas, que cet objet est une structure C et non un
objet Perl. En ce sens, il n'y a pas de différence entre l'objet créé
par la XSUB getnetconfigent()
et un objet créé par une sous-routine
Perl normale.
Le typemap est un ensemble de fragments de code utilisés par le
compilateur xsubpp pour relier les paramètres de fonction et les
valeurs C à des valeurs Perl. Le fichier typemap peut contenir trois
sections dénommées TYPEMAP
, INPUT
et OUTPUT
. La section
INPUT
indique au compilateur comment convertir des valeurs Perl en
valeurs de certains types C. La section OUTPUT indique au compilateur
comment traduire les valeurs de certains types C en des valeurs
accessibles à Perl. La section TYPEMAP indique au compilateur quels
fragments de code, dans les sections INPUT et OUTPUT, doivent être
utilisés pour faire correspondre un type C donné à une valeur
Perl. Chacune des sections du typemap doit être précédée de l'un des
mots-clés TYPEMAP, INPUT et OUTPUT.
Le typemap par défaut, dans le répertoire ext
du code source Perl,
contient un grand nombre de types utiles qui sont mis à disposition
des extensions Perl. Certaines extensions définissent des typemaps
supplémentaires qu'elles peuvent conserver dans leur propre
répertoire. Ces typemaps supplémentaires peuvent se référer aux tables
de correspondance INPUT et OUTPUT du typemap principal. Le compilateur
xsubpp permet au typemap d'une extension de redéfinir des
correspondances présentes dans le typemap par défaut.
La plupart des extensions qui nécessitent un typemap personnalisé
n'ont besoin que de la section TYPEMAP du fichier typemap. Le typemap
personnalisé utilisé dans l'exemple getnetconfigent()
présenté
précédemment montre ce qui est peut-être l'utilisation typique des
typemaps dans les extensions. Ce typemap est utilisé pour faire
correspondre une structure C au typemap T_PTROBJ. Le typemap utilisé
par getnetconfigent()
est présenté ici. Remarquez que le type C est
séparé du type XS avec une tabulation, et que l'opérateur unaire *
de C est considéré comme une partie du nom du type C.
TYPEMAP Netconfig *<tab>T_PTROBJ
Voici un exemple plus compliqué. Supposons que vous vouliez que
struct netconfig
soit béni dans la classe Net::Config
. Une
manière de le faire est d'utiliser de la manière suivante le caractère
souligné (_) pour séparer les noms de paquetage :
typedef struct netconfig * Net_Config;
et de fournir ensuite une entrée de typemap T_PTROBJ_SPECIAL
qui
relie le souligné au quadruple point (::), puis de déclarer
Net_Config
avec ce type :
TYPEMAP Net_Config T_PTROBJ_SPECIAL
INPUT T_PTROBJ_SPECIAL if (sv_derived_from($arg, \"${(my $ntt=$ntype)=~s/_/::/g;\$ntt}\")) { IV tmp = SvIV((SV*)SvRV($arg)); $var = ($type) tmp; } else croak(\"$var n'est pas de type ${(my $ntt=$ntype)=~s/_/::/g;\$ntt}\")
OUTPUT T_PTROBJ_SPECIAL sv_setref_pv($arg, \"${(my $ntt=$ntype)=~s/_/::/g;\$ntt}\", (void*)$var);
Les sections INPUT et OUTPUT substituent à la volée le souligné au quadruple point, ce qui produit l'effet désiré. Cet exemple donne une idée du pouvoir et de la souplesse du mécanisme des typemaps.
Fichier RPC.xs
: interface avec certaines fonctions de la librairie
de connexion ONC+ RPC.
#include "EXTERN.h" #include "perl.h" #include "XSUB.h"
#include <rpc/rpc.h>
typedef struct netconfig Netconfig;
MODULE = RPC PACKAGE = RPC
SV * rpcb_gettime(host="localhost") char *host PREINIT: time_t timep; CODE: ST(0) = sv_newmortal(); if( rpcb_gettime( host, &timep ) ) sv_setnv( ST(0), (double)timep );
Netconfig * getnetconfigent(netid="udp") char *netid
MODULE = RPC PACKAGE = NetconfigPtr PREFIX = rpcb_
void rpcb_DESTROY(netconf) Netconfig *netconf CODE: printf("NetconfigPtr::DESTROY\n"); free( netconf );
Fichier typemap
: typemap personnalisé pour RPC.xs.
TYPEMAP Netconfig * T_PTROBJ
Fichier RPC.pm
: module Perl pour l'extension RPC.
package RPC;
require Exporter; require DynaLoader; @ISA = qw(Exporter DynaLoader); @EXPORT = qw(rpcb_gettime getnetconfigent);
bootstrap RPC; 1;
Fichier rpctest.pl
: programme de test Perl pour l'extension RPC.
use RPC;
$netconf = getnetconfigent(); $a = rpcb_gettime(); print "time = $a\n"; print "netconf = $netconf\n";
$netconf = getnetconfigent("tcp"); $a = rpcb_gettime("poplar"); print "time = $a\n"; print "netconf = $netconf\n";
Ce document couvre les fonctionnalités supportées par xsubpp
1.935.
Dean Roehrich <roehrich@cray.com> Jul 8, 1996.
Cette traduction française correspond à la version anglaise distribuée avec perl 5.005_02. Pour en savoir plus concernant ces traductions, consultez http://perl.enstimac.fr/.
Thierry Bézecourt <thbz@worldnet.fr>
Régis Julié <Regis.Julie@cetelem.fr>