index | project | pods | responsabili

NOME

perlXStut - Tutorial per scrivere estensioni XSUB


DESCRIZIONE

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.


NOTE SPECIALI

make

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.

attenzione alle versioni

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

Caricamento Dinamico e Caricamento Statico

È 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.


TUTORIAL

Che si dia inizio allo spettacolo!

ESEMPIO 1

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!
        %

ESEMPIO 2

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
        %

Che è successo?

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.

Scrivere buoni script di test

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.].

ESEMPIO 3

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!

Che c'è di nuovo?

  • 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.

Parametri di Ingresso e di Uscita

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

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 file TYPEMAP

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).

Attenzione agli Argomenti di Uscita

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.

ESEMPIO 4

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.

Che è successo?

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.

Anatomia del file .xs

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.

Tirare fuori la ``ciccia'' dalle XSUB

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).

Ancora sugli argomenti XSUB

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.

Lo Stack degli Argomenti

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.

Estendere la vostra Estensione

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.

Documentare la vostra Estensione

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.

Installare la vostra Estensione

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.

ESEMPIO 5

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";

Cose Nuove in questo Esempio

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.

ESEMPIO 6

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");

Cose Nuove in questo Esempio

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.

ESEMPIO 7 (Arriverà presto...)

XPUSH degli argomenti E impostazione di RETVAL E assegnazione del valore di ritorno ad un array.

ESEMPIO 8 (Arriverà presto...)

Impostare $!

ESEMPIO 9 Passare file aperti alle XS

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().

Se gli Esempi danno errore

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.

  • Nella versione 5.002, prima della revisione gamma, gli script di test dell'ESEMPIO 1 non funzioneranno a dovere. Avete bisogno di cambiare la riga use lib come segue:
            use lib './blib';

  • In versioni di 5.002 prima della 5.002b1h, il file test.pl non veniva generato automaticamente da h2xs. Questo significa che non potete lanciare make test per far partire lo script dei test. Avrete bisogno di aggiungere le seguenti righe prima dell'istruzione use extension:
            use lib './blib';

  • Nelle versioni 5.000 e 5.001, invece di utilizzre la riga di cui sopra, avrete bisogno di utilizzare quanto segue:
            BEGIN { unshift(@INC, "./blib") }

  • Questo documento presume che l'eseguibile chiamato perl sia relativo alla versione 5 di Perl. Alcuni sistemi potrebbero avere questa versione installata come perl5.


Consultate anche

Per maggiori informazioni, consultate perlguts, perlapi, perlxs, perlmod e perlpod.


Autore

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.

Ultima Modifica

2002/05/08


TRADUZIONE

Versione

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/ .

Traduttore

Traduzione a cura di Flavio Poletti.

Revisore

Revisione a cura di dree.


Mon Jun 11 22:02:20 2012