Calling Fortran Routines Externally from Unix Versions of IDL. Jeff Valenti (Valenti@Soleil.Berkeley.Edu) Mar 26, 1992
Additional Credits: Fen Tamanaha, Gwyn Fireman, Phil Turet Disclaimer: It worked for me, but it may not work for you.
Many people have requested help calling fortran routines externally from Unix versions of IDL. The makers of IDL (RSI) can't help much, as they apparently do not have fortran on their Unix machines, and thus they lack the required experience. I have succeeded in calling a few fortran routines from IDL, so perhaps it would be appropriate for me to discuss some of the problems involved and provide a working example.
The following discussion applies specifically to a Sun IPC running SunOS 4.1.1, but it will probably provide useful hints for other Unix environments as well. Please let me know if you get this working on other machines, so I can include the information in later revisions of this letter. Corrections, additions, and comments should be sent to me at Valenti@Soleil.Berkeley.Edu.
All right, my first task is to try to convince you to turn back from your goal. Calling fortran from Unix IDL is rather painful and should only be attempted if it is absolutely necessary. Here are some other options to consider first.
============================ IDL Spawn Examples ============================
soleil:idl% cat fexample1.f
integer*2 n
real*4 x(50), y(50)
end
soleil:idl% idl
IDL> n = 10 ;length of data vectors
IDL> x = findgen(n) ;init data to fortran
IDL> y = fltarr(n) ;init data from fortran
IDL> openw,unit,'temp_file',/get_lun ;open temporary file
IDL> printf,unit,n,x ;write data for fortran
IDL> free_lun,unit ;close file, release lun
IDL> spawn,'fexample1' ;fortran program runs, exits
IDL> openr,unit,'temp_file',/get_lun ;open temporary file
IDL> readf,unit,y ;read data from fortran
IDL> free_lun,unit ;close file, release lun
IDL> print,y
0.00000 1.00000 4.00000 9.00000 16.0000 16.0000 36.0000 49.0000 64.0000 81.0000
IDL> exit
integer*2 n
real*4 x(50), y(50)
end
soleil:idl% idl
IDL> n = 10 ;length of data vectors
IDL> x = findgen(n) ;init data to fortran
IDL> openw,unit,'temp_file',/get_lun ;open temporary file
IDL> printf,unit,n,x ;write data for fortran
IDL> free_lun,unit ;close file, release lun
IDL> spawn,'fexample2',result ;run program, capture output
IDL> y = float(result) ;convert strings to real*4
IDL> help,result
RESULT STRING = Array(10)
IDL> print,y
0.00000 1.00000 4.00000 9.00000 16.0000 16.0000 36.0000 49.0000 64.0000 81.0000
IDL> exit
integer*2 n
real*4 x(50), y(50)
end
soleil:idl% idl
IDL> n = 10 ;length of data vectors
IDL> x = findgen(n) ;init data to fortran
IDL> y = fltarr(n) ;init data from fortran
IDL> spawn,'fexample3',unit=unit ;run program, establish pipe
IDL> printf,unit,n,x ;write data to pipe
IDL> readf,unit,y ;read data from fortran
IDL> free_lun,unit ;close pipe, kill program
IDL> print,y
0.00000 1.00000 4.00000 9.00000 16.0000 16.0000 36.0000 49.0000 64.0000 81.0000
IDL> exit
======================== End of IDL Spawn Examples =========================
If these alternatives simply will not solve your problem, then it's time to discuss some of the difficulties involved in calling fortran externally from Unix IDL. Again, these comments are specific to SunOS 4.1.1, but may have more general applicability. Call_external is *not* available in IDL running under DEC's Ultrix operating system; use one of the alternatives described above.
The first problem is that IDL uses the standard C mechanism for passing arguments. Unfortunately, fortran is not flexible enough to handle arguments passed in this manner. So you will have to write a C interface to sit between IDL and your fortran routines and to convert between the two different argument formats. This C interface must be linked with your fortran program. IDL passes the number of arguments followed by the address of a list which contains the addresses of the data for each of the arguments. Fortran expects to recieve just a list of addresses of the data for each argument. I have successfully passed real*8 and integer*4 data types between IDL and fortran via a C interface. There may be problems passing other data types. Let me know if you are successful or even unsuccessful passing other data types.
Now comes the really tricky part. You have to compile your program and link it with the fortran library to make a "shared library", rather than a stand alone executable. This is what allows IDL to interface with your routine at execution time. Begin by compiling your programs (with f77 or cc as appropriate) with the -pic and -c flags. These options allow the code to be loaded anywhere in memory and supress automatic linking respectively. Now you must link the programs with the fortran libraries becasue IDL does not include these libraries (or even know where to find them). Normally, f77 automatically links your programs and the fortran libraries, but we supressed this behavior with the -c flag above because f77 would have linked our program as a stand alone executable.
To find your libraries, type f77 -v /dev/null from your unix prompt. On my system, I get the following output.
/bin/ld -dc -dp -e start -u _MAIN_ -X -o a.out /usr/lang/SC1.0/crt0.o
/usr/lang/SC1.0/cg87/_crt1.o -L/usr/lang/SC1.0/cg87 -L/usr/lang/SC1.0
/dev/null -lF77 -lm -lc
ld: /dev/null: premature EOF
Compilation failed
These are the options f77 uses to link your fortran program. The first modifications required for our purpose are to remove the -e start option and to remove the crt0.o and _crt1.o modules from the link list. This will force our output to be a shared object, rather than a stand alone program. I also had to include the math (/usr/lang/SC1.0/libm.a) and fortran (/usr/lang/SC1.0 /libF77.a) libraries as explicit link objects rather than simple library references as in the ld -v /dev/null example above. The -lc is uneccessary for our purpose, since IDL is already linked with the C library. (As a side note, I was unable to dynamically link at run time with the .so versions of these libraries.) In SunOS 4.1 the output file (specified with the -o option) should have the extension ".so". A sample link line is given in the Makefile in the example below.
The last step is to call your shared library from IDL. Make sure to prepend an underscore before the name of your C interface routine (e.g. _cinterface below). When you call your fortran routine from the C interface, you must both prepend and append underscores (e.g. _ftest_ below). It is essential that the data types you specify in IDL prior to the call_external be the types expected by your programs. Failure to heed this warning can lead to very cryptic error messages. As always in IDL, don't expect data to be returned in specifically referenced array elements. Use a scalar temporary variable instead.
Finally, here is an example that works for me. It's probably best if you begin with this example and slowly modify it to match your needs. Feel free to contact me for help or to give me corrections, additions, or comments.
=========================== Call_External Example ============================
Script started on Fri Sep 20 11:42:52 1991
soleil:idl% cat ftest.f
soleil:idl% cat cinterface.c
/*
Here we let our C interface know our fortran function is integer*4.
Perhaps the function could be defined as (void) for greater generality.
The symbol PROG will be assigned a value "_ftest_" in the Makefile.
Other fortran programs need only specify a different value of PROG.
*/
extern long PROG();
/*
Here we define the return value of our C interface as integer*4.
*/
int cinterface(argc, argv)
/*
Here we prepare for IDL's argument passing structure. The double could
perhaps be changed to void for greater generality.
*/
int argc;
double *argv[];
{
/*
This is currently set up for no more than 3 arguments, but can clearly
be extended by analogy to more arguments.
*/
switch (argc) {
case 0: return PROG(); case 1: return PROG(argv[0]); case 2: return PROG(argv[0], argv[1]); case 3: return PROG(argv[0], argv[1], argv[2]); default: (void) printf("Too many arguments.\n");
}
return -1;
}
soleil:idl% cat Makefile
# Note that PROG in cinterface.c will be replaced by _ftest_.
# Also note that the <TAB>s in makefiles like this are essential!
cinterface.o: cinterface.c Makefile
$(CC) -pic -c -DPROG=ftest_ cinterface.c
ftest.o: ftest.f
f77 -pic -c ftest.f
ftest: ftest.o cinterface.o
$(LD) $(LDFLAGS) -d -o ftest.so \
cinterface.o ftest.o /usr/lang/SC1.0/libF77.a /usr/lang/SC1.0/libm.a
soleil:idl% make ftest
soleil:idl% idl
IDL. Version 2.0.11 (sunos sparc). Copyright 1989, Research Systems, Inc.
All rights reserved. Unauthorized reproduction prohibited.
Site: 968.
Licensed for use by: UC Berkeley Astronomy
IDL> m=0L ;integer*4 result of call_external()
IDL> n=10L ;integer*4 as expected by ftest
IDL> x=dindgen(n) ;real*8 as expected by ftest
IDL> y=dblarr(n) ;real*8 as will be returned by ftest
IDL> m=call_external('ftest.so','_cinterface',n,x,y)
This is FORTRAN at work...
IDL> print,m
285
IDL> print,y
0.0000000 1.0000000 4.0000000 9.0000000 16.000000 25.000000 36.000000 49.000000 64.000000 81.000000
IDL> exit