Mixed language programming - C++11 and Fortran 2008
Posted on May 11, 2012 by Sol

The code for this tutorial is on GitHub: https://github.com/sol-prog/mix_fortran_cpp.

Different programming languages have different strengths and while you can express any imaginable algorithm in C++11 sometimes you need to interface your code with legacy codes written in Fortran, or you want to use modern Fortran for his rich matrix operations. This post will present some simple examples of mixed Fortran 2008 and C++11 code, as a side note it is entirely possible to write these examples in pure Fortran or C++, coding every operations from scratch in C++ or using matrix libraries like Eigen.

Mixing Fortran with C (or C++) was a painful experience in the past and the exact mechanism was compiler dependent. Starting with Fortran 2003 you can use a standardized mechanism for interoperability with C, calling Fortran from C and vice versa can be done using the iso_c_binding Fortran module.

Let’s start by writting a C++11 code that creates two N by N arrays filled with random numbers. In this post we use the column-major order storage (Fortran order) and not the typical row-major storage used in C or C++ for multidimensional arrays.

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include #include #include #include using namespace std; //Fill a vector with random numbers in the range [lower, upper] void rnd_fill(vector &V, double lower, double upper) { //use system clock to create a random seed size_t seed (clock()); //use the default random engine and an uniform distribution default_random_engine eng(seed); uniform_real_distribution distr(lower, upper); for( auto &elem : V){ elem = distr(eng); } } int main() { size_t N = 3; vector A(N * N), B(N * N); //Fill A and B with random numbers in the range [0,1]: rnd_fill(A, 0.0, 1.0); rnd_fill(B, 0.0, 1.0); return 0; } 

The iso_c_binding Fortran module defines numerical types compatible with C and C++ numerical types, for e.g. a C int is defined as c_int and a C double as c_double. We are going to implement a Fortran subroutine, callable from C, that multiplies two matrices filled with doubles. For matrix multiplication we are going to use a Fortran intrinsic function named matmul:

 1 2 3 4 5 6 7 subroutine matrix_multiply(A, rows_A, cols_A, B, rows_B, cols_B, C, rows_C, cols_C) bind(c) use iso_c_binding integer(c_int) :: rows_A, cols_A, rows_B, cols_B, rows_C, cols_C real(c_double) :: A(rows_A, cols_A), B(rows_B, cols_B), C(rows_C, cols_C) C = matmul(A, B) end subroutine matrix_multiply 

Please note the use of bind(c) on line 1 and iso_c_binding module on line 2, the actual matrix multiplication is done on line 6.

In order to be able to use the above Fortran subroutine in C we need to translate Fortran’s subroutine definition to a C function:

 1 subroutine matrix_multiply(A, rows_A, cols_A, B, rows_B, cols_B, C, rows_C, cols_C) bind(c) 

became:

 1 void matrix_multiply(double *A, int *rows_A, int *cols_A, double *B, int *rows_B, int *cols_B, double *C, int *rows_C, int *cols_C); 

Please note that every variable is declared as a pointer, this is because Fortran uses call by reference by default.

What about a C++ definition for matrix_multiply ? We can use the C definition wrapped in an extern “C” declaration:

 1 2 3 extern "C" { void matrix_multiply(double *A, int *rows_A, int *cols_A, double *B, int *rows_B, int *cols_B, double *C, int *rows_C, int *cols_C); } 

Now, we can modify our original C++ code in order to use matrix_multiply for multiplying A and B:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include #include #include #include using namespace std; //Fortran subroutine definition "translated" to C++ extern "C" { void matrix_multiply(double *A, int *rows_A, int *cols_A, double *B, int *rows_B, int *cols_B, double *C, int *rows_C, int *cols_C); } //Fill a vector with random numbers in the range [lower, upper] void rnd_fill(vector &V, double lower, double upper) { //use system clock to create a random seed size_t seed (clock()); //use the default random engine and an uniform distribution default_random_engine eng(seed); uniform_real_distribution distr(lower, upper); for( auto &elem : V){ elem = distr(eng); } } //Print matrix V(rows, cols) storage in column-major format void print_matrix(vector const &V, int rows, int cols) { for(int i = 0; i < rows; ++i){ for(int j = 0; j < cols; ++j){ std::cout << V[j * rows + i] << " "; } std::cout << std::endl; } std::cout << std::endl; } int main() { size_t N = 3; vector A(N * N), B(N * N), C(N * N); //Fill A and B with random numbers in the range [0,1]: rnd_fill(A, 0.0, 1.0); rnd_fill(B, 0.0, 1.0); //Multiply matrices A and B, save the result in C int sz = N; matrix_multiply(&A[0], &sz, &sz, &B[0], &sz, &sz, &C[0], &sz, &sz); //print A, B, C on the standard output print_matrix(A, sz, sz); print_matrix(B, sz, sz); print_matrix(C, sz, sz); return 0; } 

Please note the syntax used at line 50 for sending a C++ STL vector to our Fortran subroutine by reference.

The above codes can be compiled with gcc-4.7 on Linux and Mac or with Visual Studio and Intel Fortran on Windows.

If you save the Fortran code in fortran_matrix_multiply.f90 and C++ in cpp_main_1.cpp you can compile and link them on Unix:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 sols-MacBook-Pro:mix_fortran_cpp sol$gfortran-4.7 -c fortran_matrix_multiply.f90 sols-MacBook-Pro:mix_fortran_cpp sol$ g++-4.7 -c -std=c++11 cpp_main_1.cpp sols-MacBook-Pro:mix_fortran_cpp sol$gfortran-4.7 fortran_matrix_multiply.o cpp_main_1.o -lstdc++ -o mix_example.out sols-MacBook-Pro:mix_fortran_cpp sol$ ./mix_example.out 0.411975 0.886183 0.041323 0.130066 0.576865 0.507137 0.26862 0.938682 0.385561 0.464278 0.317101 0.3179 0.799268 0.0544081 0.744739 0.0202931 0.0940126 0.447147 0.900407 0.182738 0.809419 0.531748 0.120308 0.697726 0.882796 0.172499 0.956869 sols-MacBook-Pro:mix_fortran_cpp sol\$ 

On Windows open an Intel Fortran command prompt and write these two lines for compiling and linking the codes:

 1 2 ifort -c fortran_matrix_multiply.f90 cl fortran_matrix_multiply.obj cpp_main_1.cpp.cpp 

If you are interested in learning more about the new C++11 syntax I would recommend reading Professional C++ by M. Gregoire, N. A. Solter, S. J. Kleper 2nd edition:

or, if you are a C++ beginner you could read C++ Primer (5th Edition) by S. B. Lippman, J. Lajoie, B. E. Moo.

If you need to brush your Fortran knowledge a good book is Modern Fortran Explained by M. Metcalf, J. Reid and M. Cohen: