Introducción a XS

El sistema/lenguaje XS es un entorno de programación para la descripción de extensión de Perl usando código C. A partir de la descripción - escrita en una suerte de extensión de C que se denomina XS - es traducida a código C puro y este código es empaquetado en una librería - habitualmente dinámica - cuyas funciones pueden ser llamadas desde el código Perl.

Creación de un Módulo con Código XS con h2xs

Para crear el esqueleto de un módulo que incluye código C usando XS utilizaremos h2xs con las opción -A.

  1	lhp@nereida:~/Lperl/src/XSUB$ h2xs -A -n Example
  2	Defaulting to backwards compatibility with perl 5.8.8
  3	If you intend this module to be compatible with earlier perl versions, please
  4	specify a minimum perl version with the -b option.
  5	
! 6	Writing Example/ppport.h
  7	Writing Example/lib/Example.pm
! 8	Writing Example/Example.xs
  9	Writing Example/Makefile.PL
 10	Writing Example/README
 11	Writing Example/t/Example.t
 12	Writing Example/Changes
 13	Writing Example/MANIFEST
 14	lhp@nereida:~/Lperl/src/XSUB$
La opción -A indica que no se requiere carga automática de constantes. En esta ocasión omitimos la habitual opción -X para indicar que se trata de una extensión XS.

Un cambio que es apreciable de la salida es la creación del fichero Example/Example.xs.

El Módulo .pm

El módulo generado Example.pm también es un poco diferente del que se obtiene cuando se usa -X. Veamos un extracto del fichero Example.pm:

 lhp@nereida:~/Lperl/src/XSUB$ cd Example/lib/
 lhp@nereida:~/Lperl/src/XSUB/Example/lib$ cat -n Example.pm
  1  package Example;
  2
  3  use 5.008008;
  4  use strict;
  5  use warnings;
  6
  7  require Exporter;
  8
  9  our @ISA = qw(Exporter);
  .  .................
!30  require XSLoader;
!31  XSLoader::load('Example', $VERSION);
 32
 33  # Preloaded methods go here.
 34
 35  1;
 36  __END__
 .. ........ 
 lhp@nereida:~/Lperl/src/XSUB/Example/lib$
Hay varios cambios. Primero, el módulo usa XSLoader . El módulo XSLoader proporciona el código necesario para cargar librerías compartidas en Perl.

Las librerías compartidas serán creadas desde el código XS en aquellos sistemas operativos que permiten el uso de librerías compartidas. El segundo cambio está en la línea 31: La función XSLoader::load se encarga de cargar la librería dinámica con nombre Example y comprueba que su versión es $VERSION.

El Módulo DynaLoader

Una alternativa es usar el módulo DynaLoader. El módulo DynaLoader proporciona un mecanismo mas potente pero también una interfaz mas compleja. El aspecto típico de un módulo XS construido con DynaLoader es similar:

package YourPackage;
require DynaLoader;

our @ISA = qw( OnePackage OtherPackage DynaLoader );
our $VERSION = '0.01';
bootstrap YourPackage $VERSION;

El fichero .xs

El fichero Example.xs debe contener los ficheros de cabecera que permiten el acceso a las funciones internas de Perl (líneas 1-5).

lhp@nereida:~/Lperl/src/XSUB/Example$ cat -n Example.xs
 1  #include "EXTERN.h"
 2  #include "perl.h"
 3  #include "XSUB.h"
 4
 5  #include "ppport.h"
 6
 7
 8  MODULE = Example                PACKAGE = Example
 9 /* Aqui comienza la sección XS */

Portabilidad entre Versiones ppport.h

Para que un módulo XS sea portable en múltiples plataformas es necesario tener en cuenta varios factores. Uno de ellos es que la API de Perl cambia con el tiempo. Se añaden nuevas funciones y se eliminan otras. El módulo Devel::PPPort tiene por objetivo proveer compatibilidad a través de esos cambios de manera que un programador XS no tenga que preocuparse del problema de las versiones de Perl. Devel::PPPort genera un fichero C de cabecera ppport.h que es al mismo tiempo un programa Perl que nos da información sobre la compatibilidad del código XS:

lhp@nereida:~/Lperl/src/XSUB/Example$ perl ppport.h
Scanning ./Example.xs ...
=== Analyzing ./Example.xs ===
No need to include 'ppport.h'
Suggested changes:
--- ./Example.xs
+++ ./Example.xs.patched
@@ -2,7 +2,6 @@
 #include "perl.h"
 #include "XSUB.h"

