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

Posted

技术标签:

【中文标题】如何有条件地将 C 代码片段编译到我的 Perl 模块?【英文标题】:How do I conditionally compile C code snippets to my Perl module? 【发布时间】:2010-04-07 06:13:37 【问题描述】:

我有一个针对多个不同操作系统的模块 和配置。有时,一些 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),但它可能无法像它所写的那样工作 无处不在。

【问题讨论】:

【参考方案1】:

理想情况下,使用Module::Build。在配置时(perl Build.PL),检测平台和头文件位置(但也让用户指定命令行选项覆盖检测),在构造函数中设置相关的extra_compiler_flagsextra_linker_flags,然后复制相关文件从例如contriblibExtUtils::CBuilder 将自动接收它们)。现在分发已针对平台进行定制 - 后续步骤 (./Build ; …) 将正常工作。

【讨论】:

耐人​​寻味...我将咬住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 &somefunction) 
    #define it

详情看我的Build.PL和Module.pm

【讨论】:

【参考方案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 instead\n";

【讨论】:

这类似于我想到的方法,但在运行时编译(第一次使用 slow_function 而不是第一次调用脚本时)。跨度> 这当然也有效。就我而言,我知道我将提前需要该功能。在开发过程中,使用 Inline::C 执行此操作的好处之一是,只需更改 C 代码将在下次运行时触发重新编译,因此无需记住重新运行 Build 脚本。

以上是关于如何有条件地将 C 代码片段编译到我的 Perl 模块?的主要内容,如果未能解决你的问题,请参考以下文章

如何将变量添加到我的 Perl 单行中?

如何正确地将 Kal 框架添加到我的 iPhone 项目中?

如何有条件地将子字符串复制到熊猫数据框的新列中?

如何根据从 ajax 调用返回的数据动态地将选项添加到我的自动完成功能?

尝试将 Vlookup 片段添加到我的 Excel 宏

如何最好地将 C++/Cython 项目编译成可执行文件?