如何有条件地将 C 代码片段编译到我的 Perl 模块?
我有一个针对多个不同操作系统的模块 和配置。有时,一些 C 代码可以完成该模块的任务 更容易一点,所以我有一些想要绑定的 C 函数 代码。我没有必须绑定 C 函数——我不能保证 例如,最终用户甚至有一个 C 编译器,而且通常是 优雅地故障转移到纯 Perl 的完成方式不是问题 同样的事情——但如果我可以调用 C 函数那就太好了 来自 Perl 脚本。
还在我身边吗?这是另一个棘手的部分。几乎所有 C 代码 是系统特定的——为 Windows 编写的函数无法编译 Linux 反之亦然,以及在 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
< /a>,例如)?哪个 方式更好吗?我如何知道哪些函数已成功编译并且正在运行 可以从 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),但它可能不会像写的那样工作 到处。
I have a module that will target several different operating systems
and configurations. Sometimes, some C code can make this module's task
a little easier, so I have some C functions that I would like to bind
the code. I don't have to bind the C functions -- I can't guarantee
that the end-user even has a C compiler, for instance, and it's generally
not a problem to failover gracefully to a pure Perl way of accomplishing
the same thing -- but it would be nice if I could call the C functions
from the Perl script.
Still with me? Here's another tricky part. Just about all of the C code
is system specific -- a function written for Windows won't compile on
Linux and vice-versa, and the function that does a similar thing on
Solaris will look totally different.
#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;
}
Furthermore, even for native code that targets the same system, it's
possible that only some of them can be compiled on any particular
configuration.
#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;
}
But ideally we could still use the C functions that would compile
with that configuration. So my questions are:
how can I compile C functions conditionally (compile only the code that
is appropriate for the current value of$^O
)?how can I compile C functions individually (some functions might not
compile, but we still want to use the ones that can)?can I do this at build-time (while the end-user is installing the
module) or at run-time (withInline::C
, for example)? Which
way is better?how would I tell which functions were successfully compiled and are
available for use from Perl?
All thoughts appreciated!
Update: Thanks to all who responded. So here's what I did:
I considered a scheme of run-time binding with Inline::C
inside ofeval
statements, but ultimately settled on subclassing Module::Build
and customizing the ACTION_build
method:
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(@_);
}
}
The check on $@
is probably pretty fragile. It works on the systems
I've tried (all using gcc), but it probably won't work as it's written
everywhere.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
理想情况下,使用
Module::Build
。在配置时(perl Build.PL
),检测平台和标头位置(但也让用户指定命令行选项来覆盖检测),设置相关的extra_compiler_flags
和extra_linker_flags
在构造函数中,然后将相关文件从contrib
复制到lib
(其中它们将被ExtUtils 自动获取::CBuilder
)。现在,发行版已针对平台进行了定制 - 接下来的步骤 (./Build ; ...
) 将正常工作。Ideally, use
Module::Build
. At configure time (perl Build.PL
), detect the platform and header location (but also let the user specify command-line options to override detection), set the relevantextra_compiler_flags
andextra_linker_flags
in the constructor and then copy the relevant files from e.g.contrib
tolib
(where they will be automatically picked up byExtUtils::CBuilder
). Now the distribution is customised to the platform - the next steps (./Build ; …
) will work as normal.在我的一个模块中,我有以下代码:
然后在代码中我通过加载 Module_name::ConfigData 并调用 config 方法来检测它。
有关详细信息,请查看我的 Build.PL 和Module.pm
In one of my modules I have the following piece of code:
Then in the code I detect it by loading Module_name::ConfigData and calling the config method.
For details, look at my Build.PL and Module.pm
我使用过这样的技术:
I've used techniques like this: