wiki:DevelopersPerlExtending
Last modified 7 years ago Last modified on 11/15/06 10:36:33

Building a Perl Module

Example.pm

    # example taken from Extending and Embedding Perl book
    package Example;

    use 5.006;
    use strict;
    use warnings;

    use base qw(Exporter);

    our $VERSION = '0.01';

    our @EXPORT_OK = qw(myfunc);

    sub myfunc {
        my $arg = shift;
        return $arg;
    }

    1;

This is a simple example of a perl module. You tell perl what the package name is, Example. As always, use strict, pull the Exporter package from the base module. We use @EXPORT_OK so that the myfunc does not always get exported to the users namespace. @EXPORT_OK contains all functions that can be exported and have to be explicitly requested by the user. We do not use the @EXPORT array because that always exports the function, even even when you dont ask for it.

<need to show a code example of difference>

Creating the correct directory structure of a module is made quick and easy with the h2xs script. h2xs's purpose is to translate a c/c++ header file to the correct xs file. To create a perl module, we might the command

    h2xs -X Example

This would build the correct directory structure for a module named Example. Because we used the -X flag, the Example.xs file would not be provided. This is ok for modules implemented in perl. The xs file acts as glue for creating bindings from perl back to c/c++ code.

<need to explain the module directory structure>

We can test our new module by pointing perl's library search path to the directory where our module lives and running a test command.

    export PERL5LIB=/home/derrick/playground/perl/extending/Example/lib
    perl -MExample -e 'print Example::myfunc( "hi derrick\n" )'

That is a very simple way to create a module, lets now place our .pm file into a module directory suitable for distribution.

    cp Example.pm Example/lib/.
    export PERL5LIB=""
    cd Example

We use perl's Makefile.PL and MakeMaker module to generate a makefile for our Example module.

    perl Makefile.PL
    make

The above commands build (or really copy in this case) the module. With the following command, we can test the module.

    perl -Mblib -MExample -e 'print Example::myfunc( "hi derrick\n" )'

Simple C Function to Perl Example

Next we'll explore a perl module that calls a c function. We start by using h2xs to create a new module directory.

    h2xs -A -n CFuncToPerl

The -A flag means "Omit all autoload facilities" The -n flag is used to specify the name of the extension. I do not know why we use -A, but many people do it in their examples. We use -n because we did not specify an actual C header file.

Introducing the Lingo Terms to know:

XS - eXternal Subroutines, wrappers that allow you to access C code
XSUB - individual wrapper / subroutine within an XS file
xsubpp - XS compiler

What is the XS file? If we take a look into our newly created module directory, we will see a file named CFuncToPerl.xs. This is the XS file. It holds code that resembles a C-like syntax and is eventually processed into C source files by the xsubpp processor. The C file generated by xsubpp is then compiled into a library that you link into perl as a module.

Lets start by editing CFuncToPerl.xs to add a C function called void print_hello(). The function is simple, it prints out the word "hello". Here is the code for the function:

    void print_hello(void)
    {
        printf("hello\n");
    }

We open up the file CFuncToPerl.xs and see the basics of an xs file:

    #include "EXTERN.h"
    #include "perl.h"
    #include "XSUB.h"

    #include "ppport.h"

    MODULE = CFuncToPerl    PACKAGE = CFuncToPerl

You can see the #include statements for header files, and then there is the MODULE and PACKAGE tags. This line establishes the name of the module. All code above this line is considered C code, and everything below is considered XS code. Adding our function to the xs file, the new xs file looks as follows:

    #include "EXTERN.h"
    #include "perl.h"
    #include "XSUB.h"

    #include "ppport.h"

    void print_hello(void)
    {
        printf("hello\n");
    }

    MODULE = CFuncToPerl    PACKAGE = CFuncToPerl

    void
    print_hello();

You'll notice we not only added our function, but also added two lines of code under the MODULE line. With those two lines we are telling the xsubpp that we within the CFuncToPerl package, there is a function called print_hello. The print_hello function prototype is print_hello(). It takes in no parameters and returns no data.

We can quickly compile this example using:

    perl Makefile.PL
    make

and test it using

    perl -Mblib -MCFuncToPerl -e "CFuncToPerl::print_hello()"

Perl's xsubpp recognizes many of the simple C data types like void, int, and double. Lets try adding an example with some doubles.

Our new function is:

    double subtract (double a, double b)
    {
        return (b-a);
    }

We add the xsub definition as follows:

    double
    subtract (a,b)
        double a
        double b

Here, as described in the xsub, the perl function invocation would look like this:

    my ($result) = subtract(a,b)

This basically says subtract is looking to take in two double values, and return a double value represented by $result

Now take a look at another way of writing a function. This add function takes in two double values and a pointer to the result. The function returns an integer signaling an error has occured in the function.

    int add(double a, double b, double* result)
    {
        int err = 0;
        if (result != NULL) {
            *result = a+b;
        }
        else {
            err++;
        }

        return err;
    }

The xsub that would allow us to access this function utilizes the IN and OUTLIST keywords. By default, all unspecified paramters are considered IN.

    int
    add (IN double a, IN double b, OUTLIST double result)

or

    int
    add (a, b, OUTLIST double result)
        double a
        double b

The OUTLIST keyword tells XS that the C function will write results to the address pointed to by the parameter. XS also orgranizes the Perl function prototype to return the return value as well as the parameters listed with the OUTLIST keyword. So the Perl function prototype for the add function would be:

    my ($result) = add($a,$b);

If you wanted to return the integer return value of the function, you would describe the XSUB as follows:

    int
    add_again(a, b, OUTLIST double result)
        double a
        double b
      CODE:
        RETVAL = add(a,b,&result);
      OUTPUT:
        RETVAL

