2010-04-07 7 views
5

Tengo un módulo que se dirigirá a varios sistemas operativos diferentes y configuraciones. A veces, algunos códigos C pueden hacer que la tarea de este módulo sea un poco más fácil, por lo que tengo algunas funciones C que me gustaría vincular al código . No tengo tengo para enlazar las funciones C - No puedo garantizar que el usuario final tenga incluso un compilador C, por ejemplo, y generalmente es no es un problema para conmutar por error con gracia a un modo Perl puro de Alcanzando lo mismo, pero sería bueno si pudiera llamar a las funciones C del script de Perl.¿Cómo compilo condicionalmente los fragmentos de código C en mi módulo Perl?

¿Seguirme? Aquí hay otra parte difícil. Casi todo el código C es específico del sistema; una función escrita para Windows no se compilará en Linux y viceversa, y la función que hace algo similar en Solaris se verá totalmente diferente.

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

Por otra parte, incluso para código nativo que se dirige el mismo sistema, es posible que sólo algunos de ellos puede ser compilado en cualquier configuración particular .

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

Pero lo ideal aún podríamos utilizar las funciones C que recogería las con esa configuración. Así que mis preguntas son:

  • cómo puedo compilar condicionalmente funciones C (compilar sólo el código que es adecuado para el valor actual de $^O)?

  • ¿cómo puedo compilar las funciones de C individualmente (algunas funciones pueden no compilar , pero aún queremos usar las que sí pueden)?

  • ¿Puedo hacer esto en tiempo de compilación (mientras el usuario final está instalando el módulo ) o en tiempo de ejecución (con Inline::C, por ejemplo)? ¿Qué forma de es mejor?

  • ¿Cómo puedo saber qué funciones se han compilado correctamente y están disponibles para Perl?

Todos los pensamientos apreciados!


Actualización: Gracias a todos los que respondieron.Así que aquí está lo que hice:

consideré un esquema de tiempo de ejecución de unión con Inline::C interior de eval declaraciones, pero finalmente se establecieron en la subclasificación Module::Build y personalizar el ACTION_build método:

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 [email protected] = undef; 
    do { 
     my $r = eval { $self->ACTION_code() }; 
     if ([email protected] =~ /error building (\S+\.o) from/i 
      || [email protected] =~ /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 ([email protected]) { 
     print STDERR "Compile error not handled in $^O: [email protected]\n"; 
     } 
    } while [email protected]; 
    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(@_); 
    } 
} 

La comprobación de [email protected] es probablemente bastante frágil. Funciona en los sistemas Lo he intentado (todos usando gcc), pero probablemente no funcionará, ya que está escrito en todas partes.

Respuesta

5

Lo ideal es utilizar Module::Build. En el momento de la configuración (perl Build.PL), detecte la ubicación de la plataforma y el encabezado (pero también permita que el usuario especifique opciones de línea de comandos para anular la detección), establezca extra_compiler_flags y extra_linker_flags en el constructor y luego copie los archivos relevantes de, p. Ej. contrib a lib (donde serán recogidos automáticamente por ExtUtils::CBuilder). Ahora la distribución está personalizada para la plataforma; los próximos pasos (./Build ; …) funcionarán normalmente.

+0

intrigante ... voy a morder la bala 'Módulo :: Build' y probar esto. ¿Los archivos en 'contrib /' deberían ser C o XS? XS sería otra bala para morder. – mob

+0

Necesita ser XS. – daxim

2

En uno de mis módulos tengo el siguiente fragmento de código:

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; 

Luego, en el código que detectarlo mediante la carga de module_name :: configdata y llamar al método de configuración.

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

Para más detalles, ver mi Build.PL y Module.pm

1

técnicas que he utilizado como este:

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: [email protected] perl fallback used instead\n"; 
} 
+0

Esto es similar al enfoque que tenía en mente, pero compilando en tiempo de ejecución (tomando el hit la primera vez que se usó 'slow_function' en vez de la primera vez que se llamó al script). – mob

+0

Eso ciertamente también funciona. En mi caso, sabía que iba a necesitar la función antes de tiempo. Durante el desarrollo, una de las cosas buenas de usar Inline :: C para hacer esto es que simplemente cambiando el código C desencadenará una recompilación la próxima vez que se ejecute, por lo que no hay necesidad de recordar volver a ejecutar el script de compilación. –

Cuestiones relacionadas