代码之家  ›  专栏  ›  技术社区  ›  mob

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

  •  5
  • mob  · 技术社区  · 14 年前

    我有一个针对不同操作系统的模块 以及配置。有时,一些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使用?

    感谢所有的想法!


    更新: 感谢所有回应的人。所以我做的是:

    我考虑了一个运行时绑定方案 内联:: 里面 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),但可能写的不好 到处都是。

    3 回复  |  直到 14 年前
        1
  •  5
  •   daxim Fayland Lam    14 年前

    理想地,使用 Module::Build . 在配置时( perl Build.PL ),检测平台和报头位置(还允许用户指定命令行选项来覆盖检测),设置相关的 extra_compiler_flags extra_linker_flags 在构造函数中,然后从 contrib lib (它们将由 ExtUtils::CBuilder )现在,发行版已定制到平台上-下一步( ./Build ; … )会正常工作的。

        2
  •  2
  •   Leon Timmermans    14 年前

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

    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
  •  1
  •   Eric Strom    14 年前

    我用过这样的技巧:

    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";
    }
    
    推荐文章