JaW.
JaW.

Reputation: 87

Fortran interoperability with C pointer struct

I have a commercial C library which I want to use with Fortran. There are two functions and a pointer struct like:

struct env;
typedef struct env *ENVptr;

the two functions have the prototype:

ENVptr open(int *status_p);
int close(ENVptr **env_p);

I wrote an Fortran interface to access them:

interface
    function c_open(status) bind(c, name='c_open')
        use, intrinsic :: iso_c_binding
        integer(kind = c_int) :: status
        type(c_ptr) :: c_open
    end function 

    function c_close(env) bind(c, name='c_close')
        use, intrinsic :: iso_c_binding
        type(c_ptr)                  :: env
        integer(kind = c_int)        :: c_close
    end function
end interface

I use this code to use this in my Fortran program:

type(c_ptr)  :: env = c_null_ptr

env = c_open(status)

if ( status .ne. 0 ) then
   print *, 'Could not open environment'
   stop
end if

...some more code...

if ( c_associated(env) ) then
   status = c_close(env)
    if ( status .ne. 0 ) then
       print *, 'Could not close environment.'
    end if
end if

but when I execute the program, I get an Segmentation fault error when the program reaches the c_close function.

Is this the right way to interface the C routines?

Upvotes: 1

Views: 797

Answers (1)

user5713492
user5713492

Reputation: 974

I don't see how your program could have linked because the binding name of a procedure has to agree with the name in the actual C prototype. I suppose you could square the names up with a *.def file. Also Fortran has a concept of argument keywords so it is good practice in my opinion to make the Fortran dummy arguments in the interface agree with their documented argument names. Other than that you seem to have the right level of indirection in your interface bodies, so my version would be:

interface
   function c_open(status_p) bind(C,name='open')
      use, intrinsic :: iso_c_binding
      implicit none
      type(c_ptr) :: c_open
      integer(kind = c_int) status_p
   end function c_open

   function c_close(env_p) bind(c,name='close')
      use, intrinsic :: iso_c_binding
      implicit none
      integer(c_int) c_close
      type(c_ptr) env_p
   end function c_close
end interface

Now, there is a problem with the level of indirection when you invoke c_close because the C typedef for ENVptr already makes it a pointer, so ENVptr** envp is a pointer to a pointer to a pointer. In your Fortran code you are passing a c_ptr that points at an opaque type by reference, so you are passing a pointer to a pointer. Thus you need to create an extra level of indirection to make it fly. I would accordingly attempt to modify your code to something like:

type(c_ptr)  :: env = c_null_ptr, envpp = c_null_ptr
target env
integer(c_int) status

env = c_open(status)

if ( status .ne. 0 ) then
   print *, 'Could not open environment'
   stop
end if

!...some more code...

if ( c_associated(env) ) then
   envpp = c_loc(env)
   status = c_close(envpp)
    if ( status .ne. 0 ) then
       print *, 'Could not close environment.'
    end if
end if

I can't test this obviously but at this point it's syntactically correct and has the right level of indirection according to my reading of your problem statement.

Upvotes: 1

Related Questions