[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index][Thread Index][Top&Search][Original]

New way to do XS constants



It looks like the makemaker e-mail list may have become defunct so
I'm writing to p5p and CCing makemaker.  Since the only example module
is Win32-specific, I'm also CCing Perl-Win32-Porters.

I've written a new way to deal with constants in XS modules.  The
major advantages:
  * No autoloading required [but could be used if you had a truely
    huge list of constants and most users need few of them].
  * No doing lots of strcmp()s at module run time when you have lots
    of constants.
  * The most efficient method [at module run time] if not tons of
    unused constants
  * No modifying big complex C case statements when you decide to
    add more constants to your module.  To change which constants
    you support, you simply update your @EXPORT, @EXPORT_OK, and/or
    %EXPORT_TAGS entries and possibly make minor changes to your
    Makefile.PL.
  * Still uses C code to determine constant values -- not a
    throw-back to h2ph.

I'd like to patch MakeMaker, h2xs, etc. to more fully support this
new method so I wanted to get some initial feed back.

You can get http://www.metronet.com/~tye/alpha/Win32API/Registry-0.19.zip
for a full example.  The parts of the module specific to this
new method are included in this message so you shouldn't need
to fetch the module.  This release isn't going to CPAN until I
have more experience and/or feed back on it.

The heart of the new method is constants_pm_c.PLX.  It is used to
build a simple F<constants.pm> at module build time that simply
defines a bunch of constant subroutines.  Then the constants are
loaded into the module by a simple:

    require auto::Module::Name::constants;

[F<constants.pm> doesn't have a C<package> statement so the constants
get defined straight into the main module's package.]

The current version supports signed and unsigned integral types
of constants with a C compiler and adds support for floating-point
types if using a C++ compiler.  I also plan to add support for
string constants when using a C++ compiler.

The constants_pm_c.PLX's usage message summarizes the process well:

Usage: constants_pm_c.PLX Package::Name [-opts] [export_constant [const [...]]]
Example:  constants_pm_c.PLX Win32API::Registry :HKEY_ :KEY_ :REG_
Example:  constants_pm_c.PLX Win32API::Registry /[A-Z]_/ !/[a-z]/
    Writes F<constants_pm.c> that, when compiled and run, writes Perl code
    to STDOUT that defines constant subroutines for constants from the
    Win32API::Registry module.  This should be redirected to F<constants.pm>.
Process:
    By default, we C<eval> [the equivalent of] the output from the command:
	perl -pe "last if /^\s*(bootstrap|XSLoader::load)\b/" Name.pm
    then we call:
	package _constants;
	Package::Name->import(@consts);
    and note the list of symbols that get imported.
    Next we write [the equivalent of] the output from the command:
	perl -pe "last if /^\s*MODULE\b/" Name.xs
    to F<constants_pm.c> and then append C/C++ code that writes [to STDOUT]
    Perl code that defines constant subroutines for the exported constants.
Options:
    -e			Call Package::Name->export("_constants",@consts)
    			instead of Package::Name->import(@consts).
    -o output_pm.c	Create and write to F<output_pm.c>.
    -pe "pe_code"	Perl code to process F<Name.pm> before C<eval>ing it.
    		Defaults to C<last if /^\s*(bootstrap|XSLoader::load)\b/>.
    -pf Name.pm		Perl file to process then C<eval> before import/export.
    -ce "ce_code"	Perl code to process F<Name.xs> before copying into the
    			new C file.  Defaults to C<last if /^\s*MODULE\b/>.
    -cf Name.xs		C or XS file to process then include in new C file.
    -ps "pe_code" Name.pm	For when pe_code is specific to one file.
    -cs "ce_code" Name.xs	For when ce_code is specific to one file.
Example:  constants_pm_c.PLX IO::Pipe::Named -o Named_pc.cc -pf Pipe.pm /NAMED/
          constants_pm_c.PLX IO::Pipe::Sock -o Sock_pc.cc -pf Pipe.pm /SOCK/

Some specific Perl/MakeMaker changes that I'd like:

  * Add a "ccoutexe" to Config.pm that says what option to pass to the
    C compiler to specify the name of the executable file (usually "-o").
  * Have ExtUtils::MakeMaker copy this to $self->{CCOUTEXE}.
  * Have MakeMaker set $self->{LIBPERL_A} based on $Config{libperl}.
  * Add constants_pm_c.PLX to lib/ExtUtils.
  * Add a EXPORT_CONSTS option to WriteMakefile that gets copied to the
    command line of constants_pm_c.PLX.
  * Have WriteMakefile write a "constants" section.
  * Rename it to constants_pm_c.PL but have WriteMakefile know it is an
    exception like Makefile.PL.
  * Give h2xs the option of using this new method over a constant()
    subroutine written in C.
  * Have WriteMakefile add C<constants.pm> to the list of *.pm
    files if EXPORT_CONSTS is specified.
  * Give h2xs option to add ":constants" export tag that includes all
    constants.

Here is a Makefile.PL that implements the new scheme with an old
version of MakeMaker:

    use ExtUtils::MakeMaker;
    use Config;
    # See lib/ExtUtils/MakeMaker.pm for details of how to influence
    # the contents of the Makefile that is written.
    WriteMakefile(
	'NAME'		=> 'Win32API::Registry',
	'VERSION_FROM'	=> 'Registry.pm',	# finds $VERSION
	($] < 5.005 ? () : ('CAPI' => 'TRUE')),
	(  ! $Config{libperl}  ?  ()  :  ( LIBPERL_A => $Config{libperl} )  ),
	'PM'		=> {
	    'Registry.pm'	=> '$(INST_LIBDIR)/Registry.pm',
	    'constants.pm'	=> '$(INST_ARCHAUTODIR)/constants.pm',
	},
	'postamble'		=> {CONSTS => '/._/ !/[a-z]/'},
	'realclean'		=> {FILES =>
	    'constants_pm.c constants_pm$(EXE_EXT) constants.pm'},
    );

    sub MY::postamble
    {
	my( $self, %attribs )= @_;
	my $code= "";
	if(  $self->{CCOUTEXT}  ) { # We hope future MakeMaker will set this.
	    $code .= "";
	} elsif(  $self->{CC} =~ /cl([.]|$)/i  ) {
	    $code .= "\nCCOUTEXE = -Fe\n";
	} else {
	    $code .= "\nCCOUTEXE = -o\n";
	}
	$code .= <<'END';

    constants_pm.c:	constants_pm_c.PLX Makefile
	    $(PERL) -I$(INST_ARCHLIB) -I$(INST_LIB) \
		-I$(PERL_ARCHLIB) -I$(PERL_LIB) constants_pm_c.PLX \
    END
	$code .= "\t    $self->{NAME} $attribs{CONSTS}\n";
	$code .= <<'END';

    constants_pm$(EXE_EXT):	constants_pm.c
	    $(CC) $(INC) $(CCFLAGS) $(OPTIMIZE) $(PERLTYPE) $(LARGE) $(SPLIT) \
		$(DEFINE_VERSION) $(XS_DEFINE_VERSION) -I$(PERL_INC) $(DEFINE) \
		constants_pm.c $(PERL_INC)/$(LIBPERL_A) \
		$(CCOUTEXE)constants_pm$(EXE_EXT)

    constants.pm:	constants_pm$(EXE_EXT)
	    ./constants_pm$(EXE_EXT) >constants.pm

    END
	for(  qw{ $(PERL_INC)/$(LIBPERL_A) ./constants_pm$(EXE_EXT) }  ) {
	    $code =~ s#\Q$_\E# $self->catfile( split m{/}, $_ ) #e;
	}
	return $code;
    }

