Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I am working on an application which can use part of Dierckx's fitpack implementation at Netlib. The code looks to be old FORTRAN-IV converted minimally converted into FORTRAN-77. Generating a FORTRAN library to call from perl shouldn't be too hard. However, one of the routines requires the user to pass in a reference to a subroutine as an argument to a FORTRAN subroutine. If I implement a perl front end, I would guess this means the user provided subroutine would be in perl? Is this something which is possible, or should I just skip that particular function?

Replies are listed 'Best First'.
Re: perl calling FORTRAN calling perl?
by almut (Canon) on Sep 25, 2007 at 12:47 UTC

    If I'm understanding you correctly, you'd like to write a Perl binding to a Fortran lib containing a function such as

    real function evapol(tu,nu,tv,nv,c,rad,x,y)

    from Dierckx's fitpack, where rad is the user-defined callback routine in question, which the user of the Perl binding of course wants to write in Perl

    c rad : real function subprogram, defining the boundary of the c approximation domain. must be declared external in the c calling (sub)-program

    Although this is possible, it's a bit involved, as the flow of execution has to go through a layer of C glue code in both directions...

    Anyway, I tried to put together a minimal example, which will hopefully get you started. Unfortunately, it's still rather lengthy...  I've tested it with the g77 GNU Fortran compiler, but things should in principle work similarly with other compilers.

    Let's assume we have the following Fortran code

    double precision function myfunc(x, y, func) double precision x, y, z, v double precision func v = 1.5 z = func(v) myfunc = x * y * z end

    This defines a function myfunc which we're going to call from Perl. It takes three arguments, the last one of which is normally expected to be a reference to a user-defined Fortran function (declared as external in the calling Fortran code). The latter function is supposed to be implemented as a user-defined Perl routine here.

    Ok, the first step is to compile the Fortran code:

    $ g77 -c myfunc.f -o myfunc.o

    The resulting object file myfunc.o is what you need to link the Perl extension against.

    Next, create a project working directory for the extension and populate it with the necessary files. There are several ways to do this... one of them would be to use h2xs. For example

    $ h2xs -Afn P2f2p

    where "P2f2p" is the name of the extension ("Perl-to-Fortran-to-Perl" — couldn't think of a better name). This creates several files and directories, most importantly P2f2p.xs and Makefile.PL.

    However, P2f2p.xs is just a default stub file like this

    #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" MODULE = P2f2p PACKAGE = P2f2p

    so you need to modify it to actually do something:

    #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" static SV * userfunc; double userfunc_wrapper(double *x) { int n; double r; dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVnv(*x))); PUTBACK; n = call_sv(userfunc, G_SCALAR); SPAGAIN; if (n != 1) croak("wrong number of args returned!\n"); r = POPn; PUTBACK; FREETMPS; LEAVE; return r; } // the Fortran function double myfunc_(double *x, double *y, void* func); double myfunc(double x, double y) { double r; r = myfunc_(&x, &y, &userfunc_wrapper); return r; } MODULE = P2f2p PACKAGE = P2f2p double myfunc(x, y, ufunc) double x double y SV * ufunc CODE: userfunc = newSVsv(ufunc); RETVAL = myfunc(x, y); OUTPUT: RETVAL

    (To keep things as concise as possible, I chose to put all code in this .xs file... In a larger project, you could of course do it proPerly and put stuff in separate .h/.c files...)

    Then, modify Makefile.PL to have the generated Makefile issue commands to link against the Fortran object (myfunc.o) and g77 runtime support lib (libg2c.so), i.e. change

    LIBS => [''], # e.g., '-lm' OBJECT => '$(O_FILES)', # link all the C files too

    in WriteMakefile(...) to read:

    LIBS => ['-lg2c -lm'], # e.g., '-lm' OBJECT => '$(O_FILES) myfunc.o', # link all the C files + too

    Lastly, don't forget to copy myfunc.o into the P2f2p project directory.

    Having completed those steps, you're ready to do the usual

    perl Makefile.PL make make test

    (The make test isn't doing much — without any other tests having been written, it just checks if the extension can be loaded.)

    Now, if you're like many people, you probably don't want to install the newly created extension system-wide yet, in order to just play around with it. Of course, you could've messed with PREFIX= and friends, but the easiest way might be to simply copy the important two files into some testing directory:

    $ mkdir -p /path/to/working/directory/auto/P2f2p $ cp blib/arch/auto/P2f2p/P2f2p.so /path/to/working/directory/auto/P2f +2p/ $ cp blib/lib/P2f2p.pm /path/to/working/directory/

    Then you can write a little test script (and place it in the same test directory):

    #!/usr/bin/perl use P2f2p; sub userfunc { my $v = shift; print "userfunc()\n called from Fortran with arg: $v\n"; my $r = $v + 0.5; print " returning: $r\n"; return $r; } my $r = P2f2p::myfunc(3, 7, \&userfunc ); printf "myfunc() returned: %.2f\n", $r;

    When you run it, it should print

    userfunc() called from Fortran with arg: 1.5 returning: 2 myfunc() returned: 42.00

    So what's happening in detail?  P2f2p::myfunc is calling XS_P2f2p_myfunc under the hood, which is the XS wrapper function as expanded by xsubpp. This function stores the reference to the passed in user-defined Perl routine, and then calls myfunc, which in turn calls the actual Fortran function myfunc_ (note the underscore, as automatically appended by g77), passing it a pointer to the callback wrapper function userfunc_wrapper. The wrapper is responsible for calling the real Perl routine (via call_sv) and handling its arguments (see perlcall for what all those macros are doing). It is being called from within the Fortran code, and is expected to return some value computed by Perl.  (I'll leave it as an exercise for the reader to figure out how this all results in the final 42.)  Good luck!

      I am going to have to study this a bit. I've played with a lot of FORTRAN and perl over the years, but never together. Since someone in the python community has Dierckx's code working in python, I thought I would give it a try in perl. I don't (quickly/easily) see that evapol is in the python package. I think this is going to be a good exercise for this particular reader. Thanks.
Re: perl calling FORTRAN calling perl?
by perlfan (Parson) on Sep 24, 2007 at 23:36 UTC
    Are you trying to create a Perl script UI to an executable, or are you planning on using a fortran library as part of a Perl program?
    If the former, then I suggest creating an interactive fortran program that take in all arguments via STDIN (interactively, though). Once you have that, you can easily create a simple Perl (or even shell) front end that creates whatever interface you want around it. I actually recommend a shell script in this case.

    #!/bin/sh ./myexe <<EOF arg1 arg2 ... argN EOF

    If the latter, then I have no idea. And I am confused because it sounds like you are saying that fortran (77) subs take subroutine (the fortran kind) as arguments. I don't think that is true.

    Finally, if you are thinking about calling a Perl program via fortran's "system" call, I have one bit of advice - don't. Figure out a way around this if at all possible.
      I had thought about creating a perl script UI to an executable calling a specific function in the Dierckx fitpack. However, many of the routines there require work arrays which contain temporary results, and the function has to be called again with an integer flag changed to have it calculate the final results. Since I am planning on thousands of data points for the function of most interest, the floating point work array would need to be dimensioned at something like 500,000 elements, which would all need to be read in from STDIN. It just didn't look like a workable solution.

      FORTRAN has been able have a subroutine/function name as an argument to a subroutine/function for a long time. There are lots of math problems which require this.