#perl #swig #xs
#perl #swig #xs
Вопрос:
У меня есть модуль, который будет нацелен на несколько разных операционных систем и конфигураций. Иногда некоторый код на C может немного облегчить задачу этого модуля, поэтому у меня есть некоторые функции C, к которым я хотел бы привязать код. Мне не нужно привязывать функции C — я не могу гарантировать, что у конечного пользователя даже есть компилятор C, например, и, как правило, не проблема корректно перейти на чистый Perl-способ выполнения того же самого — но было бы неплохо, если бы я мог вызывать функции C из Perl-скрипта.
Все еще со мной? Вот еще одна сложная часть. Почти весь код C зависит от системы — функция, написанная для Windows, не будет компилироваться в Linux и наоборот, а функция, которая выполняет аналогичную функцию в Solaris, будет выглядеть совершенно иначе.
#include <some/Windows/headerfile.h>
int foo_for_Windows_c(int a,double b)
{
do_windows_stuff();
return 42;
}
#include <path/to/linux/headerfile.h>
int foo_for_linux_c(int a,double b)
{
do_linux_stuff(7);
return 42;
}
Более того, даже для машинного кода, предназначенного для той же системы, это
возможно, что только некоторые из них могут быть скомпилированы на каком-либо конкретном
настройка.
#include <some/headerfile/that/might/not/even/exist.h>
int bar_for_solaris_c(int a,double b)
{
call_solaris_library_that_might_be_installed_here(11);
return 19;
}
Но в идеале мы все равно могли бы использовать функции C, которые компилировали бы
с такой конфигурацией. Итак, мои вопросы:
-
как я могу скомпилировать функции C условно (скомпилировать только тот код, который подходит для текущего значения
$^O
)? -
как я могу скомпилировать функции C по отдельности (некоторые функции могут не скомпилироваться, но мы все равно хотим использовать те, которые могут)?
-
могу ли я сделать это во время сборки (пока конечный пользователь устанавливает модуль) или во время выполнения (с
Inline::C
, например)? Какой способ лучше? -
как мне определить, какие функции были успешно скомпилированы и доступны для использования из Perl?
Все мысли приветствуются!
Обновление: Спасибо всем, кто откликнулся. Итак, вот что я сделал:
Я рассмотрел схему привязки во время выполнения с помощью инструкций Inline::C
inside of eval
, но в конечном итоге остановился на подклассах Module::Build
и настройке ACTION_build
метода:
my $builderclass = Module::Build->subclass(
class => 'My::Custom::Builder',
code => <<'__CUSTOM_BUILD_CODE__,',
sub ACTION_build {
use File::Copy;
my $self = shift;
### STEP 1: Compile all .xs files, remove the ones that fail ###
if (! -f "./lib/xs/step1") {
unlink <lib/xs/*>;
foreach my $contrib_file (glob("contrib/*.xs")) {
File::Copy::copy($contrib_file, "lib/xs/");
}
open my $failed_units_fh, '>', 'lib/xs/step1';
local $@ = undef;
do {
my $r = eval { $self->ACTION_code() };
if ($@ =~ /error building (S .o) from/i
|| $@ =~ /error building dll file from '(S .c)'/i) {
my $bad_file = $1;
$bad_file =~ s!\!/!g;
my $bad_xs = $bad_file;
$bad_xs =~ s/.[oc]$/.xs/;
print STDERR "ERROR COMPILING UNIT $bad_xs ... removingnn";
unlink $bad_xs;
print $failed_units_fh "$bad_xsn";
} elsif ($@) {
print STDERR "Compile error not handled in $^O: $@n";
}
} while $@;
print "Removed all uncompilable units from lib/xs/n";
close $failed_units_fh;
}
### STEP 2: Combine valid .xs files into a single .xs file ###
if (! -f "./lib/xs/step2") {
open my $valid_units_fh, '>', "lib/xs/step2";
my (@INCLUDE,%INCLUDE,$MODULE,@PREMOD,@POSTMOD);
foreach my $xs (glob("lib/xs/*.xs")) {
open my $xs_fh, '<', $xs;
while (<$xs_fh>) {
if (m/#include/) {
next if $INCLUDE{$_} ;
push @INCLUDE, $_;
} elsif (/^MODULE/) {
$MODULE = $_;
push @POSTMOD, <$xs_fh>;
} else {
push @PREMOD, $_;
}
}
close $xs_fh;
print $valid_units_fh "$xsn";
}
close $valid_units_fh;
unlink <lib/xs/*>, <blib/arch/auto/xs/*/*>;
unlink 'lib/My/Module.xs';
open my $xs_fh, '>', 'lib/My/Module.xs' or croak $!;
print $xs_fh @INCLUDE, @PREMOD, $MODULE, @POSTMOD;
close $xs_fh;
print "Assembled remaining XS files into lib/My/Module.xsn";
}
### STEP 3: Clean all .xs stuff and compile My/Module.xs ###
unlink <lib/xs/*>;
$self->ACTION_code();
return $self->SUPER::ACTION_build(@_);
}
}
Проверка на $@
, вероятно, довольно хрупкая. Это работает в системах
Я пробовал (все с использованием gcc), но, вероятно, это не сработает так, как написано
везде.
Ответ №1:
В идеале используйте Module::Build
. Во время настройки ( perl Build.PL
) определите платформу и расположение заголовка (но также позвольте пользователю указать параметры командной строки для переопределения обнаружения), установите соответствующие extra_compiler_flags
и extra_linker_flags
в конструкторе, а затем скопируйте соответствующие файлы, например, contrib
в lib
(где они будут автоматически подобраны ExtUtils::CBuilder
). Теперь дистрибутив адаптирован к платформе — следующие шаги ( ./Build ; …
) будут работать в обычном режиме.
Комментарии:
1. Интригующе … Я собираюсь стиснуть
Module::Build
зубы и попробовать это. Должны ли файлы вcontrib/
быть C или XS? XS было бы еще одной проблемой, которую нужно укусить.
Ответ №2:
В одном из моих модулей у меня есть следующий фрагмент кода:
my $C_support = Module::Build::ConfigData->feature("C_support")
my $builder = Module::Build->new(
...
config_data => {
C_support => $C_support
}
);
$builder->xs_files({}) if not $C_support;
Затем в коде я обнаруживаю это, загружая Module_name::ConfigData и вызывая метод config.
if (Module_name::ConfigData->config("C_support")) {
XSLoader::load(__PACKAGE__, $VERSION);
}
if (not defined amp;somefunction) {
#define it
}
Ответ №3:
Я использовал подобные методы:
sub slow_function {
# slow fallback perl code if possible
}
BEGIN {
eval {
require Inline;
if ($condition) {
Inline->import(C => q {
int slow_function (...) {
// c function to conditionally compile
}
})
} else {
Inline->import(C => q {
int slow_function (...) {
// c function with something different
}
})
}
1;
} or print STDERR "Inline::C error: $@ perl fallback used insteadn";
}
Комментарии:
1. Это похоже на подход, который я имел в виду, но компиляция во время выполнения (был использован удар при первом
slow_function
вызове, а не при первом вызове скрипта).2. Это, безусловно, тоже работает. В моем случае я заранее знал, что мне понадобится функция. Во время разработки одна из приятных особенностей использования Inline::C для этого заключается в том, что простое изменение кода C вызовет перекомпиляцию при следующем запуске, поэтому нет необходимости помнить о повторном запуске сценария сборки.