F<constants_pm_c.PLX> is included below my signature.

Anyway, comments appreciated.  Thanks,
-- 
Tye McQueen    Nothing is obvious unless you are overlooking something
         http://www.metronet.com/~tye/ (scripts, links, nothing fancy)

#!/usr/bin/perl -w

use strict;

use File::Basename;

my $output= basename($0);
if(  $output !~ s#_([^_.]+)[.][^.]+$#.$1#  ) {
    $output =~ s#[.][^.]+$##;
    $output .= ".c";
}
my $final= $output;
if(  $final !~ s#_([^_.]+)[.][^.]+$#.$1#  ) {
    $final =~ s#[.][^.]+$##;
    $final .= ".pm";
}
my( $pe_code, $ce_code )=
  ( 'last if /^\s*(bootstrap|XSLoader::load)\b/', 'last if /^\s*MODULE\b/' );

if(  ! @ARGV  ) {
    die <<END;
Usage: $0 Package::Name [-opts] [export_constant [const [...]]]
Example:  $0 Win32API::Registry :HKEY_ :KEY_ :REG_
Example:  $0 Win32API::Registry /[A-Z]_/ !/[a-z]/
    Writes F<$output> that, when compiled and run, writes Perl code
    to STDOUT that defines constant subroutines for constants from the
    Win32API::Registry module.  This should be redirected to F<$final>.
Process:
    By default, we C<eval> [the equivalent of] the output from the command:
	perl -pe "$pe_code" Name.pm
    then we call:
	package _constants;
	Package::Name->import(\@consts);
    and note the list of symbols that get imported.
    Next we write [the equivalent of] the output from the command:
	perl -pe "$ce_code" Name.xs
    to F<$output> and then append C/C++ code that writes [to STDOUT]
    Perl code that defines constant subroutines for the exported constants.
Options:
    -e			Call Package::Name->export("_constants",\@consts)
    			instead of Package::Name->import(\@consts).
    -o output_pm.c	Create and write to F<output_pm.c>.
    -pe "pe_code"	Perl code to process F<Name.pm> before C<eval>ing it.
    		Defaults to C<$pe_code>.
    -pf Name.pm		Perl file to process then C<eval> before import/export.
    -ce "ce_code"	Perl code to process F<Name.xs> before copying into the
    			new C file.  Defaults to C<$ce_code>.
    -cf Name.xs		C or XS file to process then include in new C file.
    -ps "pe_code" Name.pm	For when pe_code is specific to one file.
    -cs "ce_code" Name.xs	For when ce_code is specific to one file.
Example:  $0 IO::Pipe::Named -o Named_pc.cc -pf Pipe.pm /NAMED/
          $0 IO::Pipe::Sock -o Sock_pc.cc -pf Pipe.pm /SOCK/
END
}

