Writing C++ Interfaces To FORTRAN Packages
Writing C++ Interfaces To FORTRAN Packages
H. P. Langtangen
Title
D. Calhoun
H. P. Langtangen
Communicated by
Contents
1 Introduction
2.1
2.2
2.3
2.4
2.5
2.6
2.7
Linkage . . . . . . . . . . . . . . . . . . . . . . . . . . .
Declaring FORTRAN subroutines in C++ program les
Calling the FORTRAN subroutine . . . . . . . . . . . .
Passing constants . . . . . . . . . . . . . . . . . . . . . .
Passing data arrays . . . . . . . . . . . . . . . . . . . . .
Data type compatibility . . . . . . . . . . . . . . . . . .
An Example: Solving a tridiagonal system . . . . . . . .
...
...
...
...
...
...
...
2
3
4
5
5
6
6
4 A complete example
12
5 Conclusions
16
17
18
19
@techreport{OSCA1998-3,
author = "D. Calhoun and H. P. Langtangen",
title = "Writing C++ Interfaces to FORTRAN Packages",
type = "Oslo Scientific Computing Archive ",
note = "URL: http://www.math.uio.no/OSCA; ISSN 1500-6050",
number = "\#{}1998-3",
year = "June 20, 1998",
}
ii
H. P. Langtangen2
Abstract
The report starts with a summary of the mechanics of calling FORTRAN from C++. Thereafter, a convention for writing C++ interfaces
to FORTRAN packages is described. In particular, we demonstrate
how to program user dened functions, required by the FORTRAN
package, as virtual functions in C++.
1 Introduction
Scientists and engineers have already realized the benets of object-oriented
programming in their code development. Due to computational eciency
reasons, object-oriented constructs are usually restricted to higher-level administration code, while the most CPU-time intensive computations take
place in low-level code involving standard loops and simple array data structures, which are easily reconginzed for optimization by the compiler. Such
program design opens up for the possibility of using existing high-quality
FORTRAN packages as the low-level code. C++ has emerged as the standard language for object-oriented programming in numerical applications.
Although the C++ syntax is in many respects quite dierent from FORTRAN, the two languages apply practically the same basic data types, memory addressing and calling conventions. For this reason, it is easy to make
use of existing FORTRAN code when developing modern object-oriented
numerical applications in C++.
This short report details brie
y the mechanics of how to call FORTRAN
from C++, and then goes in to detail on how to create a C++ "wrapper"
for existing FORTRAN code. One of the key issues in the latter is how to
1
Page 2
In order to accomodate the essential object-oriented feature of polymorphism, C++ allows the user to create several functions with the same name
[1]. These functions dier because they either 1) are dened in distinct
classes (usually in classes which are derived from a common base class) or
2) have argument lists, or "signatures", that dier in the type of data that is
passed to the function and returned from the function. Ultimately, however,
the C++ compiler must be able to distinguish these identically named functions, and does so by constructing internal names for these functions. While
the details are not important, the names are formed by encoding the information about the class in which the function is dened and the argument
list for the function. This dening of internal names for C++ functions is
often referred to as "name mangling".
When calling FORTRAN from C++, it is essential that names for the
FORTRAN subroutines, which will be prototyped in C++ header les, not
be mangled. To prevent the usual name mangling carried out by the C++
compiler, declarations of FORTRAN subroutines in C++ header les must
be identied as requiring either FORTRAN or C linkage. This is done by
using the extern "FORTRAN" or extern "C" wrapper:
// Use FORTRAN linkage. Not supported by all compilers
extern "FORTRAN" {
// Declare prototype for FORTRAN subroutine here
}
Page 3
// Use C linkage. Most commonly used.
extern "C" {
// Declare prototype for FORTRAN subroutine here
}
The reader should note that not all compilers support the FORTRAN
style linkage and so C style linkage is most commonly used.
2.2 Declaring FORTRAN subroutines in C++ program les
A discussion of how the FORTRAN subroutines should be declared ultimately involves describing how to call the FORTRAN subroutine from C++,
but for now, the two issues will be discussed separately. To prototype a FORTRAN subroutine in C++ header les, one must rst know that many (but
not all!) compilers require the use of an underscore sux on FORTRAN
subroutine names that are declared in C++. So, for example, if the user
wishes to declare the use of the Lapack FORTRAN subroutine SGTSV for
solving a tridiagonal system, the subroutine should actually be declared as
sgtsv . The use of lower-case in the name is important. Declaring the FORTRAN subroutine using the name SGTSV (or SGTSV) will cause the linker to
report that the subroutine SGTSV (or SGTSV) is unresolved.
The key dierence
between FORTRAN and C/C++ is that all arguments to subroutines in
FORTRAN are passed by reference, whereas in C/C++ data can be passed
to functions by value or by reference. In fact, since FORTRAN subroutines
have no formal output arguments, any results produced by the FORTRAN
subroutine are recorded on variables passed into the subroutine as input.
The C/C++ function on the other hand, will, without use of special syntacial structures such as pointers, only expect to operate on a local copy of
the data it requires. Any changes made to this data is lost once the function
goes out of scope. To accomodate this dierence in the way in which function arguments are handled by the two dierent languages, the user must
pass address locations of data to the FORTRAN subroutine, rather than
the data itself. This is accomplished by the use of either pointer variables or
references. For example, a FORTRAN subroutine ADD which adds two integers and stores the result in a third integer variable could have a prototype
which looks like
The argument list: Passing references to data.
extern "C"
{
void add1_(int* a, int* b, int* a_plus_b);
}
Page 4
parameters are input or output data and makes use of references instead of
pointers. By using the const X& for input data that is not to be changed by
the subroutine, and X& for variables on which output results are to recorded,
one makes the function prototype more informative. With this in mind, the
above prototype could be written like this in C++:
extern "C"
{
void add2_(const int& a, const int& b, int& a_plus_b);
}
Here, variables a and b are input data and are not to be changed, whereas
output variable a plus b should be changed by the subroutine.
It should be noted that by simply declaring parameters as const in the
C++ header les, the user is not protected from changes that the FORTRAN
subroutine may make to these variables. The FORTRAN compiler has no
way of knowing that variables passed to it have been declared as const and
will allow redenition of such variables. Moreover, any such redenition
will alter the original data, possibly destroying the integrity of the that
data. The prototyping done using the const declarator should be viewed as
documentation only.3
2.3 Calling the FORTRAN subroutine
Calling the FORTRAN subroutine from within a C++ program is straightforward, although it does depend on how the function was prototyped. For
example, the FORTRAN function add1 above can be called using the following short program:
void main()
{
int a = 1, b = 5, a_plus_b;
add1_(&a, &b, &a_plus_b);
cout << "Result is : " << a_plus_b << endl;
}
Recall that in C++, the compiler issues an error if a const variable is changed inside
a function.
Page 5
2.4 Passing constants
By using the new command, users can create dynamic arrays of data easily
in C++ (at run-time), and then pass this data to FORTRAN to be used as
input data or output data. For example, the following program can be used
to add to vectors together:
#include <iostream.h>
extern "C"
{ void addVec_(const float v1[], const float v2[], const int& n, float v3[]); }
// alternative:
// void addVec_(const float* v1, const float* v2, const int& n, float* v3); }
void main()
{
int n = 10;
float* v1 = new float[n];
float* v2 = new float[n];
float* v3 = new float[n];
for(int i = 0; i < n; i++) {
v1[i] = i; v2[i] = -i; }
addVec_(v1, v2, n, v3);
for(i = 0; i < n; i++)
cout << v3[i] << " ";
cout << endl;
}
Page 6
returns the pointer to the rst element of the underlying C array. This
pointer is essential for communicating with FORTRAN. Moreover, the number of entries in the C array is given from
int Vec::size()
Page 7
class TriDiagMatrix
{
private:
Vec main_diag, upper_diag, lower_diag;
public:
void solve (Vec& b)
{
// Solve a tridiagonal system Tx=b, in double precision.
// From Lapack User's Guide (page 153)
// SUBROUTINE DGTSV(N,NRHS,DL,D,DU,B,LDB,INFO)
// N
: Size of the matrix
// NRHS
: Number of right hand sides
// DL, D, DU : 3 diagonals
// B
: right hand side vector. WILL BE overwritten!
// LDB
: leading dimension of the array B.
// INFO
: Output information
// Call Lapack routine
int info;
dgtsv_(main_diag.size(),1,lower_diag.getPtr0(),main_diag.getPtr0(),
upper_diag.getPtr0(),b.getPtr0(),b.size(),info);
// process info if desired...
};
}
// additional utility functions...
Page 8
{
double a = 0, b = 1, result;
integrate_((CFunc_Ptr) userFunction, a, b, result);
cout << "Integral is : " << result << endl;
The user should note the use of the EXTERNAL declaration in the FORTRAN subroutine.
3.1 Passing member functions to a FORTRAN subroutine
Page 9
{
}
The above will work in many cases where the user does not have a
complicated inheritance tree to work with, and therefore is not working
with virtual functions. The function square may of course make use of any
instance variables or member functions in the class A.
3.2 Passing virtual functions to FORTRAN
Page 10
Here, the class MathFunctions is an abstract base class with a pure virtual
member function func. This function is dened in subclasses that are derived
from this class. The user creates instances of MathFunctions and passes
them to an instance of the class Integral, as the following code fragment
illustrates:
// C++ code fragment
void main()
{
// Integrate over the inteval (0,1).
Integral I(0,1);
// Create instance of subclass of MathFunctions, math_pow
double p = 2.0;
math_pow f1(p); // f1(x) = x^p
cout << " I(f1) = " << I(&f1) << endl;
The class MathFunctions has two essential tasks. One, it denes a common interface which can be called by the global function which is eventually
passed to the FORTRAN subroutine. Second, it must assign a value to a
global pointer to an instance of the class MathFunctions. With this in mind,
the denition of MathFunctions is quite simple. This time, we will avoid the
use of a global function pointer by using a static variable declared in the
class MathFunctions:
// More C++ code fragments
class MathFunctions
{
public:
friend class Integral;
virtual double func(double x) = 0;
static MathFunctions* MathFunctions_class_ptr;
protected:
virtual void init() { MathFunctions_class_ptr = this; }
};
The use of the static variable here only declares it as a member of the
class MathFunctions; it does not allocate any storage for the pointer. This
must be done in the following global statement:
Page 11
// Allocate storage for the static member of MathFunctions:
MathFunctions* MathFunctions::MathFunctions_class_ptr = NULL;
Here, there is not problem in calling func using the global pointer
MathFunctions class ptr
Page 12
4 A complete example
The above examples illustrate the main principles one needs to adhere to
when calling a FORTRAN subroutine from C++. In particular, it was
noted that virtual fuctions may be passed quite easily, with the help of
global "helper" functions, to a FORTRAN subroutine.
In some instances, however, it may not be desirable to seperate the class
which calls the FORTRAN subroutine, from the denition of the virtual
functions. The virtual functions may require data only available in the
functions which call the FORTRAN subroutines. The underlying principles
described in this section are essentially the same as that described in the
above approach, but the philosophy is slightly dierent.
To be more specic and illustrate the ideas further, we will now present
a toy package in FORTRAN, called SCL, for solving the simple problem
u + f (u) = 0; x 2 (0; L)
by the upwind nite dierence scheme for 0 < t T . The initial condition
reads u(x; 0) = g(x). Let u be the numerical approximation to u((i
1)x; kt), where x and t are the space and time step, respectively,
and i = 1; : : : ; n and k = 1; : : : ; T =t. The numerical algorithm then takes
the following form.
Set initial condition for u0.
Compute u from the explicit scheme
t f (u 1) f (u 1)
u =u 1
1
x
for i = 2; : : : ; n and k = 1; : : : .
t
k
i
k
i
k
i
k
i
k
i
k
i
We assume that the user must supply a function user u0 for setting the
initial conditions. Moreover, the specic form of the
ux function f (u) is
provided in another user dened function user flux. Calling the function
scheme results in computing u(x; T ).
subroutine u0 (u_prev, n)
integer n
double precision u_prev(n)
double precision function flux (u_value)
double precision u_value
subroutine scheme (u, u_prev, n, dx, dt, T, user_u0, user_flux, dbg)
integer n, dbg, i
double precision u(n), u_prev(n), dx, dt, T, user_flux
external user_u0, user_flux
Page 13
class SCL
{
protected:
double* u; double* u_prev;
double dt, dx, L, T;
int n;
public:
virtual void u0 () = 0;
virtual void flux (double& value) = 0;
static SCL* f2cpp;
// global pointer for virtual function calls
virtual void scan (); // read dt, dx etc., allocate u and u_prev, set f2cpp
void solveProblem (); // call FORTRAN function scheme
};
The C++ class SCL applied primitive C data structures. In a C++ program one will often use objects at higher abstraction levels. Let us now, for
demonstration purposes, apply high-level C++ abstractions for the computational grid, as well as for the scalar eld u at the current and previous
time level. The abstractions are taken from Dipack [2]. That is, u(; t) is
represented as a scalar, spatial, nite dierence eld of type FieldLattice.
Roughly speaking, a FieldLattice object contains a specication of a grid
and an array of the point values of the eld.
class FieldLattice
Page 14
Handle(GridLattice)
mesh;
Handle(ArrayGen(real)) vec;
public:
ArrayGen(real)& values() { return *vec; } // user's access to point values
// indexing functions
// interpolation functions etc.
};
class DpSCL
{
protected:
Handle(GridLattice)
Handle(FieldLattice)
Handle(FieldLattice)
real
public:
static DpSCL*
grid;
u;
u_prev;
dt, T;
//
//
//
//
We can understand why the u0 function does not need any arguments.
This is because the DpSCL class has all the data that are necessary in u0.
The purpose of u0 is to ll u prev with proper data.
Page 15
The scan function reads parameters, like n, L and t, and allocates the
data structures grid, u and u prev. The solveProblem function just calls the
scheme function in the FORTRAN package.
In the DpSCL class we also include a static pointer DpSCL* f2cpp that can
be used to invoke the user dened virtual functions anywhere in the code
(f2cpp is hence similar to a standard global variable). The f2cpp pointer
must be initialized in, e.g., the DpSCL::scan function (in that function it
should be set to point to the this variable).
To solve a specic problem, derive a subclass where you implement the
u0 and flux functions, and additional data structures (and a redened scan
function to initialize them) if desired. An example is given in appendix C.
4.3 The FORTRAN interface as seen from C++
The only FORTRAN function that is called by the C++ code is scheme.
This function must be prototyped in an extern "C" statement. As already
mentioned, user dened virtual functions need to be called from "shell"
functions with a plain C signature. Here we introduce two such shell functions; u0 i and flux i. These functions must take the same parameters as
expected in the FORTRAN routines user u0 and user flux and call the user
dened virtual functions in DpSCL by means of the DpSCL::f2cpp pointer. In
the function u0 i one can make use of the fact that the transferred u prev
array is identical to the internal array in the u prev eld in the DpSCL class.
That is why we do not need to transfer the array to DpSCL::u0.
The relevant extern "C" declarations, the denition of class DpSCL and
the denition of the shell functions are given in detail in appendix C. Here
we just illustrate the main ideas.
// make function pointers (needed as arguments in call to scheme):
typedef void (*u0_ptr) (double u_prev[], const int& n); // for u0_i
typedef double (*flux_ptr) (const double& u_value);
// for flux_i
extern "C"
{
// FORTRAN functions called from C++
double scheme_ (double u[], double u_prev[], const int& n,
const double& dx, const double& dt, const double& T,
u0_ptr user_u0, flux_ptr user_flux, const int& dbg);
Page 16
A particular set of initial conditions and
ux function can be implemented in class MyProblem:
#include <DpSCL.h>
class MyProblem : public DpSCL
{
public:
virtual void u0 ();
virtual real flux (real u_value);
};
void MyProblem:: u0 ()
{
u_prev->fill (0.0);
u_prev->values()(1) = 1;
}
// u_1^0 = 1
5 Conclusions
We have demonstrated that it is possible to write a general interface class to a
FORTRAN package. User dened functions for a particular application can
be implemented in a subclass of the interface class. Hence, dierent applications have short codes because they can reuse the interface and associated
data structures that are required by the FORTRAN package. Moreover, our
standard for writing applications makes it straighforward to integrate the
solvers in other object-oriented frameworks for scientic programming.
Page 17
There are numerous issues that have not been covered in this report. We
brie
y mention common blocks in FORTRAN, the new FORTRAN90 language which supports abstract data structures (but not virtual functions),
and casting of pointers to member functions of subclasses to member functions of the base class.
10
For test purposes we will choose f (u) = u and the initial condition u01 = 1,
u0 = 0, i = 2; : : : ; n. If we let t = x, the pointwise numerical solution
coincides with the analytical solution u(x; t) = 1 H (x t), where H () is
the Heaviside function. These choices of f and u(x; 0) are implemented in
the following user dened functions (le fuser.f):
i
subroutine u0 (u_prev, n)
integer n,i
double precision u_prev(n)
u_prev(1) = 1.0
do 10 i = 2,n
u_prev(i) = 0
Page 18
10
continue
end
double precision function flux (u_value)
double precision u_value
flux = u_value
return
end
A simple main program to test the software can look like this:
program upwind
integer n
external u0, flux
double precision L, dx, dt, u(1000), u_prev(1000), T
write(*,*) 'Give L: '
read(*,*) L
write(*,*) 'Give number of intervals: '
read(*,*) n
n is from now on used as the number of points:
n = n + 1
dx = L/float(n-1)
write(*,*) 'Give time step: '
read(*,*) dt
write(*,*) 'Give final time level: '
read(*,*) T
call scheme (u, u_prev, n, dx, dt, T, u0, flux, 1)
call dump (u, n, T)
end
Page 19
class DpSCL
{
protected:
Handle(GridLattice)
Handle(FieldLattice)
Handle(FieldLattice)
real
public:
static DpSCL*
grid;
u;
u_prev;
dt, T;
//
//
//
//
};
#endif
The body of the member functions of class DpSCL and the interface functions
in C are listed below.
#include <DpSCL.h>
DpSCL* DpSCL::f2cpp = NULL;
void FORTRANname(u0_i) (double u_prev[], const int& n)
{
Page 20
if (DpSCL::f2cpp == NULL)
errorFP("u0_i","the DpSCL::f2cpp is not initialized.\n"
"Set f2cpp to point to your simulator class!");
// assume that the C++ class has the u_prev array and its length
DpSCL::f2cpp->u0();
As a test example, one can compile the les scheme.f,4 DpSCL.h, DpSCL.cpp
and MyProblem.cpp, in the Dipack subdirectory with name C, using the
Dipack Make script. Just typing app should produce a grid with 10 intervals
and exact solution at the grid points.
4
Page 21
References
[1] J. J. Barton and L. R. Nackman: Scientic and Engineering C++ { An
Introduction with Advanced Techniques and Examples. Addison-Wesley,
1994.
[2] Dipack World Wide Web home page:
http://www.nobjects.com/Dipack, 1998