Bérenger
Bérenger

Reputation: 2758

Calling C code with in-memory data from Fortran

I have a complicated C++ object that I'd like to use in my Fortran code. In general , there is no problem in calling C++ code from Fortran (just need to provide a suitable interface with C linkage for instance).

However my problem here is that I want my Fortran calls to C++ to operate on what I would call a persistent object: a C++ object created by the first init function, and operated on by other C++ functions.

To be more specific, suppose I have the following C++ code

struct A {
    public:
      void do() { // do something on complicated stuff
    private:
      ... // complicated stuff
};

extern "C" {
    void* init_A() {
         A* a = new A();
         return reinterpret_cast<void*>(a);
    }

    void doSth(void* ptr_to_A) {
         A* a = reinterpret_cast<A*>(ptr_to_A);
         a.do();
    }

    void teardown_A(void* ptr_to_A) {
         A* a = reinterpret_cast<A*>(ptr_to_A);
         delete a;
    }
}

And the following fortran code (suppose it is main() ):

USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
INTERFACE
    TYPE(C_PTR) FUNCTION init_A() BIND(C, NAME='init_A')
        USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
        IMPLICIT NONE
    END FUNCTION init_A

    SUBROUTINE doSth(ptr_to_A) BIND(C, NAME='doSth')
        USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
        IMPLICIT NONE
        TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A
    END SUBROUTINE doSth

    SUBROUTINE teardown_A(ptr_to_A) BIND(C, NAME='teardown_A')
        USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
        IMPLICIT NONE
        TYPE(C_PTR), INTENT(IN), VALUE  :: ptr_to_A
    END SUBROUTINE teardown_A
END INTERFACE

Now in my real code, this compiles, links, and sometimes works, but sometimes not: it seems like the memory allocated in init_A() is not garanteed to be left unchanged by the Fortran code)

I wasn't able to find anything about that on the Internet:

Also, can someone explain me why the memory is not managed correctly ? Until now, I thought that

Edit : I updated my code with the answer of IanH, but this is still not working (segfaults, portions of memory are deallocated while calling doSth() from Fortran

The original code I posted is the following (for comments refering to it)

struct A {
    public:
      void do() { // do something on complicated stuff
    private:
      ... // complicated stuff
};

extern "C" {
    void init_A_(long* ptr_to_A) { // ptr_to_A is an output parameter
         A* a = new A();
         *ptr_to_A = reinterpret_cast<long>(a);
    }

    void doSth_(long* ptr_to_A) {
         A* a = reinterpret_cast<A*>(*ptr_to_A);
         a.do();
    }

    void teardown_A_(long* ptr_to_A) {
         A* a = reinterpret_cast<A*>(*ptr_to_A);
         delete a;
    }
}

And the Fortran code:

integer :: ptr_to_A

call init_A(ptr_to_A)

do i=1,10000
    call doSth(ptr_to_A)
enddo

call teardown_A(ptr_to_A)

Upvotes: 3

Views: 594

Answers (1)

IanH
IanH

Reputation: 21451

Fortran 2003 introduced C interoperability into the Fortran language. This language feature makes it much easier to write Fortran and C (and hence C++) source that can work together in a portable and robust way. Unless you are prevented from using this level of the language for other reasons, you should very much use this feature.

You have an issue with pointer indirection - whether the pointer to the C++ object is being stored in a long or a pointer to long (the operand to the casts in doSth_ and teardown_A_ should have a * before them). It depends on the C++ and Fortran compilers that you are using, but it is possible that you have a size mismatch between a C long, a C pointer and a Fortran default kind integer.

A modified example showing the approach using Fortran 2003's C interoperability feature below.

// C++
struct A {
    public:
      void do_something()
      {
         // ...
      }
    private:
      // ...
};

// Note no need for trailing underscore.
extern "C" {
    // Note pointer to pointer to void.
    void init_A(void** ptr_ptr_to_A) {
         A* a = new A;
         *ptr_ptr_to_A = reinterpret_cast<void*>(a);
    }

    void doSth(void* ptr_to_A) {
         A* a = reinterpret_cast<A*>(ptr_to_A);
         a->do_something();
    }

    void teardown_A(void* ptr_to_A) {
         A* a = reinterpret_cast<A*>(ptr_to_A);
         delete a;
    }
}


! Fortran 2003
  USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
  IMPLICIT NONE
  INTERFACE
    SUBROUTINE init_A(ptr_to_A) BIND(C, NAME='init_A')
      USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
      IMPLICIT NONE
      ! This argument is a pointer passed by reference.
      TYPE(C_PTR), INTENT(OUT) :: ptr_to_A
    END SUBROUTINE init_A
    SUBROUTINE doSth(ptr_to_A) BIND(C, NAME='doSth')
      USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
      IMPLICIT NONE
      ! This argument is a pointer passed by value.
      TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A
    END SUBROUTINE doSth
    SUBROUTINE teardown_A(ptr_to_A) BIND(C, NAME='teardown_A')
      USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR
      IMPLICIT NONE
      ! This argument is a pointer passed by value.
      TYPE(C_PTR), INTENT(IN), VALUE :: ptr_to_A
    END SUBROUTINE teardown_A
  END INTERFACE
  TYPE(C_PTR) :: ptr_to_A
  INTEGER :: i
  !****
  CALL init_A(ptr_to_A)
  DO i = 1, 100
    CALL doSth(ptr_to_A)
  END DO
  CALL teardown_A(ptr_to_A)
END

Upvotes: 8

Related Questions