die "Missing required package name [too few arguments].\n"   if  ! @ARGV;
my $pkg= shift( @ARGV );
die "Specify options ($pkg) _after_ the package name.\n"   if  $pkg =~ /^-/;
my @pkg= split /::/, $pkg;
my $mod= pop @pkg;
my $dir= join @pkg, "/";

sub NextArg
{
    my( $opt, $arg )= @_;
    # Global( $_, @ARGV );
    my $val;
    if(  "" ne $_  ) {
	$val= $_;
	$_= "";
    } elsif(  ! @ARGV  ) {
	die "Missing the required argument after -$opt [$arg].\n";
    } else {
	$val= shift( @ARGV );
    }
    return $val;
}

my( @pfiles, %pfiles, @cfiles, %cfiles );
my $doImport= 1;
while(  @ARGV  &&  $ARGV[0] =~ /^-/  ) {
    my $opt= $_= shift(@ARGV);
    s/^-//;
    do {
	if(  s/^e//i  ) {
	    $doImport= 0;
	} elsif(  s/^o//i  ) {
	    $output= NextArg( "o", $opt );
	} elsif(  s/^pe//i  ) {
	    $pe_code= NextArg( "pe", $opt );
	} elsif(  s/^pf//i  ) {
	    my $pfile= NextArg( "pf", $opt );
	    push @pfiles, $pfile;
	    $pfiles{$pfile}= $pe_code;
	} elsif(  s/^ps//i  ) {
	    my $pfile= NextArg( "ps", $opt );
	    push @pfiles, $pfile;
	    $pfiles{$pfile}= NextArg( "ps", $opt );
	} elsif(  s/^ce//i  ) {
	    $ce_code= NextArg( "ce", $opt );
	} elsif(  s/^cf//i  ) {
	    my $cfile= NextArg( "cf", $opt );
	    push @cfiles, $cfile;
	    $cfiles{$cfile}= $ce_code;
	} elsif(  s/^cs//i  ) {
	    my $cfile= NextArg( "cs", $opt );
	    push @cfiles, $cfile;
	    $cfiles{$cfile}= NextArg( "cs", $opt );
	} else {
	    die "Unrecognized option [-$_] in $opt.\n";
	}
    } while(  "" ne $_  );
}

if(  ! %pfiles  ) {
    push @pfiles, "$mod.pm";
    $pfiles{"$mod.pm"}= $pe_code;
}
if(  ! %cfiles  ) {
    push @cfiles, "$mod.xs";
    $cfiles{"$mod.xs"}= $ce_code;
}

warn "Writing $output...\n";
open( STDOUT, ">$output" )  or  die "Can't create $output: $!\n";

my $code= "";
my $file;
for $file (  @pfiles  ) {
    warn "Reading Perl file, $file:  $pfiles{$file}\n";
    open( MODULE, "<$file" )  or  die "Can't read Perl file, $file: $!\n";
    eval qq{
	while(  <MODULE>  ) {
	    $pfiles{$file};
	    \$code .= \$_;
	}
	1;
    }  or  die "$file eval: $@\n";
    close( MODULE );
}

for $file (  @cfiles  ) {
    warn "Reading C file, $file:  $cfiles{$file}\n";
    open( XS, "<$file" )  or  die "Can't read C file, $file: $!\n";
    eval qq{
	while(  <XS>  ) {
	    $cfiles{$file};
	    print;
	}
	1;
    }  or  die "$file eval: $@\n";
    close( XS );
}

print <<'END';
#ifdef __cplusplus
#include <iostream.h>
#define const2perl( const )	\
	cout << "sub " << #const << " () { " << const << "; }" << endl
#else
#include <stdio.h>
#define const2perl( const )	do {	 				\
	printf( "sub %s () { ", #const );				\
	printf( const <= 0 && const != 0 ? "%ld" : "0x%08lX", (long)const ); \
	printf( "; }\n" );						\
    } while( 0 )
#endif

#include <stdio.h>	/* Probably already included, but shouldn't hurt */

int
main( int argc, char **argv )
{
END

$^W= 0;
eval $code . q{
    $code;
    {
	package _constants;
	if(  $doImport  ) {
	    warn "Importing symbols:  @ARGV\n";
	    $pkg->import( @ARGV );
	} else {
	    warn "Exporting symbols:  @ARGV\n";
	    $pkg->export( "_constants", @ARGV );
	}
    }
    no strict 'refs';
    warn( 0 + keys %{"_constants::"}, " symbols imported/exported.\n" );
    for(  sort keys %{"_constants::"}  ) {
	print "\tconst2perl( $_ );\n";
    }
    1;
}  or  die "eval: $@\n";

print <<'END';
	printf( "1;\n" );
	return( 0 );
}
END


Follow-Ups from:
Ilya Zakharevich <ilya@math.ohio-state.edu>
Tim Bunce <Tim.Bunce@ig.co.uk>

[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index][Thread Index][Top&Search][Original]