-#include "ppport.h"


 MODULE = Example               PACKAGE = Example
Para generar ppport.h sin la ayuda de h2xs podemos hacer.
perl -MDevel::PPPort -eDevel::PPPort::WriteFile

La inclusión de ppport.h nos da acceso a una parte de la API de Perl que no estaba disponible en versiones anteriores. Ejecute perl ppport.h --list-provided para obtener la lista de elementos proveídos por ppport.h.

Además se puede usar para obtener información sobre la portabilidad de las diferentes funciones, por ejemplo:

lhp@nereida:~/Lperl/src/XSUB/Example$ perl ppport.h --api-info=sv_magicext

=== sv_magicext ===

Supported at least starting from perl-5.7.3.

Para mas detalles consulta perldoc ppport.h.

Estructura de un Fichero XS

Un fichero XS comienza con una sección en lenguaje C que termina con la primera aparición de la directiva MODULE (línea 8). A partir de ahí pueden seguir otras directivas XS o definiciones XSUB . Una definición XSUB proporciona la información necesaria para establecer el puente entre los convenios de llamada Perl y los de C para una función dada. Se proporciona una definición XSUB por cada función C que se desea hacer pública. El lenguaje utilizado en este parte del fichero se conoce como lenguaje XS. El traductor xsubpp reconoce los trozos de documentación POD en ambas secciones.

La directiva MODULE declara el espacio de nombres del módulo y define el nombre de la librería que será creada. La palabra clave PACKAGE define el espacio de nombres Perl para las subrutinas. Después de esta línea todo lo que sigue es código XS. Añadamos el siguiente código XS:

lhp@nereida:~/Lperl/src/XSUB/Example$ cat -n Example.xs
 1  #include "EXTERN.h"
 2  #include "perl.h"
 3  #include "XSUB.h"
 4
 5  #include "ppport.h"
 6
 7  double computepi(int id, int N, int np) {
 8    double sum, left;
 9
10    int i;
11    for(i=id, sum = 0; i<N; i+=np) {
12      double x = (i + 0.5)/N;
13      sum += 4/(1 + x*x);
14    }
15    sum /= N;
16    return (sum);
17  }
18
19
20  MODULE = Example                PACKAGE = Example
21
22  double
23  computepi(id, N, np)
24  int id
25  int N
26  int np

Para funciones como computepi que manejan tipos simples la declaración de XSUB se limita a proporcionar el prototipo de la función (líneas 22-26) en estilo KR. La primera línea define el tipo de retorno, luego sigue una línea con la forma de llamada a la función y después una línea por argumento, especificando su tipo C.

Construcción con make de un Módulo XS

Podemos construir el módulo siguiente el esquema clásico:

! 1 lhp@nereida:~/Lperl/src/XSUB/Example$ perl Makefile.PL
  2 Checking if your kit is complete...
  3 Looks good
  4 Writing Makefile for Example
! 5 lhp@nereida:~/Lperl/src/XSUB/Example$ make
  6 
  7 cp lib/Example.pm blib/lib/Example.pm
  8 cp useexample.pl blib/lib/useexample.pl
! 9 /usr/bin/perl /usr/share/perl/5.8/ExtUtils/xsubpp  
!                     -typemap /usr/share/perl/5.8/ExtUtils/typemap  
!                     Example.xs > Example.xsc && mv Example.xsc Example.c
!10 Please specify prototyping behavior for Example.xs (see perlxs manual)
!11 cc -c  -I. -D_REENTRANT -D_GNU_SOURCE -DTHREADS_HAVE_PIDS -DDEBIAN -fno-strict-aliasing 
!          -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -O2   
!          -DVERSION=\"0.01\" -DXS_VERSION=\"0.01\" -fPIC "-I/usr/lib/perl/5.8/CORE"   
!          Example.c
!12 Running Mkbootstrap for Example ()
!13 chmod 644 Example.bs
!14 rm -f blib/arch/auto/Example/Example.so
!15 cc  -shared -L/usr/local/lib Example.o  -o blib/arch/auto/Example/Example.so    \
 16                 \
 17 
!18 chmod 755 blib/arch/auto/Example/Example.so
!19 cp Example.bs blib/arch/auto/Example/Example.bs
!20 chmod 644 blib/arch/auto/Example/Example.bs
 21 Manifying blib/man3/Example.3pm

