Fortran interface to call a C function that returns a pointer to an array

a 夏天 提交于 2021-01-23 11:05:55

问题


After much searching, I found what I believe to be the closest answer to my problem is on Stack Overflow (SO) at Fortran interface to call a C function that return a pointer, (posted nearly 10 years ago!)

I quote this because using that example keeps the code simple and still illustrates my problem.

I want to return an array that has been created/memory allocated in C++ and be able to analyse the answer in Fortran, because that is where the bulk of the code for this application lies. My application goes off into C++ to produce the integer array answer and returns it to the Fortran program via the C interface. The original SO example used a single double precision variable as the return. I’ve changed it to integer because that is what I will be dealing with in my application. The example code (as changed) works.

I have highlighted with comments the changes that I have tried to make to return an array pointer, but I’ve run out of ideas. (I could say, “Oh for the bad old days when I could just equivalence an integer to an iarray(1) and go beyond the size of the array”, but I won’t. It’s good to have coding protections, but sometimes it gets frustrating.)

I am using Visual Studio 2017 and Intel Fortran parallel_studio_xe_2019_update5_composer.

My modified example of the original SO code:

! ps_test_pointers.f90

program foo
  use, intrinsic :: iso_c_binding, only : c_ptr,        &
                                          c_f_pointer,  &
                                          c_int
  implicit none
  type(c_ptr) :: c_p!(:) ! <-------
  integer(c_int), pointer :: f_p!(:) ! <-------

  interface
    function foofunc() bind(c)
      import :: c_ptr
      implicit none  
      type(c_ptr) :: foofunc!(:) ! <-------
    end function foofunc
  end interface

  c_p = foofunc()
  call c_f_pointer(c_p, f_p)
  print *, f_p

end program foo
// ps_test_pointersC.cpp : 'Subroutine' only.

extern "C" {

    int bar[3] = { 2, 3, 4 };
    
    int *foofunc() {
        return bar;
    }

}

As I said above, the code works, in the sense that it prints out the first element of the array (‘2’).

If I add the ‘(:)’ to the definition of f_p, the code compiles without error, but when I run it, the program fails with the run-time error: “forrtl: severe (408): fort: (7): Attempt to use pointer F_P when it is not associated with a target” at the line “call c_f_pointer(c_p, f_p)”.

I have tried declaring c_p as an array (“c_p(:)”), but I get the same error in the same place.

I have also tried calling c_p as an argument to a subroutine – still only using integers:

    ! ps_test_pointers.f90
    
    program foo
      use, intrinsic :: iso_c_binding, only : c_ptr,        &
                                              c_f_pointer,  &
                                              c_int
      implicit none
      type(c_ptr) :: c_p!(:) ! <-------
      integer(c_int), pointer :: f_p!(:) ! <-------
    
      interface
        subroutine foofunc(c_p) bind(c)
          import :: c_ptr
          implicit none  
          type(c_ptr) :: c_p!(:) ! <-------
        end subroutine foofunc
      end interface
    
      call foofunc(c_p)
      call c_f_pointer(c_p, f_p)
      print *, f_p
    
    end program foo
    
    // ps_test_pointersC.cpp : 'Subroutine' only.
    
    extern "C" {
    
        int bar[3] = { 2, 3, 4 };
        
        void foofunc(int *rtn) {
            rtn = bar;
        }
    
    }

but the created pointer in the C function never gets assigned to c_p on return (hence f_p is never defined).

Reading around the problem, I hope I’m not at the bleeding edge of compiler implementation and have exposed a problem between restrictions tightening but not coping with all the use cases!

Is there a solution to this?


回答1:


RE the subroutine approach, I think we probably need to declare c_p as int** (rather than int*) on the C/C++ side to get the address of bar via argument association (rather than function return value). So something like...

main.f90:

program foo
  use, intrinsic :: iso_c_binding, only : c_ptr,        &
                                          c_f_pointer,  &
                                          c_int
  implicit none
  type(c_ptr) :: c_p
  integer(c_int), pointer :: f_p(:)
  integer(c_int) :: nsize

  interface
    subroutine foosub( c_p, nsize ) bind(c)
      import :: c_ptr, c_int
      implicit none  
      type(c_ptr)    :: c_p    !<-- sends the pointer to c_p
      integer(c_int) :: nsize  !<-- sends the pointer to nsize
    end subroutine
  end interface

  call foosub( c_p, nsize )
  call c_f_pointer( c_p, f_p, [nsize] )

  print *, "nsize  = ", nsize
  print *, "f_p(:) = ", f_p(:)

end program

sub.cpp:

extern "C" {
    int bar[3] = { 2, 3, 4 };
    
    void foosub( int** rtn, int* nsize ) {
        *rtn = bar;
        *nsize = sizeof(bar) / sizeof(int);
    }
 }

Compile & run:

$ g++-10 -c sub.cpp
$ gfortran-10 -c main.f90
$ g++-10 main.o sub.o -lgfortran
$ ./a.out
 nsize  =            3
 f_p(:) =            2           3           4



回答2:


Your C function is returning a pointer scalar; you want to associate this target with a Fortran array. This means you have the declarations

type(c_ptr) :: c_p                  ! <- scalar address
integer(c_int), pointer :: f_p(:)   ! <- array to associate

In the call to c_f_pointer you specify the shape of the Fortran pointer array with another argument. However, in this case the Fortran side has no way of knowing how large the array returned by the C function is.

Consider:

use, intrinsic :: iso_c_binding
implicit none

type(c_ptr) :: c_p
integer(c_int), pointer :: f_p(:)

interface
    function foofunc() bind(c)
      import :: c_ptr
      implicit none  
      type(c_ptr) :: foofunc
    end function foofunc
end interface

c_p = foofunc()
call c_f_pointer(c_p, f_p, [3])
print *, f_p

end

If you don't like the magic number 3 you'll need to find some other way to get that number (as you would if this function were being called in a C world). You can have the length as an extra argument, as with roygvib's subroutine example, as a linkage associated extra variable, through a separate query call (such as how for a character array one may use strnlen), etc.

Alternatively, if you want to be very fancy and you have flexibility in the language interface, you can use "improved interoperability" features in a C subroutine to do Fortran memory management:

program foo
  use, intrinsic :: iso_c_binding, only : c_int

  implicit none
  integer(c_int), pointer :: f_p(:)

  interface
     subroutine foosub(f_p) bind(c)
       import c_int
       implicit none  
       integer(c_int), pointer, intent(out) :: f_p(:)
     end subroutine foosub
  end interface
    
  call foosub(f_p)
  print *, f_p
    
end program foo
#include "ISO_Fortran_binding.h"

int bar[3] = { 2, 3, 4 };
        
void foosub(CFI_cdesc_t* f_p) {
  CFI_index_t nbar[1] = {3};
  CFI_CDESC_T(1) c_p;

  CFI_establish((CFI_cdesc_t* )&c_p, bar, CFI_attribute_pointer, CFI_type_int,
                nbar[0]*sizeof(int), 1, nbar);
  CFI_setpointer(f_p, (CFI_cdesc_t *)&c_p, NULL);
}

You could also use an allocatable variable instead of a pointer variable if you prefer.

This approach is not available with a Fortran function because interoperable functions cannot have array, pointer or allocatable results.



来源:https://stackoverflow.com/questions/63412077/fortran-interface-to-call-a-c-function-that-returns-a-pointer-to-an-array

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!