我有一个针对几种不同操作系统的模块 和配置。有时,一些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
内部运行时绑定的方案
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 ... removing\n\n";
unlink $bad_xs;
print $failed_units_fh "$bad_xs\n";
} 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 "$xs\n";
}
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.xs\n";
}
### STEP 3: Clean all .xs stuff and compile My/Module.xs ###
unlink <lib/xs/*>;
$self->ACTION_code();
return $self->SUPER::ACTION_build(@_);
}
}
对$@
的检查可能非常脆弱。它适用于系统
我已经尝试过(所有使用gcc),但它可能无法正常工作
无处不在。
答案 0 :(得分:5)
理想情况下,请使用Module::Build
。在配置时(perl Build.PL
),检测平台和标头位置(但也让用户指定命令行选项以覆盖检测),在构造函数中设置相关的extra_compiler_flags
和extra_linker_flags
然后从例如复制相关文件contrib
到lib
(ExtUtils::CBuilder
会自动选择它们)。现在,分发是针对平台定制的 - 接下来的步骤(./Build ; …
)将正常工作。
答案 1 :(得分: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 &somefunction) {
#define it
}
答案 2 :(得分:1)
我使用过这样的技术:
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 instead\n";
}