Expliquemos en mas detalle la secuencia anterior:

  1. Se crea el Makefile (líneas 1-4) a partir de Makefile.PL:
    lhp@nereida:~/Lperl/src/XSUB/Example$ cat -n Makefile.PL
     1  use 5.008008;
     2  use ExtUtils::MakeMaker;
     3  # See lib/ExtUtils/MakeMaker.pm for details of how to influence
     4  # the contents of the Makefile that is written.
     5  WriteMakefile(
     6      NAME              => 'Example',
     7      VERSION_FROM      => 'lib/Example.pm', # finds $VERSION
     8      PREREQ_PM         => {}, # e.g., Module::Name => 1.1
     9      ($] >= 5.005 ?     ## Add these new keywords supported since 5.005
    10        (ABSTRACT_FROM  => 'lib/Example.pm', # retrieve abstract from module
    11         AUTHOR         => 'Lenguajes y Herramientas de Programacion') : ()),
    12      LIBS              => [''], # e.g., '-lm'
    13      DEFINE            => '', # e.g., '-DHAVE_SOMETHING'
    14      INC               => '-I.', # e.g., '-I. -I/usr/include/other'
    15          # Un-comment this if you add C files to link with later:
    16      # OBJECT            => '$(O_FILES)', # link all the C files too
    17  );
    
    La ejecución de Makefile.PL detecta la presencia de ficheros .xs en el directorio y adapta el Makefile para que dichos ficheros sean procesados.
  2. Se copian los ficheros al directorio de construcción blib/lib/ (líneas 7-8). El nombre blib viene de Build Library.
  3. Se ejecuta el traductor de XS a C xsubpp (línea 9). El compilador usa unos ficheros de información denominados typemap para determinar la correspondencia entre los tipos de C y los de Perl. En vez de producir directamente un fichero con extensión .c la salida se almacena en un fichero temporal Example.xsc y luego se renombra como Example.c. Ello se hace para evitar que ficheros en proceso de formación puedan ser erróneamente tomados por código C válido.
  4. El warning de la línea 10 puede ser ignorado. En XS es posible especificar un prototipo Perl para determinar la forma en la que ocurre la llamada a la XSUB (vea un ejemplo en la sección 17.11). El mensaje de advertencia puede desactivarse usando la directiva PROTOTYPES: DISABLE en el fichero XS justo después de la declaración MODULE:
    lhp@nereida:~/Lperl/src/XSUB/Example$ cat -n Example.xs
         1  #include "EXTERN.h"
         2  #include "perl.h"
         3  #include "XSUB.h"
         4
         5  #include "ppport.h"
         6
        ..  .................................................
        20  MODULE = Example                PACKAGE = Example
        21  PROTOTYPES: DISABLE
        22
        23  double
        24  computepi(id, N, np)
        25  int id
        26  int N
        27  int np
    
  5. Se compila el fichero Example.c generado por xsubpp (línea 11).
    cc -c  -I. -D_REENTRANT -D_GNU_SOURCE -DTHREADS_HAVE_PIDS -DDEBIAN \
      -fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE \
      -D_FILE_OFFSET_BITS=64 -O2-DVERSION=\"0.01\" -DXS_VERSION=\"0.01\" \
      -fPIC "-I/usr/lib/perl/5.8/CORE" Example.c
    
    El compilador y las opciones del compilador usadas son las mismas que se emplearon para construir la instalación actual del intérprete Perl. Los valores con los que la versión usada de Perl fue compilada pueden obtenerse por medio del módulo Config . Por ejemplo:
    lhp@nereida:~/Lperl/src/XSUB$  perl -MConfig -e 'print Config::myconfig()'
    Summary of my perl5 (revision 5 version 8 subversion 8) configuration:
      Platform:
        osname=linux, osvers=2.6.15.4, archname=i486-linux-gnu-thread-multi
        uname='linux ninsei 2.6.15.4 #1 smp preempt mon feb 20 09:48:53 pst 2006\
                                                 i686 gnulinux '
        config_args='-Dusethreads -Duselargefiles -Dccflags=-DDEBIAN
        -Dcccdlflags=-fPIC -Darchname=i486-linux-gnu
        -Dprefix=/usr -Dprivlib=/usr/share/perl/5.8
        -Darchlib=/usr/lib/perl/5.8 -Dvendorprefix=/usr
        -Dvendorlib=/usr/share/perl5 -Dvendorarch=/usr/lib/perl5
        -Dsiteprefix=/usr/local -Dsitelib=/usr/local/share/perl/5.8.8
        -Dsitearch=/usr/local/lib/perl/5.8.8 -Dman1dir=/usr/share/man/man1
        -Dman3dir=/usr/share/man/man3 -Dsiteman1dir=/usr/local/man/man1
        -Dsiteman3dir=/usr/local/man/man3 -Dman1ext=1 -Dman3ext=3perl
        -Dpager=/usr/bin/sensible-pager -Uafs -Ud_csh -Uusesfio -Uusenm
        -Duseshrplib -Dlibperl=libperl.so.5.8.8 -Dd_dosuid -des'
        hint=recommended, useposix=true, d_sigaction=define
        usethreads=define use5005threads=undef useithreads=define usemultiplicity=define
        useperlio=define d_sfio=undef uselargefiles=define usesocks=undef
        use64bitint=undef use64bitall=undef uselongdouble=undef
        usemymalloc=n, bincompat5005=undef
    ! Compiler:
    !   cc='cc', ccflags ='-D_REENTRANT -D_GNU_SOURCE -DTHREADS_HAVE_PIDS
    !   -DDEBIAN -fno-strict-aliasing -pipe -I/usr/local/include
    !   -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64',
    !   optimize='-O2',
        cppflags='-D_REENTRANT -D_GNU_SOURCE -DTHREADS_HAVE_PIDS -DDEBIAN 
                 -fno-strict-aliasing -pipe -I/usr/local/include'
        ccversion='', gccversion='4.0.3 (Debian 4.0.3-1)', gccosandvers=''
        intsize=4, longsize=4, ptrsize=4, doublesize=8, byteorder=1234
        d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=12
        ivtype='long', ivsize=4, nvtype='double', nvsize=8, Off_t='off_t', lseeksize=8
        alignbytes=4, prototype=define
      Linker and Libraries:
        ld='cc', ldflags =' -L/usr/local/lib'
        libpth=/usr/local/lib /lib /usr/lib
        libs=-lgdbm -lgdbm_compat -ldb -ldl -lm -lpthread -lc -lcrypt
        perllibs=-ldl -lm -lpthread -lc -lcrypt
        libc=/lib/libc-2.3.6.so, so=so, useshrplib=true, libperl=libperl.so.5.8.8
        gnulibc_version='2.3.6'
      Dynamic Linking:
        dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-Wl,-E'
        cccdlflags='-fPIC', lddlflags='-shared -L/usr/local/lib'
    
  6. A continuación se procede a la construcción de la librería dinámica. El proceso depende de la plataforma y la forma de hacerlo viene de nuevo dirigida por el módulo Config. Observe que la librería se deja en el directorio blib/arch/auto/Example/ (línea 18).
    !13 chmod 644 Example.bs
    !14 rm -f blib/arch/auto/Example/Example.so
    !15 cc  -shared -L/usr/local/lib Example.o  -o blib/arch/auto/Example/Example.so    \
     16                 \
     17 
    !18 chmod 755 blib/arch/auto/Example/Example.so
    !19 cp Example.bs blib/arch/auto/Example/Example.bs
    !20 chmod 644 blib/arch/auto/Example/Example.bs
     21 Manifying blib/man3/Example.3pm
    

Para probar el módulo escribimos un programa de prueba:

lhp@nereida:~/Lperl/src/XSUB/Example$ cat -n useexample.pl
 1  #!/usr/bin/perl -w
 2  use blib; # Para que encuentre el módulo
 3  use strict;
 4  use Example;
 5
 6  my $n = shift || 1000;
 7  my $x = 0;
 8
 9  $x += Example::computepi(0,$n,4) for 0..3;
10  print "$x\n";
lhp@nereida:~/Lperl/src/XSUB/Example$ time ./useexample.pl 1000
3.14459174129814

real    0m0.032s
user    0m0.020s
sys     0m0.010s
lhp@nereida:~/Lperl/src/XSUB/Example$ time ./useexample.pl 10000000
3.14159295358978

real    0m0.359s
user    0m0.360s
sys     0m0.000s



Subsecciones
Casiano Rodríguez León
2012-02-29