2

Problem statement

The main part of my code is in C (called from Python). The C-part calls functions written in Fortran. Possible errors are propagated using an error-code and an error-string with a description of the error.

The problem is that I cannot seem to get the correct interface to write the string in Fortran and read/copy/manipulate it in C. The code below outlines what I want to do, the comments with marked with * ... * indicate where extensions are needed.

C

// global variable: read from Python if an error is encountered
char* error_string;

// template for the Fortan-subroutine
void fortran_calculation_( double* , int* );


int heavy_calculation( double* x )
{

  int error_code;


  // ... some code ...


  // * should accept and write "error_string" *
  fortran_calculation_( x , &error_code );

  if ( error_code ) 
  {
    error_string = "TO BE WRITTEN BY FORTRAN > REMOVE!!";
    return 1;
  }


  // ... some code ...


  return 0;

}

Fortran

subroutine fortran_calculation_(x,error_code)

implicit none

! * include "error_string" as argument *
real*8  :: x
integer :: error_code


! ... some code ...


if ( ... ) then
  ! * write "error_string" *
  error_code = 1
  return
end if


return 
end subroutine

I've tried many things, but I cannot seem to get it working...

Tom de Geus
  • 5,625
  • 2
  • 33
  • 77
  • 1
    Does your error string need to be global, or can it be passed as an argument to your Fortran routine? – Gilles Sep 22 '15 at 07:27
  • For passing around strings, also see this question: http://stackoverflow.com/q/9972743/577108 – haraldkl Sep 22 '15 at 07:36
  • Can also be passed as argument to the Fortran routine, but in the end the ``error_string`` pointer should point to the string. This has to do with how I read it from Python – Tom de Geus Sep 22 '15 at 08:45

2 Answers2

4

You have two problems. One, how to access a C global variable from Fortran. This one is relatively straightforward, create an interface in a module with iso_c_binding. See https://gcc.gnu.org/onlinedocs/gfortran/Interoperable-Global-Variables.html for an example.

However, the trickier problem is that you have defined your error_string as a pointer to char. That means that your Fortran code must allocate the string before writing to it. The Fortran allocatable and pointer variables work with descriptors, not raw pointers, so you must first create an interface to the C malloc function. Only after that you can write to it. Something like:


module my_error_string
  use iso_c_binding
  interface
     type(c_ptr) function c_malloc(size) bind(C, name="malloc")
       use iso_c_binding
       integer(kind=c_size_t), value :: size
     end function c_malloc
  end interface
  type(c_ptr), bind(C) :: error_string

contains
  subroutine write_error(str)
    character(len=*) :: str
    character, pointer :: fstr(:)
    integer(c_size_t) :: strlen
    integer :: i

    strlen = len(str, kind=c_size_t) + 1_c_size_t
    error_string = c_malloc(strlen)
    if (.not. c_associated(error_string)) then
       call perror("error_string is a null pointer => malloc failed?!")
       stop 1
    end if
    call c_f_pointer(error_string, fstr, shape=[strlen])
    do i = 1, len(str)
       fstr(i) = str(i:i)
    end do
    fstr(strlen) = c_null_char
  end subroutine write_error
end module my_error_string

(It might be simple to change the interface such that you instead pass an allocated C string to the Fortran function to fill in, or perhaps use a callback function. But the above works, if that's what you want.)

janneb
  • 36,249
  • 2
  • 81
  • 97
  • Thanks for your solution! In principle this is more elegant, but I think I will use @Gilles 's solution as it results in a simpler Fortran code. This is a practical choice which has mostly to do with how I reuse the Fortran part of my code. – Tom de Geus Sep 22 '15 at 08:42
  • @Tom: Fixed example so it now actually works. But yeah, string handling across the C-Fortran boundary is tricky no matter how you want to do it. – janneb Sep 22 '15 at 09:01
2

Here is a shamefully ugly "solution" to your problem, based on the design you provided.

main.c:

#include <stdio.h>
#include <string.h>

char error_string_[512];

void fortan_calculation_( double*, int*, int* );

int main() {
   double d = 2.5;
   int l, i = 3;
   memset( error_string_, 0, 512 );

   fortan_calculation_( &d, &i, &l );
   error_string_[l] = 0;
   printf( "After call: '%s'\n", error_string_ );

}

error.f90:

subroutine fortan_calculation( d, i, l )
    implicit none
    character(512) str
    common /error_string/ str
    double precision d
    integer i, l

    str = "Hello world!"
    l = len_trim( str )
end subroutine fortan_calculation

Compilation and test:

$ gcc -c main.c
$ gfortran -c error.f90
$ gcc main.o error.o -lgfortran
$ ./a.out 
After call: 'Hello world!'

But that is just disgusting code: it assumes a lot of (arguably) common practices for Fortran compilers, whereas it exists some ways of linking properly C and Fortran using the iso_c_binding Fortran module.

I'll have a look and see if I can come up with a proper solution to that.


EDIT: actually, there's a nice SO page about that available.

Community
  • 1
  • 1
Gilles
  • 9,269
  • 4
  • 34
  • 53
  • In spite of its ugliness it does exactly what I wanted, thanks! One benefit is that there is no "cross-dependency". What I didn't say is that I use the Fortran code also as part of a native Fortran code. – Tom de Geus Sep 22 '15 at 08:39