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!
In reply to Re: perl calling FORTRAN calling perl?
by almut
in thread perl calling FORTRAN calling perl?
by Anonymous Monk
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |