WRAPIT
- Introduction to WRAPIT
- Steps for using WRAPIT
- Examples
- WRAPIT options
- Special considerations
- Troubleshooting common problems
Introduction to WRAPIT
If you have a Fortran function or procedure that you'd like to call from NCL, you can do it by "wrapping" this function using the WRAPIT script. WRAPIT works on many UNIX systems including Sun, Linux, AIX, and MacOSX. It does not currently work under Cygwin/Windows. This document does not cover how to wrap C code. For details on this, see the "Extending the NCL function and procedure set" section in the NCL Reference Manual.
In order to use WRAPIT, you must have a C compiler and a Fortran 77 or 90 compiler. WRAPIT will look for these compilers on your system and exit if it can't find them.
When you run WRAPIT on the Fortran code you want to call from NCL, it creates a special C wrapper file, compiles it and the Fortran file, and then generates a *.so file that you can then load into NCL using the "external" statement.
Steps for using WRAPIT
To use WRAPIT, you must follow these three steps:
- Step 1 - Write special wrapper text
- Step 2 - Run WRAPIT
- Step 3 - Load the shared object and call the routine
Step 1
Fortran 77 - Add special wrapper text to Fortran 77 code:
If you have a Fortran 77 routine, use the special interface delimiters "C NCLFORTSTART" and "C NCLEND" to bracket the argument declarations right within the Fortran code.
For example, assume you have a Fortran code called "ex01.f":
C NCLFORTSTART
subroutine cquad (a, b, c, nq, x, quad)
real x(nq), quad(nq)
C NCLEND
C
C Calculate quadratic polynomial values.
C
do 10 i=1,nq
quad(i) = a*x(i)**2 + b*x(i) + c
10 continue
return
end
C NCLFORTSTART
function arcln (numpnt, pointx, pointy)
dimension pointx(numpnt),pointy(numpnt)
C NCLEND
C
C Calculate arc lengths.
C
if (numpnt .lt. 2) then
print *, 'arcln: number of points must be at least 2'
stop
endif
arcln = 0.
do 10 i=2,numpnt
pdist = sqrt((pointx(i)-pointx(i-1))**2 +
+ (pointy(i)-pointy(i-1))**2)
arcln = arcln + pdist
10 continue
return
end
Only the argument variables should be included between the delimiters. If a particular variable is not typed, then the usual Fortran rules will apply for typing. Additionally, only the Fortran subroutine(s) actually called from NCL require the interface delimiters. The rest of the Fortran code may include other subroutines without delimiters.
Fortran 90 - Write special wrapper text as separate file:
If you have a Fortran 90 file, then you need to create a separate "stub" file that contains the "C NCLFORTSTART" and "C NCLEND" delimiters. For example, assume the above "cquad" routine was in a Fortran 90 file called "cquad.f90":
subroutine cquad(a,b,c,nq,x,quad) implicit none integer, intent(in) ::nq real, intent(in) ::a,b,c,x(nq) real, intent(out) ::quad(nq) integer ::i quad = a*x**2+b*x+c return end subroutine cquad
Create a separate file, called something like "cquad90.stub" that contains nothing more than the following lines:
C NCLFORTSTART
subroutine cquad(a,b,c,nq,x,quad)
real a,b,c
integer nq
dimension x(nq),quad(nq)
C NCLEND
Commercial library routine - Write special wrapper text as separate file:
If there's a commercial library routine you want to call from NCL, use the same method as with the Fortran 90 routine to create a separate "stub" file.
For example, assume you want to call the IMSL routine "rline" to fit a line to a set of data points via least-squares. The "rline" arguments are rline(nobs,x,y,b0,b1,stat) where "nobs" is the number of observations, "x" and "y" are the data vectors, "b0" and "b1" are the intercept and slope, and "stat" is a vector of length 12 containing assorted statistics. The Fortran stub file you create (call it "rline.stub") would look like:
C NCLFORTSTART
subroutine rline (n,x,y,b0,b1,stat)
integer n ! explicit typing NOT required
real x(n), y(n), b0, b1, stat(12)
C NCLEND
Fortran 77 - Run WRAPIT to compile the external code(s):
Run WRAPIT on your code to generate a shared object. It will have the same name as your Fortran file, except with ".so" appended instead of ".f" or ".f90. For the Fortran 77 example:
WRAPIT ex01.f
This should create a file called "ex01.so".
Fortran 90 - Run WRAPIT to compile the external code(s) and the separate wrapper text:
Using the "cquad90.stub" file you created in the previous step, and assuming "cquad.f90" is the Fortran 90 file that contains the "cquad" subroutine, then you would run WRAPIT as follows:
WRAPIT cquad90.stub cquad.f90
This should create a file called "cquad90.so".
To build a shared object that includes a call to one or more commercial library routines, you must include the list of commercial libraries on WRAPIT command line so the shared object will be properly linked:
WRAPIT -l imsl_mp rline.stub
This should create a file called "rline.so". Note that if WRAPIT gives you an error about being unable to find the commercial library, then you may need to include the "-L" option along with the path to the library:
WRAPIT -L /opt/lib -l imsl_mp rline.stub
Note for all types of routines:
If WRAPIT does not appear to work, then you can modify the WRAPIT script directly ($NCARG_ROOT/bin/WRAPIT) and change the paths to your appropriate local compilers. WRAPIT does not contain paths to any libraries, so they may have to be added depending upon your system.
Step 3 - Call the shared object from an NCL
script
Before you can call external subroutines from your NCL script, you need to load the shared object you created in Step 2. There are two ways to load a shared object:
Loading the shared object using external:At the top of your NCL script and before the "begin" statement, add an "external" statement to load the shared object, followed by a name you want to give the shared object, followed by the path to the shared object in double quotes.
The name of the shared object is arbitrary, but by convention, is capitalized. If the shared object is in a different directory, be sure to include the path to it (you can use relative or absolute paths). If the shared object is in the same directory as your script, be sure to put a "./" in front of it.
Now, to call any one of your functions or procedures from your NCL script, precede it with two colons ("::") and the name you gave the shared object. The example below is from the Fortran 77 example, and assumes the shared object "ex01.so" is in the same directory as your script:
external EX01 "./ex01.so"
begin
;
; Calculate three values of a quadratic equation
;
nump = 3
x = (/ -1., 0.0, 1.0 /)
qval = new(nump,float)
EX01::cquad(-1., 2., 3., nump, x, qval) ; Call the NCL version of
; your Fortran subroutine.
print("Polynomial value = " + qval) ; Should be (/0,3,4/)
;
; Calculate an arc length.
;
xc = (/ 0., 1., 2. /)
yc = (/ 0., 1., 0. /)
arclen = EX01::arcln(nump,xc,yc) ; Call the NCL version of
; your Fortran function.
print("Arc length = " + arclen) ; should be 2.82843
end
Loading the shared object using an environment variable:
Create a directory on the machine you wish to run NCL, and put your shared object(s) in this directory. Set the environment variable NCL_DEF_LIB_DIR to this path. For example:
setenv NCL_DEF_LIB_DIR /home/haley/shared_objects
NCL will recognize the path given by the NCL_DEF_LIB_DIR environment variable as another place to look for shared objects. You can include multiple directory paths by separating them with colons:
setenv NCL_DEF_LIB_DIR /home/shea/shared_objects/:/home/haley/shared_objects
Now you can call the shared object just like a built-in NCL function:
begin
;
; Calculate three values of a quadratic equation
;
nump = 3
x = (/ -1., 0.0, 1.0 /)
qval = new(nump,float)
cquad(-1., 2., 3., nump, x, qval) ; Call the NCL version of
; your Fortran subroutine.
print("Polynomial value = " + qval) ; Should be (/0,3,4/)
;
; Calculate an arc length.
;
xc = (/ 0., 1., 2. /)
yc = (/ 0., 1., 0. /)
arclen = arcln(nump,xc,yc) ; Call the NCL version of
; your Fortran function.
print("Arc length = " + arclen) ; should be 2.82843
end
Be sure to see the section on special considerations for trouble shooting.
Examples
This section contains examples illustrating how to incorporate sample Fortran codes into NCL.
- Example 1 -- Fortran subroutine and function
- Example 2 -- Fortran embedded wrapit interface blocks
- Example 3 -- Fortran subroutine from a commercial library
- Example 4 -- Fortran subroutine with CHARACTER input and output arguments
- Example 5 -- Fortran subroutine with a 2-dimensional array; printing
Example 1 -- Fortran subroutine and function
Begin with the Fortran source (in file ex01.f):
SUBROUTINE CQUAD (A, B, C, NQ, X, QUAD)
REAL X(NQ),QUAD(NQ)
C
C Calculate quadratic polynomial values.
C
DO 10 I=1,NQ
QUAD(I) = A*X(I)**2 + B*X(I) + C
10 CONTINUE
C
RETURN
END
FUNCTION ARCLN (NUMPNT, POINTX, POINTY)
DIMENSION POINTX(NUMPNT),POINTY(NUMPNT)
C
C Calculate arc lengths.
C
IF (NUMPNT .LT. 2) THEN
PRINT *, 'ARCLN: Number of points must be at least 2'
STOP
ENDIF
ARCLN = 0.
DO 10 I=2,NUMPNT
PDIST = SQRT((POINTX(I)-POINTX(I-1))**2 +
+ (POINTY(I)-POINTY(I-1))**2)
ARCLN = ARCLN + PDIST
10 CONTINUE
C
RETURN
END
This first example follows in detail the three step process described above.
Step 1 - define the wrapit interface block.
Create a file ex01.stub that contains the following two wrapit interface blocks:
C NCLFORTSTART
SUBROUTINE CQUAD (A,B,C,NQ,X,QUAD)
REAL X(NQ),QUAD(NQ)
C NCLEND
C NCLFORTSTART
FUNCTION ARCLN (NUMPNT,POINTX,POINTY)
DIMENSION POINTX(NUMPNT),POINTY(NUMPNT)
C NCLEND
Step 2 - run WRAPIT
WRAPIT ex01.stub ex01.f
Step 3 - tell NCL where your shared object is.
The external statement in the following example script tells NCL where to look for the dynamic shared object you just created.
external EX01 "./ex01.so"
begin
;
; calculate three values of a quadratic equation
;
nump = 3
x = (/ -1., 0.0, 1.0 /)
qval = new(nump,float)
EX01::cquad(-1, 2, 3, nump, x, qval) ; call the new NCL version of
; your original Fortran subroutine
print("Polynomial value = " + qval)
;
; calculate an arc length.
;
xc = (/ 0., 1., 2. /)
yc = (/ 0., 1., 0. /)
arclen = EX01::arcln(nump,xc,yc) ; call the new NCL version of
; your original Fortran function
print("Arc length = " + arclen)
end
If you submit the above script to the NCL interpreter, it produces the output:
opening: ./ex01.so (0) Polynomial value = 0 (1) Polynomial value = 3 (2) Polynomial value = 4 (0) Arc length = 2.82843
The numbers in parentheses at the left in the above printout are an artifact of how the NCL print function works and are of no relevance in this example.
Example 2 -- Fortran embedded wrapit interface blocks
Instead of using a separate stub file as in example 1 above, you could have used the following code (in file ex02.f) as input to WRAPIT:
C NCLFORTSTART
SUBROUTINE CQUAD (A,B,C,NQ,X,QUAD)
REAL X(NQ),QUAD(NQ)
C NCLEND
C
C Calculate quadratic polynomial values.
C
DO 10 I=1,NQ
QUAD(I) = A*X(I)**2 + B*X(I) + C
10 CONTINUE
C
RETURN
END
C NCLFORTSTART
FUNCTION ARCLN (NUMPNT, POINTX, POINTY)
DIMENSION POINTX(NUMPNT),POINTY(NUMPNT)
C NCLEND
C
C Calculate arc lengths.
C
IF (NUMPNT .LT. 2) THEN
PRINT *, 'ARCLN: Number of points must be at least 2'
STOP
ENDIF
ARCLN = 0.
DO 10 I=2,NUMPNT
PDIST = SQRT((POINTX(I)-POINTX(I-1))**2 +
+ (POINTY(I)-POINTY(I-1))**2)
ARCLN = ARCLN + PDIST
10 CONTINUE
C
RETURN
END
In this example, the wrapit interface blocks are embedded directly into the Fortran code, thus avoiding the need to create a separate stub file containing them. All that WRAPIT is looking for in its input is blocks delimited by comment lines containing NCLFORTSTART and NCLEND.
Now execute:
WRAPIT ex02.f
and proceed with step 3.
Example 3 -- Fortran subroutine from a commercial library
Suppose you are calling:
SUBROUTINE LIBSUB(IARG1,RARG)
from a commercial library named libcommercial.a. Using the following wrapit interface block (in file libsub.stub):
C NCLFORTSTART
SUBROUTINE LIBSUB(IARG,RARG)
INTEGER IARG
REAL RARG
C NCLEND
To create a dynamic shared object named libsub.so, use WRAPIT:
WRAPIT libsub.stub -l commercial
Example 4 -- Fortran subroutine with CHARACTER input and output arguments
Start with a Fortran subroutine that takes an input string and returns a number of letters based on the length of the input string:
SUBROUTINE EX04 (STRIN,STROUT)
CHARACTER*(*) STRIN
CHARACTER*26 ABET,STROUT
DATA ABET/'ABCDEFGHIJKLMNOPQRSTUVWXYZ'/
C
IMX = MIN(LEN(STRIN),26)
STROUT = ABET(1:IMX)
C
RETURN
END
You could use embedded wrapit interface blocks, as in example 2 above, or use the following wrapit interface block:
C NCLFORTSTART
SUBROUTINE EX04 (STRIN,STROUT)
CHARACTER*(*) STRIN
CHARACTER*26 STROUT
C NCLEND
to create the NCL wrapper and dynamic shared object (named ex04.so). Passing the following NCL script to the NCL interpreter:
external EXAMPLE04_SO "./ex04.so"
begin
cstr = new(26,character) ; create a character array of length 26
EXAMPLE04_SO::ex04("fifteen letters",cstr)
str = chartostring(cstr)
print(str)
end
produces the output:
opening: ./ex04.so
Variable: str
Type: string
Total Size: 4 bytes
1 values
Number of Dimensions: 1
Dimensions and sizes: [1]
Coordinates:
(0) ABCDEFGHIJKLMNO
Example 5 -- subroutine with a 2-dimensional array; printing
Start with one subroutine that calculates a function of two variables and stores the results in a 2-dimensional array, and another subroutine that prints 2-dimensional arrays by rows.
SUBROUTINE EX05(M,N,X,Y,FXY)
REAL X(M),Y(N),FXY(M,N)
C
C Calculate FXY(I,J) = 2*I+J
C
DO 10 J=1,N
DO 20 I=1,M
FXY(I,J) = 2.*REAL(I) + REAL(J)
20 CONTINUE
10 CONTINUE
C
RETURN
END
SUBROUTINE PRT2D(M,N,A)
REAL A(M,N)
C
C Print the array A by rows using an F6.1 format with
C 7 values per line.
C
DO 10 J=1,N
PRINT *,'Row',J,':'
DO 20 I=1,M/7
WRITE(6,500) (A(LL,J),LL=(I-1)*7+1,I*7)
500 FORMAT(7F6.1)
20 CONTINUE
IF (MOD(M,7) .NE. 0) WRITE(6,500) (A(LL,J),LL=(M/7)*7+1,M)
PRINT *,' '
10 CONTINUE
C
RETURN
END
Use the following wrapit interface block:
C NCLFORTSTART
SUBROUTINE EX05(M,N,X,Y,FXY)
REAL X(M),Y(N),FXY(M,N)
C NCLEND
C NCLFORTSTART
SUBROUTINE PRT2D(M,N,A)
REAL A(M,N)
C NCLEND
to create the NCL wrapper function and the dynamic shared object (named ex05.so). Then the following NCL script:
external EX05 "./ex05.so" begin ; ; calculate three values of a quadratic equation ; m = 11 n = 3 x = new(m,float) y = new(n,float) fxy = new((/n,m/),float) EX05::ex05(m,n,x,y,fxy) EX05::prt2d(m,n,fxy) end
will create the 2-dimensional array fxy in a manner compatible with other NCL procedures. Passing the above NCL script to the NCL interpreter produces the output:
opening: ./ex05.so
Row 1:
3.0 5.0 7.0 9.0 11.0 13.0 15.0
17.0 19.0 21.0 23.0
Row 2:
4.0 6.0 8.0 10.0 12.0 14.0 16.0
18.0 20.0 22.0 24.0
Row 3:
5.0 7.0 9.0 11.0 13.0 15.0 17.0
19.0 21.0 23.0 25.0
WRAPIT options
There are some command line options you can use with WRAPIT that are useful for debugging. These options must appear on the WRAPIT command line before the Fortran file names:
- -d
- Turns on array bounds, turns off optimization,
displays some debug information, and prevents file clean up.
- -g95
- Use the g95 compiler. (Version 4.3.0 or later.)
- -gf
- Use the gfortran compiler. (Version 4.3.0 or later.)
- -h or -help
- Gives you LOTS of information about WRAPIT, including
information about other options.
- -in
- Use the Intel compiler.
- -l <libname>
- Passes a library name to the linker.
- -L <libpath>
- Passes a directory path to the linker.
- -lf
- Use the Lahey compiler.
- -n <so name>
- Assigns a name to the created shared object.
- -pg
- Use the Portland compiler.
- -q32
- Specifies 32-bit IRIX or AIX bit precision (the default
is to use 64-bit precision, if available).
- -r8
- Promotes Fortran floats of real*4 to real*8 (if available).
Special considerations
This section contains several things that you should know to avoid common problems.
- Variable names:
- Due to a bug in the parser, no variable between the NCLFORTSTART
and NCLEND delimiters can be named "data".
- Array dimensions:
- You can't have dimension subscripts with arithmetic operators:
C NCLFORTSTART subroutine subby (X,Y,Z,N1,N2) integer N1,N2 real X(N1),Y(N2),Z(N1+N2) C NCLENDTo fix this, have the calling routine pass in an additional variable that is equal to "N1+N2", and use this instead:C NCLFORTSTART subroutine subby (X,Y,Z,N1,N2,N1N2) integer N1,N2 real X(N1),Y(N2),Z(N1N2) C NCLEND - Array indexing:
- You can't wrap code that has array indexing in the variable
declaration:
C NCLFORTSTART subroutine subbee (X,Y,N) integer N real X(0:N),Y(0:N) C NCLEND ...To fix this, you can create a driver program with the same name (rename the original routine to something else):C NCLFORTSTART subroutine subbee (X,Y,N1) integer N1 real X(N1),Y(N1) C NCLEND call subbee1(X,Y,N1-1) END subroutine subbee1 (X,Y,N) integer N real X(0:N),Y(0:N) ... - Array dimensioning:
- For NCL arrays, the fastest-varying dimension is the rightmost,
while for Fortran it is the leftmost dimension. Therefore, if
XA is a Fortran array dimensioned idim x
jdim, this array will be dimensioned jdim x
idim in NCL. Also, Fortran array subscripts start at 1,
whereas NCL array subscripts start at 0. Example 5 in this section illustrates
these concepts.
- Arrays of character strings:
- Currently, the wrapper code used by WRAPIT honors only
non-dimensioned Fortran type CHARACTER variables. You cannot pass
arrays of NCL strings to Fortran, nor can you pass Fortran CHARACTER
arrays from Fortran back to NCL.
- Passing strings from NCL to Fortran:
- If you want to pass an NCL variable of type string to a Fortran
procedure, then the argument to the Fortran procedure must be
declared as CHARACTER*(*). See example 4 in this section.
- Passing Fortran CHARACTER variables to NCL:
- If you want to pass a Fortran CHARACTER variable back to NCL, then
the Fortran argument must be a variable of type CHARACTER of
fixed length, and the corresponding NCL variable must be a
character array of the same length. If you want to use the NCL
character array as an NCL string, you will need to use the NCL
conversion function chartostring. See example 4 in this section.
- Complex numbers:
- NCL does not have a complex data type. If you want to bring
complex numbers into NCL, you will have to do it by bringing in the
real and imaginary parts as separate arrays. This will most likely
require that you write an interface subroutine to your Fortran code
that splits up the Fortran COMPLEX numbers into real and imaginary
parts. Although you will not be able to do arithmetic on the complex
numbers in NCL, you can still do analysis on the real and imaginary
parts separately.
- Procedure name conflicts:
- If the procedure that you are incorporating into NCL has the same
name as a currently existing
built-in NCL procedure, NCL will choose its built-in and not
your procedure. However, most UNIX ld commands recognize the
-B symbolic flag, and using it when you create your dynamic
shared object will force NCL to load your procedure in preference to
its own built-ins. The -B symbolic can cause ld to
report missing entries that in fact are ultimately not missing. It is
probably best just to be careful to avoid defining a procedure with
the same name as an NCL built-in.
- NCL termination:
- If a Fortran procedure that you have incorporated into NCL
executes a STOP statement, or if a C function executes an
exit statement, then the NCL interpreter will abort.
- Unsupported Fortran 77 syntax in
wrapit interface blocks:
- Fortran COMMON blocks:
- Fortran COMMON blocks are not allowed in a wrapit interface block. This would preclude your having adjustable arrays whose dimensions are passed in a COMMON block, or using COMMON to pass values for variables.
- Fortran ENTRY statements:
- There is no way to accommodate an ENTRY statement in a Fortran procedure.
- Alternate return arguments:
- Subroutines with alternate return arguments are not allowed.
Troubleshooting common problems
Before you start troubleshooting WRAPIT, please first try this simple test:
- Download ex.f and ex.ncl.
- Type:
WRAPIT ex.f ncl ex.ncl
If there were other options you were including on the WRAPIT command line, go ahead and include these.This test should produce the following output:
(0) before i = 5 (0) before x = 1.3 (0) after i = 10 (0) after x = -11.045
- If you don't get this output then there may be something
wrong with your version of WRAPIT or ncl. Please email
Mary Haley and send her
all the output from running the above commands, along with
what "uname -a" reports on your system.
- If the output from the above test looks good. then please
review the following section to see what might be wrong with using
WRAPIT on your own Fortran file.
ncl myscript.ncl warning:An error occurred loading the external file ./besi0.so, file not loaded ./besi0.so: undefined symbol: xermsg_This can possibly mean one of two things:
- The Fortran routine you are trying to wrap has a direct call to
"xermsg" (or "XERMSG") in it, but WRAPIT can't find the file or the
library where this subroutine is defined.
- This subroutine is being called internally by your compiler, and
WRAPIT needs some help in finding the library that this symbol is
defined in.
To link to a Fortran routine with this symbol, include the *.f file on the WRAPIT line after the *.f file you are wrapping:
WRAPIT myfile.f myotherfile.f
To link to a library that has this symbol defined, you will need to use the "-l" option, and possibly the "-L" option to tell WRAPIT where the library is located. For example, if the symbol is in the library "libfoo.a", and "libfoo.a" is in "/home/foo/lib", then include the following at the end of your WRAPIT command line:
WRAPIT myfile.f -L /home/foo/lib -l foo
If your situation fits the second case, you will need to find out which system library contains this symbol, and use the information in the previous step about using "-L" and "-l" to link in an additional library.
To find out what system library a symbol resides in is not always trivial. You can try the "locate" command which may not be available on all systems:
locate symbol_name
If you don't have the "locate" command, then you can try googling the symbol, or using a combination of "nm" and "grep" to look for the symbol. If you have a system administrator to ask about this, I highly recommend doing this first. Otherwise, you will need to search for the "lib*.a" files on your system, and then run "nm" and "grep":
nm libxxxx.a | grep symbol_name
If the symbol exists in your library, the "nm" command will produce output that looks like one of these lines (note: the output is different for different types of systems):
00000000 T symbol_name
[5] | 0| 8|FUNC |GLOB |0 |2 |symbol_name