perlXStut - Tutorial per scrivere estensioni XSUB
Questo tutorial spiegherà i passi necessari alla creazione di un'estensione
Perl. Si assume che abbiate a disposizione anche perlguts, perlapi
e perlxs.
Si comincia con esempi molto semplici per procedere poi con quelli
più complessi, in modo che ogni nuovo esempio aggiunge nuove
caratteristiche. Alcuni concetti potrebbero non essere spiegati
completamente se non più avanti nel tutorial, in modo da rendere
via via più facile al lettore la creazione delle estensioni.
Il tutorial è stato scritto da un punto di vista orientato a Unix.
Laddove sia a conoscenza di comportamenti differenti in altre
piattaforme (ad esempio Win32) ne farò menzione; se trovate che manchi
qualcosa fatemelo sapere, per favore.
In questo tutorial si assume che il programma make utilizzato da Perl
sia chiamato make . Negli esempi che seguono, potete sostituire le
chiamate a make con qualunque programma abbiate configurato nella
vostra installazione di Perl. Per sapere qual è, lanciate
perl -V:make.
Quando scrivete un'estensione Perl di largo consumo, dovreste ricordare
che verrà utilizzata con versioni di Perl differenti da quella
disponibile sulla vostra macchina. Poiché state leggendo questo
documento, la vostra versione di Perl è probabilmente la 5.005 o
successiva, ma gli utenti della vostra estensione potrebbero avere
versioni più datate.
Per comprendere quali tipi di incompatibilità potete aspettarvi, e nel
raro caso che la versione di Perl sulla vostra macchina sia più vecchia
di questo documento, guardate nella sezione
Se gli Esempi danno errore per maggiori dettagli.
Se la vostra estensione utilizza caratteristiche di Perl che non sono
disponibili nelle versioni più vecchie, gli utenti finali apprezzeranno
la presenza di avvisi espliciti nelle prime fasi dell'installazione.
Potreste inserire queste informazioni nel file README, ma al giorno
d'oggi l'installazione delle estensioni viene normalmente effettuata
in maniera automatica, sotto il controllo del modulo CPAN.pm o
di altri strumenti analoghi.
Nelle installazioni basate su MakeMaker, il file Makefile.PL
fornisce l'opportunità tempestiva di effettuare controlli sulle
versioni. È dunque possibile inserire allo scopo dei controlli
tipo:
eval { require 5.007 }
or die <<EOD;
############
### Questo modulo utilizza l'ambiente pincopallino, che non e` disponibile
### prima della versione 5.007 di Perl. Effettuate un aggiornamento prima
### di installare Kara::Mba.
############
EOD
È opinione diffusa che se un sistema non ha la capacità di
caricare dinamicamente una libreria non sia possibile utilizzare XSUB.
Questo non è corretto. Potete utilizzarli, ma dovete collegare
le funzioni XSUB al resto dell'eseguibile perl , creandone uno nuovo.
Questa situazione è simile al Perl 4.
Questo tutorial può essere utilizzato anche su quei sistemi. Il
meccanismo di compilazione di XSUB controllerà il sistema e genererà
una libreria caricabile dinamicamente se possibile, altrimenti una
libreria statica e, opzionalmente, un nuovo eseguibile perl statico
comprendente la libreria generata.
Se doveste aver bisogno di generare un eseguibile statico in un sistema
che è in grado di gestire librerie dinamiche, potete, in tutti gli
esempi che seguono, utilizzare il comando make perl invece del
semplice make .
Se avete generato un eseguibile statico per vostra scelta, per effettuare
le verifiche dovete utilizzare make test static invece del semplice
make test . Su sistemi che non sono in grado di generare librerie
caricabili dinamicamente, sarà sufficiente utilizzare make test .
Che si dia inizio allo spettacolo!
La nostra prima estensione sarà molto semplice. Quando chiamiamo la
funzione nell'estensione, questa stamperà un messaggio ed uscirà.
Lanciate h2xs -An MiaProva . Questo comando crea una directory
chiamata MiaProva, possibilmente sotto ext/ se tale directory esiste
nella directory corrente [Si noti che il comportamento del programma
h2xs varia da versione a versione di Perl, N.d.T.]. Vengono creati
molti file nella nuova directory, inclusi MANIFEST, Makefile.PL,
MiaProva.pm, MiaProva.xs, test.pl e Changes.
Il file MANIFEST contiene i nomi di tutti i file creati nella directory
MiaProva.
Il file Makefile.pl dovrebbe assomigliare a questo [si ricordi che
i commenti qui di seguito sono stati tradotti in italiano, ma saranno
in inglese in quanto generato da h2xs , N.d.T.]:
use ExtUtils::MakeMaker;
# Vedere lib/ExtUtils/MakeMaker.pm per maggiori dettagli su come
# intervenire sul Makefile che verra` scritto.
WriteMakefile(
NAME => 'MiaProva',
VERSION_FROM => 'MiaProva.pm', # trova $VERSION
LIBS => [''], # es. '-lm'
DEFINE => '', # es. '-DHAVE_SOMETHING'
INC => '', # es. '-I/usr/include/other'
);
Il file MiaProva.pm dovrebbe cominciare con qualcosa del genere:
package MiaProva;
use strict;
use warnings;
require Exporter;
require DynaLoader;
our @ISA = qw(Exporter DynaLoader);
# Gli elementi da esportare per default nel namespace del chiamante.
# Nota: non esportate niente senza un'ottima ragione. Utilizzate
# EXPORT_OK se possibile. Evitate di esportare tutte i vostri
# metodi/funzioni/costanti pubblici.
our @EXPORT = qw(
);
our $VERSION = '0.01';
bootstrap MiaProva $VERSION;
# I metodi precaricati vanno inseriti qui.
# I metodi Autoload vanno dopo __END__, e sono elaborati dal programma
# autosplit
1;
__END__
# Qui di seguito trovate la documentazione pilota del vostro modulo.
# Fareste bene a scriverla!
Il resto del file .pm contiene il codice di esempio per fornire la
documentazione dell'estensione.
Infine, il file MiaProva.xs dovrebbe essere qualcosa del genere:
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
MODULE = MiaProva PACKAGE = MiaProva
Modifichiamo il file .xs aggiungendo questo in fondo:
void
ciao()
CODE:
printf("Ciao, Mondo!\n");
Se le righe che iniziano dalla linea ``CODE:'' non sono indentate va tutto
bene. D'altra parte, per motivi di leggibilità, vi suggeriamo di indentare
CODE: di un livello, e le righe che seguono di un ulteriore livello.
A questo punto possiamo lanciare perl Makefile.PL . Questo genererà
il Makefile vero e proprio, necessario a make . L'uscita stampata
dovrebbe essere qualcosa del tipo:
% perl Makefile.PL
Checking if your kit is complete...
Looks good
Writing Makefile for MiaProva
%
che sarebbe [N.d.T.]:
% perl Makefile.PL
Controllo che il sistema sia completo...
Sembra a posto
Scrivo il Makefile per MiaProva
%
Ora, lanciando make verranno stampati dei messaggi simili a quelli
che seguono (alcune righe particolarmente lunghe sono state accorciate per
chiarezza, ed alcune linee estranee sono state cancellate):
% make
umask 0 && cp MiaProva.pm ./blib/MiaProva.pm
perl xsubpp -typemap typemap MiaProva.xs >MiaProva.tc && mv MiaProva.tc MiaProva.c
Please specify prototyping behavior for MiaProva.xs (see perlxs manual)
cc -c MiaProva.c
Running Mkbootstrap for MiaProva ()
chmod 644 MiaProva.bs
LD_RUN_PATH="" ld -o ./blib/PA-RISC1.1/auto/MiaProva/MiaProva.sl -b MiaProva.o
chmod 755 ./blib/PA-RISC1.1/auto/MiaProva/MiaProva.sl
cp MiaProva.bs ./blib/PA-RISC1.1/auto/MiaProva/MiaProva.bs
chmod 644 ./blib/PA-RISC1.1/auto/MiaProva/MiaProva.bs
Manifying ./blib/man3/MiaProva.3
%
ossia [N.d.T.]:
% make
umask 0 && cp MiaProva.pm ./blib/MiaProva.pm
perl xsubpp -typemap typemap MiaProva.xs >MiaProva.tc && mv MiaProva.tc MiaProva.c
Per favore, specificate il tipo di prototipi per MiaProva.xs (vedere
la pagina di manuale perlxs).
cc -c MiaProva.c
Lancio Mkbootstrap per MiaProva ()
chmod 644 MiaProva.bs
LD_RUN_PATH="" ld -o ./blib/PA-RISC1.1/auto/MiaProva/MiaProva.sl -b MiaProva.o
chmod 755 ./blib/PA-RISC1.1/auto/MiaProva/MiaProva.sl
cp MiaProva.bs ./blib/PA-RISC1.1/auto/MiaProva/MiaProva.bs
chmod 644 ./blib/PA-RISC1.1/auto/MiaProva/MiaProva.bs
Genero il manuale da ./blib/man3/MiaProva.3
%
Potete tranquillamente ignorare le linee sul ``tipo di prototipi'' - viene
spiegato nella sezione ``La parola chiave PROTOTIPI:'' in perlxs.
Se vi trovate in un sistema Win32, ed il processo di compilazione
fallisce con errori di collegamento per funzioni nella libreria C,
controllate che il vostro Perl sia configurato per utilizzare PerlCRT
(potete farlo lanciando perl -V:libc ). Se perl è configurato per
utilizzare PerlCRT, dovete assicurarvi che PerlCRT.lib sia copiata
nella stessa directory dove si trova msvcrt.lib, di modo che il
compilatore può trovarla da solo. msvcrt.lib è posta di solito nella
directory lib del compilatore Visual C (ad esempio, C:/DevStudio/VC/lib).
Perl mette a disposizione alcuni sistemi speciali per scrivere degli
script di test in maniera semplice, ma solo per questo script creeremo
il nostro script di test da soli. Create un file chiamato ciao
come segue [il percorso all'eseguibile perl è stato cambiato per
adeguarlo alla locazione più in voga, N.d.T.]:
#! /usr/bin/perl
use ExtUtils::testlib;
use MiaProva;
MiaProva::ciao();
Ora possiamo rendere lo script eseguibile (chmod +x ciao ) e
lanciarlo per vedere:
% ./ciao
Ciao, Mondo!
%
Alla nostra estensione aggiungiamo ora una funzione che prende in ingresso
un unico valore numerico, e restituisce 0 se il numero è pari, 1 se dispari.
Aggiungete quanto segue alla fine del file MiaProva.xs:
int
risulta_pari(input)
int input
CODE:
RETVAL = (input % 2 == 0);
OUTPUT:
RETVAL
Gli spazi bianchi all'inizio di int input non sono strettamente
necessari, ma sono comunque utili per migliorare la leggibilità. Anche
l'uso di un punto-e-virgola a fine riga è opzionale. Per finire, fra
int e input possono essere inseriti spazi a piacere di qualsiasi tipo.
Ora potete rilanciare make per ricostruire la libreria condivisa.
Ripetete gli stessi passi di prima, generando prima Makefile da
Makefile.PL , e poi lanciando make.
Per provare la nostra estensione, abbiamo bisogno di guardare nel file
test.pl . Questo file viene impostato per imitare lo stesso tipo di
struttura di test che ha lo stesso Perl. All'interno dello script di test,
impostate una serie di prove per assicurarvi che il comportamento
dell'estensione sia corretto, stampando ``ok'' quando un test va a buon fine,
e ``not ok'' altrimenti. Impostate l'istruzione di stampa nel blocco BEGIN
per stampare ``1..4'' ed aggiungete quanto segue alla fine del file:
print &MiaProva::risulta_pari(0) == 1 ? "ok 2" : "not ok 2", "\n";
print &MiaProva::risulta_pari(1) == 0 ? "ok 3" : "not ok 3", "\n";
print &MiaProva::risulta_pari(2) == 1 ? "ok 4" : "not ok 4", "\n";
Chiameremo lo script di test attraverso il comando make test .
Dovreste vedere un'uscita simile a questa:
% make test
PERL_DL_NONLAZY=1 /opt/perl5.004/bin/perl (qualcha argomento -I) test.pl
1..4
ok 1
ok 2
ok 3
ok 4
%
Il programma h2xs è il punto di partenza per la creazione delle estensioni.
Negli esempi successivi mostrerà come possiamo utilizzare h2xs per
leggere i file header e generare dei modelli per connetterci alle
funzioni C.
h2xs crea un certo numero di file nella directory dell'estensione. Il file
Makefile.PL è uno script Perl che genererà un Makefile vero e proprio
per costruire, infine, l'estensione. Daremo un'occhiata da vicino più
avanti.
I file .pm e .xs contentono la ``ciccia'' dell'estensione. Il file
.xs contiene le routine C che compongono l'estensione; il file .pm
contiene, invece, quelle funzioni che dicono a perl come caricare la
vostra estensione.
Generando il Makefile e lanciando make si crea una directory
chiamata blib [che sta per ``build library'', ossia una directory di
appoggio per la compilazione e la costruzione della libreria, N.d.T.] sotto
la directory corrente. Questa directory conterrà le librerie condivise
che andremo a costruire. Una volta che le abbiamo provate, possiamo
installarle nella loro posizione finale.
Chiamando lo script di test attraverso make test si ottiene un
risultato importante. In questo modo, perl viene chiamato con tutti
quegli argomenti -I , che fanno in modo che tutti i file dell'estensione
possano essere trovati. È molto importante che, mentre state
ancora provando la vostra estensione, utilizziate make test . Se provate
a lanciare lo script di test direttamente, infatti, avreste solo un
fatal error. Un'altra ragione per cui è importante usare make test
per lanciare il vostro script di test è che se state provando una
versione successiva di qualcosa che già avete installato, il suo
utilizzo vi assicura che state provando la nuova versione, non quella
che avete già.
Quando Perl vede un'istruzione use estensione; , va alla ricerca di un
file che ha lo stesso nome dell'estensione da usare, con aggiunto il
suffisso .pm . Se non riesce a trovare il file, esce con un errore
fatale. Il percorso di ricerca di default è contenuto nell'array @INC .
Nel nostro caso, MiaProva.pm indica a Perl che avrà bisogno delle
estensioni Exporter [Esportatore , N.d.T.] e Dynamic Loader
[Caricatore Dinamico , N.d.T.]. Successivamente, imposta i due array
@ISA e @EXPORT e lo scalare $VERSION ; invine, dice a perl di
effettuare il caricamento e l'inizializzazione del modulo. Perl
chiamerà la propria routine di caricamento dinamico (se ne ha una) e
caricherà la libreria condivisa.
I due array @ISA e @EXPORT sono di estrema importanza. Il primo
contiene una lista degli altri package nei quali andare a cercare
i metodi (o le subroutine) che non esistono nel package corrente. Questo
risulta di solito importante per estensioni orientate agli oggetti (di
cui discuteremo ampiamente molto più avanti), per cui di norma non ha
bisogno di essere modificato.
L'array @EXPORT indica a Perl quali variabili e funzioni dell'estensione
dovrebbero essere impostate nel namespace del package chiamante. Poiché
non potete sapere a priori se l'utente sta già utilizzando per conto suo
i nomi delle vostre variabili o delle vostre funzioni, è di vitale
importanza che selezioniate con estrema cautela cosa volete esportare.
Non esportate nomi di variabili o funzioni per default senza avere
un'ottima ragione.
Come regola generale, se il modulo è orientato agli oggetti allora non
esportate nulla. Se al contrario è solo un insieme di funzioni e variabili,
allora potete esportare attraverso un altro array chiamato @EXPORT_OK .
Quest'ultimo non imposta i nomi delle variabili o delle funzioni nel
namespace automaticamente, ma solo dietro esplicita richiesta dell'utente.
Consultate perlmod per maggiori informazioni.
La variabile $VERSION viene utilizzata per assicurare che il file
.pm e la libreria condivisa sono ``sincronizzate'' l'uno con l'altra. Ogni
volta che effettuate cambiamenti ai file .pm o .xs dovreste
incrementare il valore di questa variabile.
L'importanza di scrivere buoni script di test non verrà mai ribadita
a sufficienza. Dovreste seguire molto da vicino lo stile ``ok/not ok''
[``ok/non ok'', N.d.T.] che Perl stesso utilizza, cosicché diviene molto semplice
e non ambiguo determinare il risultato atteso da ogni singolo test.
Quando trovate e correggete un errore, assicuratevi di aggiungere un
test relativo.
Lanciando make test vi assicurate che il vostro script test.pl sia
avviato ed utilizzi la versione corretta della vostra estensione. Se
avete molti test, potreste copiare lo stile dei test di Perl. Create una
directory chiamata t nella directory dell'estensione, e aggiungete
il suffisso .t ai nomi dei file contenenti i test. Quando lanciate
make test , tutti questi file saranno eseguiti. [Questa organizzazione
dei test è già presente nelle ultime versioni
di h2xs , N.d.T.].
La nostra terza estensione prenderà un valore come argomento in ingresso,
lo arrotonderà e imposterà l'argomento stesso al valore arrotondato.
Aggiungete quanto seque alla fine di MiaProva.xs :
void
round(arg)
double arg
CODE:
if (arg > 0.0) {
arg = floor(arg + 0.5);
} else if (arg < 0.0) {
arg = ceil(arg - 0.5);
} else {
arg = 0.0;
}
OUTPUT:
arg
Modificate Makefile.PL in modo che la linea corrispondente
sia come segue:
'LIBS' => ['-lm'], # e.g., '-lm'
Generate il Makefile e lanciate make . Cambiate il blocco BEGIN
in modo da stampare ``1..9'' e aggiungete quanto segue allo script test.pl :
$i = -1.5; &MiaProva::round($i); print $i == -2.0 ? "ok 5" : "not ok 5", "\n";
$i = -1.1; &MiaProva::round($i); print $i == -1.0 ? "ok 6" : "not ok 6", "\n";
$i = 0.0; &MiaProva::round($i); print $i == 0.0 ? "ok 7" : "not ok 7", "\n";
$i = 0.5; &MiaProva::round($i); print $i == 1.0 ? "ok 8" : "not ok 8", "\n";
$i = 1.2; &MiaProva::round($i); print $i == 1.0 ? "ok 9" : "not ok 9", "\n";
make test dovrebbe ora stampare che tutti i nove test sono corretti.
Osservate che in questi nuovi test l'argomento passato da arrotondare è
una variabile scalare. Potreste chiedervi se potete arrotondare una costante
o un letterale: per vedere cosa succede, aggiungete la seguente riga
al file test.pl :
&MiaProva::round(3);
Lanciate di nuovo make test : Perl termina con un errore fatale. Perl
non vi consente di cambiare il valore delle costanti!
-
Abbiamo fatto alcune modifiche a
Makefile.PL . In questo caso, abbiamo
specificato che un'altra libreria deve essere collegata nella nostra
libreria condivisa, ossia la libreria matematica libm. Mostreremo in
seguito come scrivere XSUB che possono chiamare qualsiasi funzione in una
libreria.
-
Il valore della funzione di arrotondamento non viene passato indietro come
valore restituito, ma cambiando il contenuto della variabile che è stata
passata in ingresso. Sicuramente l'avevate già indovinato quando avete visto
che la funzione
round è di tipo void .
Potete specificare i parametri che saranno passati alla XSUB sulla
riga (o sulle righe) immediatamente successive alla dichiarazione del
valore restituito e del nome della funzione. Ciascuna riga contenente
la descrizione di un parametro di ingresso inizia con spazi vuoti opzionali
e può avere un punto e virgola finale, sempre opzionale.
La lista dei parametri di uscita va inserita alla fine della funzione,
immediatamente dopo la direttiva OUTPUT: . L'uso di RETVAL indica a Perl
che volete mandare il valore indietro al chiamante come valore restituito
dalla funzione XSUB. Nell'esempio 4, volevamo che il ``valore restituito''
fosse messo nella variabile di ingresso originale, per cui l'abbiamo
inserita nella sezione OUTPUT: e non in RETVAL .
Il programma xsubpp prende il codice XS nel file .xs e lo traduce
in codice C, inserendolo in un file dal suffisso .c . Il codice così
creato fa un uso massiccio delle funzioni C all'interno di Perl.
Il programma xsubpp utilizza delle regole per effettuare la conversione
fra i tipi di dato di Perl (scalari, array, ecc.) nei tipi di dati C
(int, char, ecc.). Queste regole sono raccolte in un file di mappatura
dei tipi ($PERLLIB/ExtUtils/typemap ), che è diviso in tre parti.
La prima sezione mappa vari tipi di dato C in un nome che corrisponde,
in una certa maniera, ai differenti tipi di Perl. La seconda parte contiene
codice C che xsubpp utilizza per trattare i parametri di ingresso. La
terza sezione, infine, contiene il codice C che xsubpp utilizza per
trattare i parametri di uscita.
Diamo un'occhiata ad una parte del file .c creato per la nostra
estensione. Il nome del file è MiaProva.c :
XS(XS_MiaProva_round)
{
dXSARGS;
if (items != 1)
croak("Utilizzo: MiaProva::round(arg)");
{
double arg = (double)SvNV(ST(0)); /* XXXXX */
if (arg > 0.0) {
arg = floor(arg + 0.5);
} else if (arg < 0.0) {
arg = ceil(arg - 0.5);
} else {
arg = 0.0;
}
sv_setnv(ST(0), (double)arg); /* XXXXX */
}
XSRETURN(1);
}
Osservate le due linee commentate con ``XXXXX''. Se controllate la prima
sezione del file typemap , noterete che i double sono del tipo
T_DOUBLE. Nella sezione INPUT , un argomento che è T_DOUBLE viene
assegnato alla variabile arg in questo modo: si chiama la routine
SvNV su ``qualcosa'', poi si modifica [typecasting, N.d.T.] in un
double, infine si assegna alla variabile arg . Allo stesso modo, nella
sezione OUTPUT , una volta che arg ha il suo valore finale, viene
passata alla funzione sv_setnv perché venga restituita indietro ala
subroutine chiamante. Queste due funzioni sono spiegate in perlguts;
parleremo più avanti, nella sezione sullo stack degli argomenti,
di cosa significhi ST(0) .
In generale, non è una buona idea scrivere estensioni che modificano
i loro parametri di ingresso come nell'esempio 3. Dovreste invece
probabilmente restituire più valori all'interno di un array, e lasciare
che sia il chiamante a gestirli (è quanto faremo in un esempio successivo).
In ogni caso, per meglio adattarci alla modalità di chiamata di funzioni
C preesistenti, che spesso modificano i loro parametri di ingresso, questo
comportamento è tollerato.
In questo esempio cominceremo a scrivere XSUB che interagiranno con le
librerie C predefinite. Per cominciare, costruiremo una piccola libreria
per conto nostro, per poi consentire a h2xs di scrivere i file .pm
e .xs al posto nostro.
Create una nuova directory chiamata MiaProva2 allo stesso livello della
directory MiaProva. In questa nuova directory, create un'altra directory
chiamata mylib, e spostatevici dentro.
Qui creeremo alcuni file che genereranno una libreria di testo. Questi
includeranno un file sorgente C ed un file di intestazione. Creeremo
anche un Makefile.PL in questa stessa directory. Successivamente, ci
assicureremo che lanciando make al livello di MiaProva2 chiamerà
automaticamente questo Makefile.PL e il Makefile risultante.
Nella directory mylib, create un file mylib.h come segue:
#define TESTVAL 4
extern double foo(int, long, const char*);
Create anche un file mylib.c :
#include <stdlib.h>
#include "./mylib.h"
double
foo(int a, long b, const char *c)
{
return (a + b + atof(c) + TESTVAL);
}
Infine, create un file Makefile.PL come segue:
use ExtUtils::MakeMaker;
$Verbose = 1;
WriteMakefile(
NAME => 'MiaProva2::mylib',
SKIP => [qw(all static static_lib dynamic dynamic_lib)],
clean => {'FILES' => 'libmylib$(LIB_EXT)'},
);
sub MY::top_targets {
'
all :: static
pure_all :: static
static :: libmylib$(LIB_EXT)
libmylib$(LIB_EXT): $(O_FILES)
$(AR) cr libmylib$(LIB_EXT) $(O_FILES)
$(RANLIB) libmylib$(LIB_EXT)
';
}
Assicuratevi di utilizzare un carattere di tabulazione e non gli spazi
nelle righe che cominciano con $(AR) e $(RANLIB) : make non
funzionerà correttamente se utilizzate gli spazi in questi posti.
È stato anche riportato che l'argomento cr a $(AR) non è
necessario nei sistemi Win32.
Creeremo ora i file al livello MiaProva2. Spostatevi in questa directory e
lanciate il seguente comando:
% h2xs -O -n MiaProva2 ./MiaProva2/mylib/mylib.h
Verrà stampato un messaggio di avvertimento sulla riscrittura di MiaProva2,
ma va bene così. I nostri file si trovano in MiaProva2/mylib, e non verranno
toccati.
Il normale file Makefile.PL generato da h2xs non sa niente sulla
directory mylib. Abbiamo bisogno di dirgli che c'è una sottodirectory,
e che genereremo una liberira lì dentro. Aggiungiamo gli argomenti
MYEXTLIB alla chiamata WriteMakefile in modo che risulti come
segue:
WriteMakefile(
'NAME' => 'MiaProva2',
'VERSION_FROM' => 'MiaProva2.pm', # finds $VERSION
'LIBS' => [''], # e.g., '-lm'
'DEFINE' => '', # e.g., '-DHAVE_SOMETHING'
'INC' => '', # e.g., '-I/usr/include/other'
'MYEXTLIB' => 'mylib/libmylib$(LIB_EXT)',
);
e quindi alla fine aggiungiamo una subroutine (che rimpiazzerà quella
pre-esistente). Ricordatevi di utilizzare il carattere di tabulazione
per indentare la riga che comincia con cd !
sub MY::postamble {
'
$(MYEXTLIB): mylib/Makefile
cd mylib && $(MAKE) $(PASSTHRU)
';
}
Modifichiamo anche il file MANIFEST in modo che rifletta accuratamente
i contenuti della nostra estensione. La singola riga che dice ``mylib''
va rimpiazzata con le seguenti tre righe:
mylib/Makefile.PL
mylib/mylib.c
mylib/mylib.h
Per mantenere il nostro namespace in ordine, modificate il file .pm
e cambiate la variabile @EXPORT in @EXPORT_OK . Infine, nel file
.xs scrivete una riga #include come segue:
#include "mylib/mylib.h"
Aggiungete anche la seguente definizione di funzione alla fine del
file .xs :
double
foo(a,b,c)
int a
long b
const char * c
OUTPUT:
RETVAL
Ora abbiamo anche bisogno di creare un file typemap perché Perl non ha
al momento un supporto di default per il tipo const char * . Create un
file chiamato typemap nella directory MiaProva2 e scriveteci quanto segue:
const char * T_PV
Ora lanciate perl Makefile.PL in MiaProva2. Notate che viene creato un
Makefile anche nella directory mylib. Lanciate make ed osservate che
entra nella directory mylib e lancia make da lì dentro.
Ora modificate lo script test.pl e cambiate il blocco BEGIN per
stampare ``1..4''; aggiungete anche le righe seguenti alla fine dello
script:
print &MiaProva2::foo(1, 2, "Ciao, mondo!") == 7 ? "ok 2\n" : "not ok 2\n";
print &MiaProva2::foo(1, 2, "0.0") == 7 ? "ok 3\n" : "not ok 3\n";
print abs(&MiaProva2::foo(0, 0, "-3.4") - 0.6) <= 0.01 ? "ok 4\n" : "not ok 4\n";
(Quando sia necessario fare confronti con valori a virgola mobile,
è meglio non farlo sull'uguaglianza, ma piuttosto sul fatto che la
differenza fra quanto ci aspettavamo ed il valore effettivo risulti
al di sotto di un certo valore prefissato (chiamato epsilon), che
nel nostro caso è pari a 0.01).
Lanciate make test e tutto dovrebbe andare bene.
Diversamente dagli esempi precedenti, abbiamo lanciato h2xs su un
vero file di intestazione. Questo ha fatto sì che apparissero alcune
parti in più sia nel file .pm che nel file .xs .
-
Nel file
.xs c'è ora una direttiva di inclusione contenete il percorso
assoluto al file di intestazione mylib.h . L'abbiamo modificato in un
percorso relativo in modo da poter spostare la directory dell'estensione
se lo vogliamo.
-
C'è ora un po' di codice C nuovo che è stato aggiunto al file
.xs . Lo
scopo della routine constant è di far sì che i valori che sono
#define nel file di intestazione siano accessibili dallo script
Perl (chiamando TESTVAL o &MiaProva2::TESTVAL ). C'è anche un po'
di codice XS per consentire le chiamate alla routine constant .
-
In origine, il file
.pm esportava il nome TESTVAL nell'array
@EXPORT . Questo potrebbe portare a collisioni di nomi. Una buona regola
di massima è che se una #define verrà utilizzata solamente dalle stesse
routine C, e non dall'utente finale, dovrebbero essere rimosse dall'array
@EXPORT . In alternativa, se non vi preoccupa utilizzare il ``nome
completo'' di una variabile, potete spostare la maggior parte (al più tutte)
delle definizioni dall'array @EXPORT dentro @EXPORT_OK .
-
Se il nostro file di intestazione contiene direttive
#include , queste
non verrebbero considerate da h2xs . Al momento non c'è nessuna buona
soluzione per questo problema.
-
Abbiamo anche detto a Perl della libreria che abbiamo costruito nella
directory mylib. Questo ha richiesto l'aggiunta della variabile
MYEXTLIB nella chiamata a WriteMakefile , ed il rimpiazzamento
della routine di postambolo perché si sposti nella sottodirectory e lanci
make . Il Makefile.PL per la libreria è un po' più complicato, ma non
è così eccessivo. Di nuovo, abbiamo rimpiazzato la routine di postambolo
per inserire il nostro codice. Questo specifica semplicemente che la
libreria da creare è un archivio statico (in opposizione ad una libreria
caricabile dinamicamente), ed inserito il comadi per costruirla.
Il file .xs dell'ESEMPIO 4 contiene alcuni nuovi elementi. Per
comprendere il loro significato, prestate attenzione alla riga
MODULE = MiaProva2 PACKAGE = MiaProva2
Tutto ciò che precede questa riga è codice C puro, che descrive quali
intestazioni includere, e definisce alcune funzioni di convenienza.
In questa sezione non viene effettuata alcuna traduzione: a parte
l'eliminazione della documentazione POD immersa (vedere perlpod), il
contenuto viene messo così com'è nel file C generato.
Tutto ciò che segue questa riga costituisce la descrizione delle funzioni
XSUB. Queste descrizioni sono tradotte da xsubpp in codice C che
implementa le funzioni utilizzando le convenzioni di chiamata di Perl,
e che dunque rende tale funzioni visibili all'interprete Perl.
Prestate particolare attenzione alla funzione constant . Questo nome
appare due volte nel file .xs generato: una nella prima parte, come
funzione C statica, l'altra nella seconda parte, quando viene definita
un'interfaccia XSUB alla funzione C statica suddetta.
Questo è piuttosto tipico in un file .xs : di solito, il file .xs
fornisce un'interfaccia ad una funzione C esistente. Questa funzione C
è definita da qualche parte (una libreria esterna, o nella prima parte
del file .xs ), e un'interfaccia Perl per questa funzione (ossia, la
``colla con il Perl'') è descritta nella seconda parte del file .xs .
La situazione in ESEMPIO 1, ESEMPIO 2 e ESEMPIO 3, ove tutto
il lavoro viene fatto dentro il ``collante'', è più un'eccezione che la
regola.
Nell'ESEMPIO 4 la seconda parte del file .xs contiene la
seguente descrizione di una XSUB:
double
foo(a,b,c)
int a
long b
const char * c
OUTPUT:
RETVAL
Osservate che, contrariamente a quanto riportato in ESEMPIO 1,
ESEMPIO 2 e ESEMPIO 3, tale descrizione non contiene il codice
vero e proprio di cosa viene fatto quando viene chiamata la funzione
Perl foo() . Per capire cosa sta succedendo, si può aggiungere una
sezione CODE a questa XSUB:
double
foo(a,b,c)
int a
long b
const char * c
CODE:
RETVAL = foo(a,b,c);
OUTPUT:
RETVAL
In ogni caso, queste due XSUB forniscono un codice C generato praticamente
uguale: il compilatore xsubpp è abbastanza intelligente da intuire
la sezione CODE: dalle prime due righe della descrizione XSUB. Che
dire della sezione OUTPUT: ? È assolutamente la stessa! Anche la
sezione OUTPUT: può essere rimossa, sempre che le sezioni CODE:
o PPCODE: non siano specificate: xsubpp può quindi vedere che ha
bisogno di generare una sezione di chiamata a funzione, e genererà anche
la sezione OUTPUT . Per quanto detto, un'abbreviazione della XSUB
diventa:
double
foo(a,b,c)
int a
long b
const char * c
Possiamo fare la stessa cosa con la XSUB
int
is_even(input)
int input
CODE:
RETVAL = (input % 2 == 0);
OUTPUT:
RETVAL
dell'ESEMPIO 2? Per farlo, avremmo bisogno di definire una funzione
C int is_even(int input) . Come abbiamo visto il Anatomia di un file .xs , un possibile posto per questa definizione si trova nella prima parte
del file .xs . A tutti gli effetti, una funzione
int
is_even(int arg)
{
return (arg % 2 == 0);
}
è probabilmente troppo in questo caso. Qualcosa di più semplice come una
#define servirà egregiamente allo scopo:
#define is_even(arg) ((arg) % 2 == 0)
Una volta inserito nella prima parte del file .xs , la ``colla Perl''
diventa semplice:
int
is_even(input)
int input
Questa tecnica di separazione della parte ``collante'' da quella che
``lavora'' presenta anche degli svantaggi: se volete cambiare un'interfaccia
Perl, dovete farlo in due punti del vostro codice. In ogni caso, vi
consente di rimuovere parecchio rumore, e rende la parte ``che lavora''
indipendente dalle idiosincrasie della convenzione di chiamata di Perl.
(Infatti, non c'è niente di specifico di Perl nella descrizione data; una
versione di xsubpp differente avrebbe potuto anche tradurlo in codice
collante TCL o Python).
Con il completamento dell'ESEMPIO 4, abbiamo ora un modo semplice per
simulare alcune librerie ``reali'' la cui interfaccia potrebbe non essere
la più pulita al mondo. Continueremo ora con una discussione degli argomenti
passati al compilatore xsubpp.
Quando specificate gli argomenti per le routine nel file .xs , in realtà
state passando tre informazioni per ciascuno degli argomenti. La prima
è l'ordine dell'argomento rispetto agli altri (primo, secondo, ecc.). La
seconda è il tipo di argomento, e consiste della dichiarazione del tipo
C dell'argomento (per esempio int, char*, ecc.). La terza informazione
è la convenzione di chiamata per l'argomento rispetto alla funzione
di libreria.
Mentre Perl passa gli argomenti alle funzioni per riferimento, C passa
gli argomenti per valore; per implementare una funzione C che modifica
i dati di uno degli ``argomenti'', l'argomento vero e proprio di questa
funzione C dovrebbe essere un puntatore al dato. Per questo, due funzioni
C con le seguenti dichiarazioni
int string_length(char *s);
int upper_case_char(char *cp);
potrebbero avere semantiche completamente differenti: la prima potrebbe
leggere un array di caratteri puntati da s , mentre la seconda potrebbe
agire sul dato puntato da cp e manipolare *cp (utilizzando il valore
di ritorno, diciamo, come indicatore di successo). Da Perl queste due
funzioni verrebbero utilizzate in maniere completamente differenti.
Si può comunicare questa informazione a xsubpp rimpiazzando * prima
dell'argomento con & . & indica che l'argomento dovrebbe essere
passato ad una funzione di libreria attraverso il suo indirizzo. Le due
funzioni verrebbero dunque XSUB-ificate come segue:
int
string_length(s)
char * s
int
upper_case_char(cp)
char &cp
Considerate ad esempio:
int
foo(a,b)
char &a
char * b
Il primo argomento Perl di questa funzione verrebbe trattato come un
carattere ed assegnato alla variabile a , laddove il suo indirizzo verrebbe
passato alla funzione C foo . Il secondo argomento Perl verrebbe trattato
come un puntatore a stringa ed assegnato alla variabile b . Il valore
di b verrebbe passato alla funzione C foo . La reale chiamata alla
funzione C che xsubpp genera sarebbe dunque come segue:
foo(&a, b);
xsubpp interpreterà le seguenti liste di argomenti di funzioni
nella stessa identica maniera:
char &a
char&a
char & a
Comunque, per maggior semplicità di comprensione, si suggerisce di
mettere & vicino al nome della variabile e lontano dal tipo, e di
mettere * vicino al tipo e lontano dal nome della variabile (come
nella chiamata a foo riportata). Facendo così, è facile capire
esattamente cosa verrà passato alla funzione C -- ossia, qualunque cosa
compaia ``nell'ultima colonna''.
Dovreste prestare la massima cura nel cercare di passare alla funzione il
tipo di variabile che questa si aspetta, quando possibile: vi risparmierà
parecchi guai nel lungo termine.
Se guardate una qualunque parte del codice C generato in uno degli esempi
(eccetto ESEMPIO 1), noterete che compaiono un certo numero di
riferimenti a ST(n) , dove n è usualmente 0. ST è, in realtà, una
macro che punta all'n-simo argomento dello stack degli argomenti.
ST(0) , dunque, è il primo argomento sullo stack e perciò il
primo argomento passato alla XSUB, ST(1) il secondo argomento, e
così via.
Quando scrivete la lista degli argomenti alla XSUB nel file .xs , ciò
dice a xsubpp quali argomenti corrispondono ai differenti elementi
dello stack (ossia, il primo della lista è il primo argomento, e così via).
State chiedendo qualche disastro se non li elencate nello stesso ordine
atteso dalla relativa funzione.
I valori effettivi sullo stack degli argomenti sono puntatori ai valori
passati. Quando un argomento viene elencato come valore OUTPUT , il
suo corrispondente valore sullo stack (ossia, ST(0) nel caso del primo
argomento) viene modificato. Potete verificarlo guardando il codice C
generato per l'ESEMPIO 3; il codice per la routine XSUB round()
contiene alcune righe come quelle che seguono:
double arg = (double)SvNV(ST(0));
/* Arrotonda i contenuti della variabile arg */
sv_setnv(ST(0), (double)arg);
La variabile arg è inizialmente impostata prendendo il valore da
ST(0) , per poi essere rimessa in ST(0) alla fine della routine.
Le XSUB possono anche restituire liste, non solo scalari. Questo
va fatto manipolando i valori sullo stack ST(0) , ST(1) , ecc. in
una maniera leggermente differente. I dettagli li trovate in perlxs.
Le XSUB possono anche evitare la conversione automatica degli argomenti
delle funzioni Perl in argomenti delle funzioni C - i dettagli sono in
perlxs. Qualcuno preferisce effettuare la conversione manualmente
ispezionando ST(i) , anche nei casi in cui la conversione automatica
risulterebbe corretta, obiettando che questo rende la logica di una XSUB
più chiara. Confrontate con Tirare fuori la ``ciccia'' dalle XSUB per
un simile compromesso riguardo la separazione della ``colla Perl'' e
della ``sezione di lavoro'' di una XSUB.
Mentre gli esperti potrebbero dubitare di tutti questi idiomi, un
neofita delle viscere di Perl potrebbe preferire una strada che è quanto più
possibile scevra di elementi specifici delle interiora stesse, il che
significa avvalersi della conversione automatica e della generazione
automatica, come in Tirare fuori la ``ciccia'' dalle XSUB. Questo
approcio ha anche il vantaggio aggiuntivo di proteggere chi scrive
XSUB da futuri cambiamenti nell'API di Perl.
Potreste a volte voler aggiungere qualche metodo o funzione aggiuntiva
come aiuto per rendere l'interfaccia fra Perl e la vostra estensione
più semplice o più comprensibile. Queste routine dovrebbero risiedere
nel file .pm . Se sono caricate automaticamente dall'estensione stessa
o solo quando sono chiamate dipende da dove la definizione della
subroutine viene posta nel file .pm . Potete consultare anche
AutoLoader per un metodo alternativo di tenere e caricare le vostre
subroutine aggiuntive.
Non avete assolutamente alcuna scusa per evitare di documentare la vostra
estensione. La documentazione va posta nel file .pm . Questo file
verrà immesso in pod2man , e la documentazione immersa verrà convertita
nel formato delle manpage [pagine del manuale stile Unix, N.d.T.] e
posta nella directory blib. Verrà infine copiata nella directory delle
pagine di manuale di Perl quando l'estensione verrà installata.
Potete alternare la documentazione ed il codice Perl all'interno del file
.pm . In effetti, se volete utilizzare il metodo dell'auto-caricamento,
dovete fare proprio così, come spiegano i commenti all'interno del file
.pm .
Consultate perlpod per maggiori informazioni sul formato pod.
Una volta che la vostra estensione è completa e passa tutti i test,
l'installazione è piuttosto semplice: non dovete far altro che lanciare
make install . Avrete bisogno di avere i permessi in scrittura nella
directory dove è installato Perl, o dovrete chiedere all'amministratore
di sistema di fare l'installazione per voi.
In alternativa, potete specificare la directory esatta dove porre i file
dell'estensione chiamando make install PREFIX=/directory/di/destinazione
(potreste aver bisogno di insirire l'aggiunta fra make e install se
avete una versione di make un po' bizzarra). Questo risulta
particolarmente utile se state costruendo un'estensione che verrà
distribuita in molti sistemi. Potete allora semplicemente archiviare i
file nella directory di destinazione, e distribuire questo archivio
nei sistemi di destinazione.
In questo esempio avremo di nuovo a che fare con lo stack degli argomenti.
Gli esempi precedenti hanno tutti restituito un solo, unico valore; qui
creeremo un'estensione che restituisce un array.
Questa estensione è molto polarizzata verso Unix (utilizza
struct statfs e la chiamata di sistema statfs ). Se non vi trovate
su un sistema Unix, potete sostituire a statfs una qualsiasi altra
funzione che restituisce più valori, potete scrivere direttamente i valori
che devono essere restituiti (sebbene questo risulterà un po' più
difficile da controllare in caso di condizioni di errore) o potete
semplicemente saltare questo esempio. Se cambiate la XSUB, assicuratevi
di cambiare anche i test in modo da allineare i cambiamenti.
Tornate nela directory MiaProva e aggiungete il seguente codice alla fine
di MiaProva.xs :
void
statfs(path)
char * path
INIT:
int i;
struct statfs buf;
PPCODE:
i = statfs(path, &buf);
if (i == 0) {
XPUSHs(sv_2mortal(newSVnv(buf.f_bavail)));
XPUSHs(sv_2mortal(newSVnv(buf.f_bfree)));
XPUSHs(sv_2mortal(newSVnv(buf.f_blocks)));
XPUSHs(sv_2mortal(newSVnv(buf.f_bsize)));
XPUSHs(sv_2mortal(newSVnv(buf.f_ffree)));
XPUSHs(sv_2mortal(newSVnv(buf.f_files)));
XPUSHs(sv_2mortal(newSVnv(buf.f_type)));
XPUSHs(sv_2mortal(newSVnv(buf.f_fsid[0])));
XPUSHs(sv_2mortal(newSVnv(buf.f_fsid[1])));
} else {
XPUSHs(sv_2mortal(newSVnv(errno)));
}
Avrete anche bisogno di aggiungere quanto segue all'inizio del file
.xs , subito dopo l'inclusione di XSUB.h :
#include <sys/vfs.h>
Aggiungete anche il seguente frammento di codice a test.pl ,
ricordandovi di incrementare la stringa ``1..9'' in ``1..11''
nel blocco BEGIN :
@a = &MiaProva::statfs("/maddeche");
print ((scalar(@a) == 1 && $a[0] == 2) ? "ok 10\n" : "not ok 10\n");
@a = &MiaProva::statfs("/");
print scalar(@a) == 9 ? "ok 11\n" : "not ok 11\n";
Questo esempio ha aggiunto un po' di concetti nuovi, li analizzeremo
uno alla volta.
-
La direttiva
INIT: contiene codice che verrà inserito immediatamente
dopo che lo stack degli argomenti viene decodificato. Il linguaggio C
non consente dichiarazioni di variaible in posizioni arbitrarie
all'interno di una funzione, per cui questa è di norma la soluzione migliore
per dichiarare le variabili locali di cui la XSUB ha bisogno. (In
alternativa, è possibile mettere l'intera sezione PPCODE: in
parentesi graffe, ed aggiungere queste dichiarazioni all'inizio).
-
Questa routine restituisce anche un numero di argomenti differenti
dipendentemente dal fatto che la chiamata a
statfs abbia successo
o meno. Se ci sono errori, viene restituito il numero dell'errore
come unico elemento di un array. Se la chiamata ha successo, viene
restituito un array di 9 elementi. Poiché questa funzione riceve un solo
argomento, abbiamo bisogno di fare spazio sullo stack per tenere i
9 valori che potrebbero essere restituiti.
Otteniamo tutto ciò utilizzando la direttiva PPCODE: , piuttosto che
la direttiva CODE: . Questo indica a xsubpp che utilizzeremo i
valori di ritorno che saranno messi sullo stack degli argomenti da noi
stessi.
-
Quando vogliamo inserire i valori da restituire al chiamante sullo stack,
utilizziamo qualla serie di marco che cominciano con
XPUSH . Ce ne sono
cinque versioni differenti a seconda dei tipi delle variabili: interi,
interi senza segno, double, stringhe e scalari Perl. Nel nostro
esempio abbiamo inserito uno scalare Perl sullo stack. (Infatti questa
è l'unica macro che può essere utilizzata per restituire valori
multipli).
Le macro XPUSH* estenderanno lo stack di ritorno automaticamente, in
modo da prevenire possibili riempimenti. Dovete inserire i valori
nello stack nello stesso ordine con cui li volete vedere nel programma
chiamante.
-
I valori inseriti nello stack di ritorno della XSUB sono in realtà
degli SV mortali. Sono resi tali in modo che una volta che i valori
vengono copiati dal programma chiamate, le variabili SV che li
contengono possono essere deallocate. Se non fossero mortali, infatti,
continuerebbero ad esistere dopo il ritorno dalla XSUB, senza però
essere accessibili; questo risulterebbe dunque in uno spreco di memoria.
-
Se fossimo interessati alle prestazioni, ma non nella compattezza del
codice, nel ramo relativo ad una chiamata andata a buon fine non
utilizzeremmo le macro
XPUSH* , ma quelle PUSH , pre-estendendo
lo stack una volta per tutte prima di inserire i valori:
EXTEND(SP, 9);
Per contro, si ha bisogno di calcolare in anticipo il numero di elementi
da restituire (sebbene estendere lo stack oltre quanto necessario
non abbia tipicamente altre conseguenze se non un maggior consumo di memoria).
In maniera analoga, nel ramo in cui la chiamata a statfs fallisce
potremmo utilizzare PUSH senza estendere lo stack: il riferimento
alla funzione Perl arriva alla XSUB sullo stack, per cui c'è sempre
spazio sufficiente per un singolo valore da restituire.
In questo esempio riceveremno un riferimento ad un array come parametro
di ingresso, e restituiremo un riferimento ad un array di hash. Potremo
in questo modo dimostrare come sia possibile manipolare strutture dati
Perl complesse all'interno di una XSUB.
L'estensione di questo esempio è artificiosa. È basata
sul codice dell'esempio precedente; chiama la funzione statfs più
volte, accettando in ingresso un riferimento ad un array di nomi di
file, e restituendo un riferimento ad un array di hash, ciascuna
contenente i dati per i filesystem.
Ritornate nella directory MiaProva ed aggiungete il codice che segue alla
fine di MiaProva.xs :
SV *
multi_statfs(paths)
SV * paths
INIT:
AV * results;
I32 numpaths = 0;
int i, n;
struct statfs buf;
if ((!SvROK(paths))
|| (SvTYPE(SvRV(paths)) != SVt_PVAV)
|| ((numpaths = av_len((AV *)SvRV(paths))) < 0))
{
XSRETURN_UNDEF;
}
results = (AV *)sv_2mortal((SV *)newAV());
CODE:
for (n = 0; n <= numpaths; n++) {
HV * rh;
STRLEN l;
char * fn = SvPV(*av_fetch((AV *)SvRV(paths), n, 0), l);
i = statfs(fn, &buf);
if (i != 0) {
av_push(results, newSVnv(errno));
continue;
}
rh = (HV *)sv_2mortal((SV *)newHV());
hv_store(rh, "f_bavail", 8, newSVnv(buf.f_bavail), 0);
hv_store(rh, "f_bfree", 7, newSVnv(buf.f_bfree), 0);
hv_store(rh, "f_blocks", 8, newSVnv(buf.f_blocks), 0);
hv_store(rh, "f_bsize", 7, newSVnv(buf.f_bsize), 0);
hv_store(rh, "f_ffree", 7, newSVnv(buf.f_ffree), 0);
hv_store(rh, "f_files", 7, newSVnv(buf.f_files), 0);
hv_store(rh, "f_type", 6, newSVnv(buf.f_type), 0);
av_push(results, newRV((SV *)rh));
}
RETVAL = newRV((SV *)results);
OUTPUT:
RETVAL
Aggiungete anche quanto segue allo script test.pl , portando il numero
di test a ``1..13'' nel blocco BEGIN :
$results = MiaProva::multi_statfs([ '/', '/maddeche' ]);
print ((ref $results->[0]) ? "ok 12\n" : "not ok 12\n");
print ((! ref $results->[1]) ? "ok 13\n" : "not ok 13\n");
Ci sono un certo numero di concetti nuovi, descritti qui a seguire.
-
Questa funzione non utilizza una
typemap . Al contrario, dichiariamo che
accetta un parametro SV* (scalare), e che restituisce un valore SV* ;
ci prendiamo direttamente cura di questi scalari all'interno del codice.
Poiché stiamo restituendo un solo valore, non abbiamo bisogno di una
direttiva PPCODE: - al contrario, utilizziamo le direttive CODE: e
OUTPUT: .
-
Quando si ha a che fare con i riferimenti, è importante trattarli con
cautela. Il blocco
INIT: prima di tutto si assicura che SvROK
abbia valore vero, il che indica che paths è un riferimento valido.
Successivamente verifica che l'oggetto riferito da paths sia un array,
utilizzando SvRV per dereferenziarlo, e SvTYPE per scoprire qual è
il suo tipo. Come controllo addizionale verifica che l'array sia non
vuoto, utilizzando la funzione av_len (la quale restituisce -1 quando
l'array è vuoto). La macro XSRETURN_UNDEF viene utilizzata per
abortire la XSUB e restituire il valore undef laddove le condizioni
suddette non siano raggiunte.
-
Manipoliamo parecchi array in questa XSUB. Osservate che un array viene
rappresentato internamente attraverso un puntatore
AV* . Le funzioni e
le macro per manipolare gli array sono simili alle funzioni in Perl:
av_len restituisce l'indice più elevato in un AV* , similmente
a $#array ; av_fetch prende un singolo valore scalare da un array,
dato il suo indice; av_push inserisce un valore scalare in fondo
all'array, estendendolo automaticamente se necessario.
Nello specifico, leggiamo i nomi dei percorsi uno alla volta dall'array
di ingresso, ed inseriamo i risultati in un array di uscita (risultati)
nello stesso ordine. Se statfs fallisce, l'elemento inserito
nell'array di uscita è il valore di errno dopo tale fallimento. Se
al contrario statfs va a buon fine, il valore inserito nell'array
di uscita è un riferimento ad una hash contenente alcune delle
informazioni contenute nella struttura struct statfs .
Così come per lo stack di uscita, sarebbe possibile (ottenedo un
piccolo vantaggio di prestazioni) pre-estendere l'array di uscita prima
di inserirvi i dati, poiché sappiamo in anticipo quanti elementi
dobbiamo restituire:
av_extend(results, numpaths);
-
In questa funzione stiamo solo effettuando un'operazione su hash, ossia
immagazzinando un nuovo scalare relativo ad una data chiave utilizzando
hv_store . Una hash è rappresentata attraverso un puntatore HV* .
Come gli array, le funzioni per manipolare le hash in una XSUB
rispecchiano le funzionalità disponibili direttamente in Perl.
Per i dettagli consultate perlguts e perlapi.
-
Per creare un riferimento, utilizziamo la funzione
newRV . Osservate
che potete trasformare un AV* o un HV* in un tipo SV* in questo
come in molti altri casi. Ciò rende possibile prendere riferimenti ad
array, hash e scalari con la stessa funzione. Di contro, la funzione
SvRV restituisce sempre un SV* , che potrebbe aver bisogno di
essere trasformata nel tipo appropriato se deve rappresentare qualcosa
di differente da uno scalare (si può verificare con SvTYPE ).
-
A questo punto, xsubpp deve fare veramente poco - le differenze fra
MiaProva.xs e MiaProva.c sono minime.
XPUSH degli argomenti E impostazione di RETVAL E assegnazione del
valore di ritorno ad un array.
Impostare $!
Potreste pensare che passare file ad una XS sia difficile, con tutti
i typeglob e roba associata. Beh, non è così.
Supponete che per qualche strana ragione abbiamo bisogno di un
wrapper per la funzione di libreria standard fputs() . Tutto
ciò di cui abbiamo bisogno è questo:
#define PERLIO_NOT_STDIO 0
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include <stdio.h>
int
fputs(s, stream)
char * s
FILE * stream
Il lavoro vero e proprio viene fatto nel file typemap standard.
Ma vi perdete tutta quella bella serie di operazioni fatte dai
layer di perlio. Qui viene chiamata la funzione stdio fputs() ,
che non ne sa niente.
La typemap offre tre varianti di PerlIO * : InputStream (T_IN),
InOutStream (T_INOUT) e OutputStream (T_OUT>. Un PerlIO *
nudo e crudo è considerato un T_INOUT. Se questo ha rilevanza nel
vostro codice (vedete sotto perché potrebbe averla), #define o
typedef uno dei nomi specifici ed utilizzatelo come tipo
dell'argomento o del risultato nel vostro file XS.
La typemap standard non contiene PerlIO * prima di perl 5.7,
ma ha questre tre varianti. Utilizzare direttamente un PerlIO *
risulta non compatibile con le versioni precedenti di perl, a meno
che non forniate una vostra typemap .
Per stream che vengono da perl la differenza principale consiste
nel fatto che OutputStream prenderà il PerlIO * di uscita - il che
potrebbe fare differenza per un socket. Come nel nostro esempio a
seguire, del resto...
Per stream restituiti a perl viene creato un nuovo handle di
file (ossia un riferimento ad un nuovo glob) ed associato con il
PerlIO * fornito. Se lo stato di lettura/scrittura del PerlIO *
non è corretto, potreste avere errori o avvertimenti nel momento in
cui utilizzate l'handle. Per questo motivo, se avete aperto il
PerlIO * come ``w'', dovrebbe in realtà essere un OutputStream ,
laddove dovrebbe essere un InputStream se aperto come ``r''.
Ora, supponiamo che vogliate utilizzare i layer perlio nella vostra
XS. Utilizzeremo la funzione perlio PerlIO_puts() come esempio.
Nella parte C del file XS (ossia, quella al di sopra della riga
MODULE ) troviamo
#define OutputStream PerlIO *
oppure
typedef PerlIO * OutputStream;
Questo è invece il codice XS:
int
perlioputs(s, stream)
char * s
OutputStream stream
CODE:
RETVAL = PerlIO_puts(stream, s);
OUTPUT:
RETVAL
Abbiamo utilizzato una sezione CODE perché PerlIO_puts() ha
gli argomenti al contrario rispetto a fputs() , e vogliamo che gli
argomenti siano gli stessi.
Volendo esplorare in dettaglio, vogliamo utilizzare la funzione
stdio fputs() su un PerlIO * . Ciò significa che dobbiamo
chiedere un FILE * di stdio al sistema perlio:
int
perliofputs(s, stream)
char * s
OutputStream stream
PREINIT:
FILE *fp = PerlIO_findFILE(stream);
CODE:
if (fp != (FILE*) 0) {
RETVAL = fputs(s, fp);
} else {
RETVAL = -1;
}
OUTPUT:
RETVAL
Nota: PerlIO_findFILE() cercherà nei layer un layer stdio. Se
non riesce a trovarlo, chiamerà PerlIO_exportFILE() per generare
un nuovo FILE stdio. Chiamate PerlIO_exportFILE() se volete
un nuovo FILE . Ne genererà uno per ciascuna chiamata ed
inserirà un nuovo layer stdio. Per questo motivo, non chiamatelo
ripetutamente sullo stesso file. PerlIO_findFILE() richiamerà
il layer stdio una volta che questo sia stato generato da
PerlIO_exportFILE() .
Quanto detto si applica solamente al sistema perlio. Per le versioni
precedenti la 5.7, PerlIO_exportFILE() è equivalente a
PerlIO_findFILE() .
Come già detto all'inizio di questo documento, se avete problemi con
le estensioni di esempio, potete vedere se quanto segue può esservi
d'aiuto.
Per maggiori informazioni, consultate perlguts, perlapi,
perlxs, perlmod e perlpod.
Jeff Okamoto <okamoto@corp.hp.com>
Revisionato ed aiutato da De Dean Roehrich, Ilya Zakharevich,
Andreas Koenig e Tim Bunce.
Il materiale su PerlIO è un contributo di Lupe Christoph, con
alcuni chiarimenti da Nick Ing-Simmons.
2002/05/08
La versione su cui si basa questa traduzione è ottenibile con:
perl -MPOD2::IT -e print_pod perlxstut
Per maggiori informazioni sul progetto di traduzione in italiano si veda
http://pod2it.sourceforge.net/ .
Traduzione a cura di Flavio Poletti.
Revisione a cura di dree.
Mon Jun 11 22:02:20 2012
|