The above code produces the following perl function prototype:

    my ($status,$result) = multiply($a,$b)

Note the CODE:, OUTPUT:, and RETVAL: keywords that are used in the example above. The CODE: keyword is used to provide more sophisticated handling of calls to C functions within an XSUB. The OUTPUT: keyword is used tell perl which values of the XSUB should be refreshed and returned to the user as a result of the function. The RETVAL: keyword is automatically assigned to the return value of the C function in the XSUB. One catch is with the use of the CODE: keyword. When the CODE: keyword is used, RETVAL is no longer automatically returned to the user. This is why we use the OUTPUT: keyword to tell the XSUB to refresh the value of RETVAL and return it to the user. In the example above we also used the OUTLIST keyword so in total, we are returning two values: double* result from the C function and the integer return value of the C function.

Next we will tackle calling functions from C Library inside of Perl. We draw up the C library, lets call it Poo

poo.h

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    void print_hello (void);
    int add (double a, double b, double* result);
    double subtract (double a, double b);

poo.c

    #include "poo.h"

    void print_hello (void)
    {
        printf("hello\n");
    }

    int add (double a, double b, double* result)
    {
        int err = 0;
        if (result != NULL) {
            *result = a+b;
        }
        else {
            err++;
        }

        return err;
    }

    double subtract (double a, double b)
    {
        return (b-a);
    }

Makefile

    CC              = gcc
    DEBUG           = -g -Wall
    LIB_POO         = -Wl,-rpath,./ -L./ -lpoo
    CFLAGS          = -fPIC

    LDLIB_MACOSX = -dynamiclib -o $@.dylib
    LDLIB_LINUX = -shared -Wl,-rpath,./ -Wl,-soname,$@.so -o $@.so.0.0

    libpoo: poo.o
        if test "`uname`" == "Darwin"; then \
            $(CXX) $(DEGUG) $(LDLIB_MACOSX) $^; \
            ar -r $@.a $^; \
            ranlib -s $@.a; \
        else \
            $(CXX) $(DEGUG) $(LDLIB_LINUX) $^; \
            /sbin/ldconfig -n ./; \
            ar -r $@.a $^; \
            ranlib $@.a; \
        fi

    poo.o: poo.c
        $(CC) $(CFLAGS) $(DEBUG) -o $@ -c $?

    poo: poo_main.c libpoo
        $(CC) $(DEBUG) $(LIB_POO) -o $@ $<

    clean:
        - rm -f *.o poo libpoo

Create a new, clean module.

    h2xs -A -n CLibToPerl

Add our XSUB's to the CLibToPerl.xs file. They are the same as those we wrote in CFuncToPerl.xs:

CLibToPerl.xs:

    #include "EXTERN.h"
    #include "perl.h"
    #include "XSUB.h"

    #include "ppport.h"


    MODULE = CLibToPerl    PACKAGE = CLibToPerl

    void
    print_hello();

    int
    add (a, b, OUTLIST double result)
        double a
        double b

    int
    add_again(a, b, OUTLIST double result)
        double a
        double b
      CODE:
        RETVAL = add(a,b,&result);
      OUTPUT:
        RETVAL

    double
    subtract (a,b)
        double a
        double b

Adjust Makefile.PL to tell perl's xsubpp where to find header files and libraries it needs to compile against C library libpoo

Makefile.PL:

    use 5.008008;
    use ExtUtils::MakeMaker;
    # See lib/ExtUtils/MakeMaker.pm for details of how to influence
    # the contents of the Makefile that is written.
    WriteMakefile(
        NAME              => 'CLibToPerl',
        VERSION_FROM      => 'lib/CLibToPerl.pm', # finds $VERSION
        PREREQ_PM         => {}, # e.g., Module::Name => 1.1
        ($] >= 5.005 ?     ## Add these new keywords supported since 5.005
          (ABSTRACT_FROM  => 'lib/CLibToPerl.pm', # retrieve abstract from module
           AUTHOR         => 'derrick <derrick@>') : ()),
        LIBS              => ['-L/home/derrick/playground/perl/extending/Poo -lpoo'], # e.g., '-lm'
        DEFINE            => '', # e.g., '-DHAVE_SOMETHING'
        INC               => '-I. -I/home/derrick/playground/perl/extending/Poo', # e.g., '-I. -I/usr/include/other'
    # Un-comment this if you add C files to link with later:
        # OBJECT            => '$(O_FILES)', # link all the C files too
    );

Building the module is no different than previous times.

    perl Makefile.PL
    make

If everything builds successfully, we can try adding tests to the file t/CLibToPerl.t

t/CLibToPerl.t:

    # Before `make install' is performed this script should be runnable with
    # `make test'. After `make install' it should work as `perl CLibToPerl.t'

    #########################

    # change 'tests => 1' to 'tests => last_test_to_print';

    use Test::More tests => 5;
    BEGIN { use_ok('CLibToPerl') };

    #########################

    # Insert your test code below, the Test::More module is use()ed here so read
    # its man page ( perldoc Test::More ) for help writing this test script.


    ok(CLibToPerl::add(1,2) == 3, "Adding 1 and 2");

    ok(CLibToPerl::subtract(1,3) == 2, "Subtract 1 from 3");

    my ($status, $result) = CLibToPerl::add_again(2,3);
    ok((($status == 0) && ($result == 5)), "Adding 2 and 3 and returning status");
    ok((CLibToPerl::add_again(2,3) == 5), "Adding 2 and 3 and ignoring status");

And we run the tests and hope for success!

    perl -Mblib -MCLibToPerl t/CLibToPerl.t