typemap
facilita
el proceso de conversión entre arrays C y listas Perl. Este servicio
es útil si tenemos funciones de una librería que espera recibir o devolver
arrays. Si lo que se quiere
es simplemente manipular la lista Perl en C es mejor usar la
técnica introducida en las secciones
17.13
y
17.14.
Para ilustrar su uso estudiaremos el código de una subrutina que
recibe y devuelve usando T_ARRAY
un vector de dobles.
La subrutina realiza la suma de prefijos y su funcionamiento
queda ilustrado en la ejecución del siguiente programa de ejemplo:
lhp@nereida:~/Lperl/src/XSUB/Arrays$ cat -n usearrays.pl 1 #!/usr/local/bin/perl -w 2 use strict; 3 use blib; 4 use List::MoreUtils qw(all); 5 use Regexp::Common; 6 use Arrays; 7 my @a = (@ARGV and all { /^$RE{num}{real}$/ } @ARGV)? @ARGV : 1..5; 8 my @x = prefixsum(@a); 9 print "Suma de prefijos de @a:\n @x\n"; lhp@nereida:~/Lperl/src/XSUB/Arrays$ usearrays.pl Suma de prefijos de 1 2 3 4 5: 1 3 6 10 15 lhp@nereida:~/Lperl/src/XSUB/Arrays$ usearrays.pl 1 a b 3 Suma de prefijos de 1 2 3 4 5: 1 3 6 10 15 lhp@nereida:~/Lperl/src/XSUB/Arrays$ usearrays.pl 5.2 3.1 -1 -2e1 Suma de prefijos de 5.2 3.1 -1 -2e1: 5.2 8.3 7.3 -12.7 lhp@nereida:~/Lperl/src/XSUB/Arrays$ usearrays.pl 0 Suma de prefijos de 0: 0
Para su funcionamiento, el código asociado con
el typemap T_ARRAY
requiere la colaboración
del programador en varios puntos.
El primero es que el tipo de entrada y/o salida de la XSUB
debe tener
un nombre que sea de la forma $subtipo."Array"
. Así deberá llamarse
doubleArray
si
es un array de dobles, intArray
si lo es de enteros, etc.
Por tanto, en el ejemplo que nos ocupa, el tipo se llamará
doubleArray
y le asignamos la entrada T_ARRAY
usando un
fichero de typemap
local al directorio de trabajo:
lhp@nereida:~/Lperl/src/XSUB/Arrays$ cat -nT typemap # La opción -T muestra tabs 1 doubleArray *^IT_ARRAY lhp@nereida:~/Lperl/src/XSUB/Arrays$ cat -n typemap 1 doubleArray * T_ARRAY
El código de la XSUB prefixsum
(líneas 30-41 del
listado que sigue) comienza indicando mediante la elípsis
que la función recibirá un número variable de argumentos.
El primero argumento
array
no se corresponde con un argumento real
de la función y se usará para indicar el nombre del array C
que almacenará el vector C de números extraido de la lista
Perl de escalares (observe la llamada en la línea 8
del código de usearrays.pl
encima: no existe
un primer argumento especial). De hecho, es este identificador
array
el que se corresponde con la variable $var
del
código del typemap
.
lhp@nereida:~/Lperl/src/XSUB/Arrays$ cat -n Arrays.xs 1 #include "EXTERN.h" 2 #include "perl.h" 3 #include "XSUB.h" 4 #include "ppport.h" 5 6 typedef double doubleArray; 7 8 doubleArray * doubleArrayPtr ( int num ) { .. ... # asigna memoria para "num" elementos de tipo double 14 } 15 16 doubleArray * 17 prefixSum ( double num, doubleArray * array ) { .. ... # retorna la suma de prefijos de array 26 } 27 28 MODULE = Arrays PACKAGE = Arrays 29 30 doubleArray * 31 prefixsum( array, ...) 32 doubleArray * array 33 PREINIT: 34 int size_RETVAL; 35 CODE: 36 size_RETVAL = ix_array; 37 RETVAL = prefixSum( ix_array, array ); 38 OUTPUT: 39 RETVAL 40 CLEANUP: 41 XSRETURN(size_RETVAL);
El código de entrada asociado con T_ARRAY
asigna memoria para el nuevo array y después procede
a copiar cada uno de los elementos en la lista de escalares
Perl en el array recién creado. Para poder llevar a
cabo dicha asignación de memoria el programador deberá
proveer una función de asignación cuyo nombre debe ser igual
a la meta-variable $ntype
(el nombre del tipo con los astericos
sustituidos por Ptr
). Esa es la misión de
la función doubleArrayPtr
que aparece en la línea 8
del código XS. Además el código de entrada asociado con T_ARRAY
declara la variable con nombre ix_$var
(ix_array
en el ejemplo) la cual
contendrá el número de elementos en el array.
lhp@nereida:~/Lperl/src/XSUB/Arrays$ cat -n /usr/share/perl/5.8/ExtUtils/typemap 1 # basic C types 2 int T_IV .. ............................................. 45 double T_DOUBLE .. .............................................$ 55 56 ############################################################################# 57 INPUT ... ............................................. 110 T_DOUBLE 111 $var = (double)SvNV($arg) ... ............................................. 176 T_ARRAY 177 U32 ix_$var = $argoff; 178 $var = $ntype(items -= $argoff); 179 while (items--) { 180 DO_ARRAY_ELEM; 181 ix_$var++; 182 } 183 /* this is the number of elements in the array */ 184 ix_$var -= $argoff
La entrada INPUT
de T_ARRAY
(líneas 176-184) del código
muestra en mas detalle las acciones que tienen lugar:
ix_$var
(ix_array
en el ejemplo) y se inicializa al desplaamiento
(offset) del argumento actual.
items - $argoff
elementos del tipo del array
confiando en que el programador ha proveído una función de asignación con
nombre $ntype
. Observe que el uso de items
implica que, para
que este código funcione, el array debe ser el último argumento de la lista.
De paso se cambia el valor de items
que pasa a tener el número de
elementos en el array.
Después se entra en un bucle en el que el campo numérico de
cada elemento de la lista Perl es copiado
en el array C. El traductor xsubpp
sustituye la cadena
DO_ARRAY_ELEM
por:
$var[ix_$var - $argoff] = ($subtype)SvNV(ST(ix_$var));que en el ejemplo acaba convertido en:
array[ix_array - 0] = (double)SvNV(ST(ix_array));
ix_$var
es actualizada para que guarde el tamaño del
array.
El código de salida del typemap
realiza el proceso
inverso:
194 OUTPUT ... ............................................. 240 T_DOUBLE 241 sv_setnv($arg, (double)$var); ... ............................................. 273 T_ARRAY 274 { 275 U32 ix_$var; 276 EXTEND(SP,size_$var); 277 for (ix_$var = 0; ix_$var < size_$var; ix_$var++) { 278 ST(ix_$var) = sv_newmortal(); 279 DO_ARRAY_ELEM 280 } 281 } ... .............................................
ix_$var
. Puesto que $var
es
el nombre de la variable siendo retornada, en la sección OUTPUT:
,
la cadena ix_$var
se convierte en ix_RETVAL
.
size_$var
escalares. Por tanto el programador deberá proveer
una variable con nombre size_$var
que determine el
tamaño de la lista que se devuelve. En el ejemplo size_RETVAL
.
DO_ARRAY_ELEM
es expandida por xsubpp
a:
sv_setnv(ST(ix_$var), ($subtype)$var[ix_$var])
La función sv_setnv copia el valor numérico (segundo argumento) en el escalar. El texto anterior se transforma en el ejemplo en:
sv_setnv(ST(ix_RETVAL), (double)RETVAL[ix_RETVAL])
Finalmente el código generado por xsubpp
para la XSUB prefixsum
queda:
lhp@nereida:~/Lperl/src/XSUB/Arrays$ cat -n Arrays.c . ................... 39 XS(XS_Arrays_prefixsum); /* prototype to pass -Wmissing-prototypes */ 40 XS(XS_Arrays_prefixsum) 41 { 42 dXSARGS; 43 if (items < 1) 44 Perl_croak(aTHX_ "Usage: Arrays::prefixsum(array, ...)"); 45 { 46 doubleArray * array; 47 #line 34 "Arrays.xs" 48 int size_RETVAL; 49 #line 50 "Arrays.c" 50 doubleArray * RETVAL; 51 52 U32 ix_array = 0; 53 array = doubleArrayPtr(items -= 0); 54 while (items--) { 55 array[ix_array - 0] = (double)SvNV(ST(ix_array)); 56 ix_array++; 57 } 58 /* this is the number of elements in the array */ 59 ix_array -= 0; 60 #line 36 "Arrays.xs" 61 size_RETVAL = ix_array; 62 RETVAL = prefixSum( ix_array, array ); 63 #line 64 "Arrays.c" 64 { 65 U32 ix_RETVAL; 66 EXTEND(SP,size_RETVAL); 67 for (ix_RETVAL = 0; ix_RETVAL < size_RETVAL; ix_RETVAL++) { 68 ST(ix_RETVAL) = sv_newmortal(); 69 sv_setnv(ST(ix_RETVAL), (double)RETVAL[ix_RETVAL]); 70 } 71 } 72 #line 41 "Arrays.xs" 73 XSRETURN(size_RETVAL); 74 #line 75 "Arrays.c" 75 } 76 XSRETURN(1); 77 } 78 .. ...................Para completar, veamos el código de la subrutina de asignación de memoria (fichero Arrays.xs):
8 doubleArray * doubleArrayPtr ( int num ) { 9 doubleArray * array; 10 SV * mortal; 11 mortal = sv_2mortal( NEWSV(1, num * sizeof(doubleArray) ) ); 12 array = (doubleArray *) SvPVX( mortal ); 13 return array; 14 }
Casiano Rodríguez León