问题
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