Reputation: 33
I am trying to be able to pass a multidimensional Fortran array to a C++ program, in a C++ Fortran interoperating program. I have a basic idea of how passing the arrays from Fortran to C++ works; you pass a location of the array from Fortran to C++. Then C++ takes the flattened array and you have to do some algebraic calculation to find the element in a given multidimensional array.
I was able to successfully test this idea on a scalar array. It is not so hard to figure out the index of element in C++, because it is linearly mapped from Fortran index to C++ with offset of -1. Sample codes for Fortran and C++ are:
! Fortran main program
program fprogram
integer :: i
real*8 :: array(2)
array(1) = 1.0
array(2) = 2.0
! call cpp function
call cppfuncarray(array, 2)
write(*,*) array
end program
//cpp file
extern "C" {
void cppfuncarray_(double *array, int *imax);}
void cppfuncarray_(double *array, int *imax) {
int iMax = *imax;
for ( int i = 0; i < iMax; i++ ) {
array[i] = array + 1.0*i;
}
};
and the output will be array(1) = 1 and array(2) = 3. Now I am trying to pass multidimensional arrays such as A(2,2) or A(2,3,2) from Fortran to C++. I know that 2 dimensional array such as A(2,2) will be easy to figure out the flattened array in C++. But I reckon it might be a bit more challenging to locate elements in C++ for 3 or 4 dimensional arrays.
What is the correct way to construct a multidimensional array in C++ so that I can refer to elements in array A(k,j,i) in Fortran as A[i][j][k] in C++?
Thanks in advance!
Upvotes: 3
Views: 4023
Reputation: 7385
Casting a pointer to scalar (int* in the example below) to a pointer to (N-1)-dim array may be useful, although writing an array view class should be more flexible...
fortsub.f90:
subroutine fortsub()
implicit none
integer a(4,3,2) !! this line will be changed in the EDIT below
integer ndims(3), i
ndims(:) = [ ( size( a, i ), i = 1, 3 ) ]
call cppfunc( a, ndims )
print *, "a(1,1,1) = ", a(1,1,1) !! gives 10101
print *, "a(2,2,1) = ", a(2,2,1) !! gives 20201
print *, "a(4,3,2) = ", a(4,3,2) !! gives 40302
end subroutine
cppfunc.cpp:
extern "C" {
void fortsub_( void );
void cppfunc_( int *A, int *n )
{
typedef int (*A3d_t)[ n[1] ][ n[0] ];
A3d_t A3d = (A3d_t) A; // get a pointer to 2-dim subarray
// 3-dim access
for( int k = 0; k < n[2]; k++ )
for( int j = 0; j < n[1]; j++ )
for( int i = 0; i < n[0]; i++ ) {
A3d[ k ][ j ][ i ] = (i+1)*10000 + (j+1)*100 + (k+1); // set test data
}
}
}; // extern "C"
int main()
{
fortsub_();
return 0;
}
Compile:
$ g++ fortsub.f90 cppfunc.cpp -lgfortran # tested with gcc >=4.4.7
EDIT: Although this may be off-topic, I also tried passing an allocatable array or array pointer to the same cppfunc() routine. Specifically, I changed the declaration of a(4,3,2) above to one of the following:
!! case 1
integer, allocatable :: a(:,:,:)
allocate( a(4,3,2) )
!! case 2
integer, pointer :: a(:,:,:)
allocate( a(4,3,2) )
!! case 3 : an array view for contiguous memory
integer, target :: b(4,3,2,5)
integer, pointer :: a(:,:,:)
a => b( :, :, :, 5 )
!! case 4 : an array view for non-contiguous memory
integer, target :: c(5,4,3,2)
integer, pointer :: a(:,:,:)
a => c( 5, :, :, : )
When compiling with
$ g++ fortsub.f90 cppfunc.cpp -lgfortran -fcheck-array-temporaries
all the cases give the correct result. Only in case 4 the compiler creates an array temporary, pass the address of its first element to cppfunc(), and copies the obtained data back to the actual argument. Otherwise, the compiler passes the address of the first element of an actual array directly to cppfunc(), as in Fortran77.
EDIT 2: Some routines may want to receive an N-dim array as an array of pointers. In this case a more traditional approach will be something like this:
getptr3d.hpp:
template <typename T>
T*** get_ptr3d( T* A, int* n )
{
typedef T (*A3d_t)[ n[1] ][ n[0] ];
A3d_t A3d = (A3d_t) A;
T*** p = new T** [ n[2] ];
for( int k = 0; k < n[2]; k++ ) {
p[ k ] = new T* [ n[1] ];
for ( int j = 0; j < n[1]; j++ ) {
p[ k ][ j ] = (T*) &( A3d[ k ][ j ][ 0 ] );
}
}
return p;
}
template <typename T>
void free_ptr3d( T*** p, int*n )
{
for( int k = 0; k < n[2]; k++ ) { delete[] p[ k ]; }
delete[] p;
}
Modified cppfunc.cpp:
#include "getptr3d.hpp"
...
void cppfunc_( int* A, int* n )
{
int*** A3d = get_ptr3d( A, n ); // can also be used for double***
... // use A3d[ k ][ j ][ i ]
// or pass A3d to other functions like func( int*** B, ... )
free_ptr3d( A3d, n ); // when calculation finished
}
Upvotes: 7