问题
I have the following Fortran subroutine named show_value
that calls a C function named show_value
:
INTERFACE
SUBROUTINE show_value(variable) BIND(C, name = "show_value")
USE, INTRINSIC :: iso_c_binding
TYPE(*) :: variable
END SUBROUTINE
END INTERFACE
The C function show_value
:
void show_value(const void *variable)
{
printf("%d\n", *(int *) variable);
}
The Fortran subroutine works well when passing scalars to it. Example:
INTEGER :: x
x = 12
call show_value(x)
This will call the C function show_value
and print 12
, which is correct.
Now, according to the Fortran documentation, if one wants to enable subroutine show_value
to receive also arrays (of any dimensions) and not just scalars, the line TYPE(*) :: variable
should be changed to TYPE(*), DIMENSION(..) :: variable
.
After making this change, when executing the following Fortran code:
INTEGER, DIMENSION(3) :: y
y(1) = 15
y(2) = 17
y(3) = 19
call show_value(y)
The C function show_value
no longer prints the correct message (i.e. prints random numbers). Moreover, I found out that the address that the C function receives is 528 lower than the original (in Fortran). To confirm this:
void show_value(const void *variable)
{
printf("%d\n", *(int *) (variable + 528));
}
... which prints 15
(correct number).
Any idea what is going here?
Environment: Ubuntu 14.04 64 bit, gfortran 4.9
回答1:
Although your first case, with scalar argument, works correctly matching with the void*
argument, when the argument to the call is assumed-rank (which type(*), dimension(..) :: variable
represents) the Fortran procedure is not interoperable with the C procedure with corresponding formal argument const void *variable
.
Instead, it is necessary to use the CFI_cdesc_t
mechanism:
#include <stdio.h>
#include <ISO_Fortran_binding.h>
void show_value(const CFI_cdesc_t* variable)
{
printf("%d\n", *(int*) variable->base_addr);
}
You can find details of this in Fortran 2018 18.5.3.
Essentially, however, this is a descriptor which has much of the detail of the Fortran entity. Here, base_addr
is the start of the data, but you'll also find allocatable/pointer/data status, rank, extents, type.
Alas, gfortran 4.9 doesn't support this. If it is supported at all, it will be in only very recent versions.
Alternatively, you may avoid using an assumed-rank assumed-type actual argument and pass instead the C-address of the argument using c_loc
. Not quite so elegant, but more widely supported:
use, intrinsic :: iso_c_binding, only : c_loc, c_ptr, c_int
interface
subroutine show_value(variable) bind(c)
import c_ptr
type(c_ptr), value :: variable
end subroutine
end interface
integer(c_int), target :: x, y(3)
x = 12
y = [15, 17, 19]
call show_value(c_loc(x))
call show_value(c_loc(y))
end
This, however, leaves the problem of how the C function knows what to do with the argument.
来源:https://stackoverflow.com/questions/51268567/passing-both-scalars-and-arrays-of-any-dimensions-from-fortran-to-c