如何有条件地将 C 代码片段编译到我的 Perl 模块?

发布于 2024-08-27 18:44:07 字数 3921 浏览 5 评论 0原文

我有一个针对多个不同操作系统的模块 和配置。有时,一些 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 (with Inline::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 of
eval 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

风吹雨成花 2024-09-03 18:44:07

理想情况下,使用 Module::Build。在配置时(perl Build.PL),检测平台和标头位置(但也让用户指定命令行选项来覆盖检测),设置相关的extra_compiler_flagsextra_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 relevant extra_compiler_flags and extra_linker_flags in the constructor and then copy the relevant files from e.g. contrib to lib (where they will be automatically picked up by ExtUtils::CBuilder). Now the distribution is customised to the platform - the next steps (./Build ; …) will work as normal.

緦唸λ蓇 2024-09-03 18:44:07

在我的一个模块中,我有以下代码:

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
}

有关详细信息,请查看我的 Build.PLModule.pm

In one of my modules I have the following piece of code:

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;

Then in the code I detect it by loading Module_name::ConfigData and calling the config method.

if (Module_name::ConfigData->config("C_support")) {
    XSLoader::load(__PACKAGE__, $VERSION);
}
if (not defined &somefunction) {
    #define it
}

For details, look at my Build.PL and Module.pm

假装不在乎 2024-09-03 18:44:07

我使用过这样的技术:

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";
}

I've used techniques like this:

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";
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文