我正在为GNU科学库中的某些函数开发XS包装模块。我没有直接在这里使用库,而是通过创建自己的库来简化问题:
mylib/mylib.h:
typedef struct {
int foo;
double bar;
} my_struct_type;
extern my_struct_type *my_symbol1;
extern my_struct_type *my_symbol2;
void use_struct( my_struct_type *s );
mylib/mylib.c:
#include "mylib.h"
#include <stdio.h>
static my_struct_type my_struct1 = { 3, 3.14 };
static my_struct_type my_struct2 = { 2, 1.06 };
my_struct_type *my_symbol1 = &my_struct1;
my_struct_type *my_symbol2 = &my_struct2;
void use_struct( my_struct_type *s ) {
printf( "use_struct: foo = %dn", s->foo);
printf( "use_struct: bar = %gn", s->bar);
}
这是使用以下命令编译到共享库中的:
$ gcc -c -o mylib.o mylib.c
$ gcc -shared -o libmylib.so mylib.o
所以我将以mylib.so
为例,而不是libgsl.so
.现在我想引用 Perl 脚本中my_symbol1
和my_symbol2
的 C 符号。首先,我创建了一个XS文件:
XsTest.xs:
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "mylib.h"
/* These definition are created ad hoc to provide an interface to the perl module */
#define STRUCT_TYPE1 1
#define STRUCT_TYPE2 2
MODULE = My::XsTest PACKAGE = My::XsTest
PROTOTYPES: DISABLE
# export STRUCT_TYPE1, STRUCT_TYPE2, ... to My::XsTest
# NOTE: I would like to avoid having to repeat the string, e.g. "STRUCT_TYPE1"
# in the lines below (if possible?)
BOOT:
{
SV* const_sv = get_sv( "My::XsTest::STRUCT_TYPE1", GV_ADD );
sv_setiv( const_sv, STRUCT_TYPE1 );
SvREADONLY_on( const_sv );
SV* const_sv2 = get_sv( "My::XsTest::STRUCT_TYPE2", GV_ADD );
sv_setiv( const_sv2, STRUCT_TYPE2 );
SvREADONLY_on( const_sv2 );
}
void
use_struct(type)
int type
CODE:
if (type == STRUCT_TYPE1 ) {
use_struct(my_symbol1);
}
else if (type == STRUCT_TYPE2) {
use_struct(my_symbol2);
}
else {
croak("Unknown struct type");
}
lib/My/XsTest.pm:
package My::XsTest;
our $VERSION = '0.01';
use strict;
use warnings;
use Exporter qw(import);
# NOTE: I would like to avoid having to define the line below here,
# it would be better if it was enough to define them in XsTest.xs
our %EXPORT_TAGS = ( 'symbols' => [ qw( STRUCT_TYPE1 STRUCT_TYPE2 ) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{symbols} } );
our @EXPORT = qw(use_struct);
# NOTE: I would like to avoid having to declare here the two line below.
# this should be done automatically from the .xs file
our $STRUCT_TYPE1;
our $STRUCT_TYPE2;
require XSLoader;
XSLoader::load();
# NOTE: I would like to avoid having to define the subs below.
# This should be done automatically from the .xs file
sub STRUCT_TYPE1 {
return $STRUCT_TYPE1;
}
sub STRUCT_TYPE2 {
return $STRUCT_TYPE2;
}
1;
然后为了编译扩展,我使用了一个ExtUtils::MakeMaker
:
Makefile.PL:
use strict;
use warnings;
use utf8;
use ExtUtils::MakeMaker;
my $lib_dir = 'mylib';
WriteMakefile(
NAME => 'My::XsTest',
VERSION_FROM => 'lib/My/XsTest.pm',
PREREQ_PM => { 'ExtUtils::MakeMaker' => 0 },
ABSTRACT_FROM => 'lib/My/XsTest.pm',
AUTHOR => 'Håkon Hægland <hakon.hagland@gmail.com>',
OPTIMIZE => '-g3 -O0',
LICENSE => 'perl',
LIBS => ["-L$lib_dir -lmylib"],
INC => "-I$lib_dir",
);
然后编译:
$ perl Makefile.PL
$ make
最后,我用一个Perl脚本测试了这个模块:
p.pl:
#! /usr/bin/env perl
use feature qw(say);
use strict;
use warnings;
use ExtUtils::testlib;
use My::XsTest qw(use_struct :symbols);
use_struct(STRUCT_TYPE1);
use_struct(STRUCT_TYPE2);
输出:
use_struct: foo = 3
use_struct: bar = 3.14
use_struct: foo = 2
use_struct: bar = 1.06
所以这有效,但它并不漂亮。如何改进此代码并避免符号名称的所有重复,尤其是在文件lib/My/XsTest.pm
中?
您可以使用newCONSTSUB
在 XSBOOT
部分中注册常量(实际上是子例程(。.pm
文件中不需要子定义或our
变量:
BOOT:
{
HV *stash = gv_stashpv("My::XsTest", 0);
newCONSTSUB(stash, "STRUCT_TYPE1", newSViv(STRUCT_TYPE1));
newCONSTSUB(stash, "STRUCT_TYPE2", newSViv(STRUCT_TYPE2));
}
你可以改变
sub STRUCT_TYPE1 {
return $STRUCT_TYPE1;
}
...
对此。
for my $id ( 1 .. $MAX_SUB ) { # Max sub is the number of exported symbles
no strict 'refs';
my $struct = 'STRUCT_TYPE' . $id;
*{ $struct } = sub { $$struct };
}
这是避免不同文件中符号名称重复的方法,并希望使事情更容易维护。首先,我生成了一个 JSON 文件:
symbols.json:
{
"symbols" : ["my_symbol1", "my_symbol2"],
"perl_names" : ["STRUCT_TYPE1", "STRUCT_TYPE2"]
}
然后我创建了一个 perl 脚本gensymbols.pl
,它基于之前的 JSON 文件生成了三个文件:
mysymbols.h(生成(:
#include "mylib.h"
#define MY_SYMBOLS_MIN 0
#define MY_SYMBOLS_MAX 1
static my_struct_type * my_symbols[2];
my_setup_array.h(生成(:
my_symbols[0] = my_symbol1;
my_symbols[1] = my_symbol2;
lib/My/Symbols.pm(生成(:
package My::Symbols;
use strict;
use warnings;
use Exporter qw(import);
our $symbols = [
"STRUCT_TYPE1",
"STRUCT_TYPE2"
];
our @EXPORT = @$symbols;
sub STRUCT_TYPE1 { 0 }
sub STRUCT_TYPE2 { 1 }
然后我将XS文件更改为:
XsTest.xs:
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "mylib.h"
#include "mysymbols.h"
MODULE = My::XsTest PACKAGE = My::XsTest
PROTOTYPES: DISABLE
BOOT:
#include "my_setup_array.h"
void
use_struct(type)
int type
CODE:
if ( (type < MY_SYMBOLS_MIN) || (type >MY_SYMBOLS_MAX) ) {
croak("Unknown symbol type");
}
else {
use_struct(my_symbols[type]);
}
和 Perl 模块:
lib/My/XsTest.pm:
package My::XsTest;
our $VERSION = '0.01';
use strict;
use warnings;
use Exporter qw(import);
use My::Symbols;
our %EXPORT_TAGS = ( 'symbols' => $My::Symbols::symbols );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{symbols} } );
our @EXPORT = qw(use_struct);
require XSLoader;
XSLoader::load();
1;
通过这种方式,我可以将有关符号的所有信息移动到 JSON 文件中,维护者只需要关心这个文件。如果他更改了文件,他必须记住运行gensymbols.pl
Perl脚本来重新生成这三个文件。以下是完整性的脚本:
gensymbols.pl
#! /usr/bin/env perl
{
GenSymbols->new(
c_symbol_array_name => 'my_symbols',
perl_symbol_module_fn => 'lib/My/Symbols.pm',
symbols_fn => 'symbols.json',
xs_include => {
my_symbols_fn => 'mysymbols.h',
my_setup_array_fn => 'my_setup_array.h'
},
);
}
package GenSymbols;
use feature qw(say);
use strict;
use warnings;
use Data::Printer;
use JSON::XS;
use Clone qw(clone);
sub new {
my ( $class, %temp ) = @_;
my $args = clone %temp;
my $self = bless $args, $class;
$self->read_json();
$self->write_xs_include_mysymbols();
$self->write_xs_include_my_setup_array();
$self->write_perl_symbol_module();
}
sub write_perl_symbol_module {
my ( $self ) = @_;
my $fn = $self->{perl_symbol_module_fn};
open ( my $fh, '>', $fn ) or die "Could not open file '$fn': $!";
say $fh 'package My::Symbols;';
say $fh 'use strict;';
say $fh 'use warnings;';
say $fh 'use Exporter qw(import);';
print $fh "n";
my $names = $self->{perl_names_array};
say $fh 'our $symbols = [';
for my $i ( 0..$#$names ) {
my $name = $names->[$i];
$name = ' "' . $name . '"';
$name .= "," if $i < $#$names;
say $fh $name;
}
say $fh '];';
say $fh 'our @EXPORT = @$symbols;';
print $fh "n";
for my $i ( 0..$#$names ) {
printf $fh ('sub %s { %d }' . "n"), $names->[$i], $i;
}
say $fh '1;';
close $fh;
}
sub write_xs_include_my_setup_array {
my ( $self ) = @_;
my $fn = $self->{xs_include}{my_setup_array_fn};
my $syms = $self->{sym_array};
open ( my $fh, '>', $fn ) or die "Could not open file '$fn': $!";
my $sym_arr_name = $self->{c_symbol_array_name};
for my $i (0..$#$syms) {
my $sym = $syms->[$i];
printf $fh "%s[%d] = %s;n", $sym_arr_name, $i, $sym;
}
close $fh;
}
sub write_xs_include_mysymbols {
my ( $self ) = @_;
my $fn = $self->{xs_include}{my_symbols_fn};
my $syms = $self->{sym_array};
open ( my $fh, '>', $fn ) or die "Could not open file '$fn': $!";
my $min_index = 0;
my $max_index = $#$syms;
my $sym_arr_name = $self->{c_symbol_array_name};
say $fh '#include "mylib.h"';
print $fh "n";
printf $fh "#define MY_SYMBOLS_MIN %dn", $min_index;
printf $fh "#define MY_SYMBOLS_MAX %dn", $max_index;
print $fh "n";
printf $fh "static my_struct_type * %s[%d];n", $sym_arr_name, $max_index + 1;
close $fh;
}
sub read_json {
my ( $self ) = @_;
my $fn = $self->{symbols_fn};
open ( my $fh, '<', $fn ) or die "Could not open file '$fn': $!";
my $str = do { local $/; <$fh> };
close $fh;
my $hash = JSON::XS->new->decode( $str );
$self->{sym_array} = $hash->{symbols};
$self->{perl_names_array} = $hash->{perl_names};
}