Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Sharing Fortran Pointer to Allocatable Array with C (interoperability)

newfortranuser
Beginner
4,493 Views
Hi again.

I have a common block which looks like this:

common /mydata/ na, nb, a_ptr, b_ptr
integer :: na, nb
integer, pointer :: a_ptr(:) ! pointer to an allocatable array of integers
real, pointer :: b_ptr(:) ! pointer to an allocatable array of reals

The allocation and association of the arrays is handled dynamically in the Fortran program units which include the common block. The fortran code is compiled as a static library.

I need to access the data stored in a_ptr and b_ptr in my C code. The ISO_C_BINDING makes it easy to get the (dynamic) SIZE(a_ptr) and SIZE(b_ptr) (after first testing their association status of course!) but I'm confused about how to actually access the data from C. First, I don't see an intrinic type constant in ISO_C_BINDING for Fortran pointer types. There is the C_PTR derived type in ISO_C_BINDING, but I have not found an example of how to make it do what I expect; actuallythings like

real, pointer(C_PTR) :: a_ptr

don't even compile, so that must be the wrong way.

I hope I've made my intent clear. Accessing fixed-length arrays of intrinsic types is easy with ISO_C_BINDING. But I need to access deferred-size arrays of intrinsic types (only after they're nontrivially associated of course). I do not insist on using ISO_C_BINDING, but I'd like whatever I do to be standards-compliant way.

Thanks!
0 Kudos
1 Solution
IanH
Honored Contributor III
4,493 Views
test_assoc1 = C_NULL_PTR if (associated(a_ptr)) then test_assoc = c_loc(a_ptr(1)) endif return end function test_assocMy main problem was that I fogot that pointers *are* values and that I needed to declare the pointer passed from C as TYPE(C_PTR), VALUE. Of course then I got the friendly reminder that arguments passed by value cannot have INTENT(OUT) or INTENT(INOUT). So I finally realized that I would need to fecth the pointer via a function return value instead of an argument.

If I understand you correctly, then "needed" above isn't right. The equivalent of

[fortran]SUBROUTINE proc(the_size, the_ptr) BIND(C, NAME='proc')
...
  INTEGER(C_INT), INTENT(OUT) :: the_size
  TYPE(C_PTR), INTENT(OUT) :: the_ptr
[/fortran]

could be declared and called (assuming the pointer will point to "int" things):

[cpp]extern "C" void proc(int *the_size, int **the_ptr);

...

int my_size;
int *my_ptr;
proc(&my_size, &my_ptr);
if (!my_ptr) { ... }[/cpp]

Your fortran pointer's kind should be C_INT.

Having POINTERs in common is ok but a bit incongruous - why not put the entity in a module?

Things that are ALLOCATEd will be contiguous. But is there a reason that the pointer isn't an allocatable instead? That also then guarantees that someone won't go and point the pointer at something that breaks the C_LOC call (like something that isn't contiguous like an array section with stride, etc). C_LOC of an allocated allocatable array is explicitly allowed in F2003, while C_LOC of a pointer array might require F2008 to be safe.

There's no need for the subscript in the C_LOC call unless the pointer has a lower bound that isn't 1.

View solution in original post

0 Kudos
10 Replies
newfortranuser
Beginner
4,493 Views
(Replying to my own post.)

I have something which works. I will appreciate any critique.

My main problem was that I fogot that pointers *are* values and that I needed to declare the pointer passed from C as TYPE(C_PTR), VALUE. Of course then I got the friendly reminder that arguments passed by value cannot have INTENT(OUT) or INTENT(INOUT). So I finally realized that I would need to fecth the pointer via a function return value instead of an argument. So that eliminated the need to pass in a pointer in the first place. Instead, I just cast return the TYPE(C_PTR) to a prototype declared void* and cast the pointer to the known C type. Here we go:

On the Fortran side we have

[fortran]integer(C_INT) function test_getdim() bind(C)
  use, intrinsic  :: ISO_C_BINDING
  implicit none
  
! This COMMON block gets reused in several places common /mydata/ na, a_ptr integer :: na integer, pointer :: a_ptr(:) ! This is the data we need to access from C integer(C_INT) :: n n = 0 if (associated(a_ptr)) n = size(a_ptr) test_getdim = n return end function test_getdim[/fortran]

and

[bash]type(C_PTR) function test_assoc() bind(C)
  use, intrinsic  ::  ISO_C_BINDING
  implicit none
    
! This COMMON block is shared by several subroutines/functions. common /mydata/ na, a_ptr integer :: na integer, pointer :: a_ptr(:) ! this is the data we need to access from C test_assoc1 = C_NULL_PTR if (associated(a_ptr)) then test_assoc = c_loc(a_ptr(1)) endif return end function test_assoc[/bash]


and on the C++ side we have

extern "C" int test_getdim();
extern "C" void* test_assoc();

int k = test_getdim();

std::cout << "Found n = " << k << std::endl;

if (k>0) {

int *p = NULL;
p = (int *) test_assoc1();
if (NULL != p)
{
std::cout << "Contents of array #1:" << std::endl;
for (int i=0; i << std::endl; }
}
else
{
std::cout << "Failed to associate array #1" << std::endl;
}

}

It seems to work. The only assumption I know I'm making is that Fortran always allocates array variables of deferred size with the POINTER attribute with contiguous memory. That's correct, I hope? Any other problems with this approach? Is there a better/easier way?

Thanks!
0 Kudos
IanH
Honored Contributor III
4,494 Views
test_assoc1 = C_NULL_PTR if (associated(a_ptr)) then test_assoc = c_loc(a_ptr(1)) endif return end function test_assocMy main problem was that I fogot that pointers *are* values and that I needed to declare the pointer passed from C as TYPE(C_PTR), VALUE. Of course then I got the friendly reminder that arguments passed by value cannot have INTENT(OUT) or INTENT(INOUT). So I finally realized that I would need to fecth the pointer via a function return value instead of an argument.

If I understand you correctly, then "needed" above isn't right. The equivalent of

[fortran]SUBROUTINE proc(the_size, the_ptr) BIND(C, NAME='proc')
...
  INTEGER(C_INT), INTENT(OUT) :: the_size
  TYPE(C_PTR), INTENT(OUT) :: the_ptr
[/fortran]

could be declared and called (assuming the pointer will point to "int" things):

[cpp]extern "C" void proc(int *the_size, int **the_ptr);

...

int my_size;
int *my_ptr;
proc(&my_size, &my_ptr);
if (!my_ptr) { ... }[/cpp]

Your fortran pointer's kind should be C_INT.

Having POINTERs in common is ok but a bit incongruous - why not put the entity in a module?

Things that are ALLOCATEd will be contiguous. But is there a reason that the pointer isn't an allocatable instead? That also then guarantees that someone won't go and point the pointer at something that breaks the C_LOC call (like something that isn't contiguous like an array section with stride, etc). C_LOC of an allocated allocatable array is explicitly allowed in F2003, while C_LOC of a pointer array might require F2008 to be safe.

There's no need for the subscript in the C_LOC call unless the pointer has a lower bound that isn't 1.
0 Kudos
Steven_L_Intel1
Employee
4,493 Views
The Intel compiler allows C_LOC on most anything.

There is a proposal at the standards level for extending C interoperability to include the concept of "C descriptors" so that you could pass an assumed shape array to C and have C access the bounds and data in a standard-conforming way. But otherwise, just passing an array to C where the interface declares it as an assumed-size array (*) should work. The compiler will pass the address of the first element. In C you would declare this as a pointer to the element type. Note that you'd have to do your own array indexing in C, especially if the array had more than one dimension.
0 Kudos
newfortranuser
Beginner
4,493 Views
Thank you, Ian.

Your solution is much more elegant than mine.
I see now that my mistake was to try (needlessly) to pass a C pointer to Fortran *and* try to modify that pointer and send the value back to C. That's how I ran into the conflict between the VALUE attribute and the INTENT(INOUT). As you point out, there is no need to pass the pointer to Fortran. Fortran just passes the pointer it knows to C.

Agreed: POINTER in COMMON is not something I would do. I inherited it from a C++ programmer who was doing Fortran, and I don't have time to refactor dozens of routines it right now.

Thanks!
0 Kudos
newfortranuser
Beginner
4,493 Views
Thanks, Steve. I've gotten pretty good at mapping multi-dimensional arrays, regardless of the extents of each. It will be good++ when the standard supports it and that support gets into IVF.
0 Kudos
stankoz
Beginner
4,493 Views
Hi Guys, I'm not sure if I should comment here because my question deals with these same issues or start a new post. I am trying to pass dynamically allocated pointer arrays from C into a Fortran subroutine. I'm not sure whether I need to pass them to Fortran pointers or if I can just use C_PTR then use them as regular arrays (which is how I want to use them) in Fortran. I don't want to use pointers at all but I think thats the only way to dynamically allocate arrays in C, right? Also, how do I handle INTENT(OUT) arrays from Fortran that need to passed to dynamically allocated C pointers? Do they need to be declared as pointers or can they just be of type C_DOUBLE? Thanks for your help, sorry if this isn't the appropriate place to ask this question. Let me know if I should just start a new post.
0 Kudos
IanH
Honored Contributor III
4,493 Views
On the way from C to Fortran, you could do either of the approaches that you suggested (pass as C_PTR or pass as an array). For the following C code: [cpp] #include #include void fortran_sub(double *array, int array_size); int main() { double *array; int array_size = 10; int i; array = (double *) malloc(array_size * sizeof(double)); fortran_sub(array, array_size); for (i = 0; i < array_size; ++ i) printf("%f\n", array); free(array); return 0; } [/cpp] You could (but probably shouldn't - see below) write a fortran procedure that takes a C pointer argument, like: [fortran] SUBROUTINE fortran_sub(array_ptr, array_size) BIND(C, NAME='fortran_sub') USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_PTR, C_DOUBLE, C_F_POINTER, C_INT IMPLICIT NONE ! Passing the C pointer to the array by value. INTENT(IN) because we ! require that the pointer be defined (as opposed to the thing being ! pointed to) prior to the call, and we are not going to modify where ! that pointer points (as opposed to modifying the thing that happens ! to be pointed at by the pointer). TYPE(C_PTR), INTENT(IN), VALUE :: array_ptr INTEGER(C_INT), INTENT(IN), VALUE :: array_size !---- REAL(C_DOUBLE), POINTER :: array(:) INTEGER :: i !**** CALL C_F_POINTER(array_ptr, array, [array_size]) FORALL (i = 1:SIZE(array)) array(i) = REAL(i) END SUBROUTINE fortran_sub [/fortran] But if the Fortran procedure is really just going to work on the array as an array, then a more natural way of writing it would be: [fortran] SUBROUTINE fortran_sub(array, array_size) BIND(C, NAME='fortran_sub') USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_DOUBLE, C_INT IMPLICIT NONE INTEGER(C_INT), INTENT(IN), VALUE :: array_size ! Passing the array by reference (no VALUE attribute). INTENT(OUT) ! because we are passing the value of the array back to our caller ! (and we do not require the value of the array to be defined prior ! to the call). REAL(C_DOUBLE), INTENT(OUT) :: array(array_size) !---- INTEGER :: i !**** FORALL (i = 1:SIZE(array)) array(i) = REAL(i) END SUBROUTINE fortran_sub [/fortran] Regardless of which Fortran option from above you choose - the C declaration is the same. The first option makes more sense when you are more interested in the C pointer as a C pointer - perhaps because you want to save that pointer somewhere, or (with different argument declarations) you are going to modify what the pointer points to, etc. If the C pointer is really just a detail behind passing an array by reference, then it is better to not describe that detail in your source code and just write the declarations for an an array that is passed by reference - the second form. Note that in the first case the INTENT(IN) applies to the C pointer - we declare that we are not going to modify where that pointer points. In the second case, where the argument is a non-pointer (in a Fortran sense) array, we declare that we are going to modify the value of the array. (C99 introduced the concept of variable length arrays (a bit like Fortran 90's automatic arrays), which C11 then made optional. They are another way of "dynamically" allocating arrays in C, but you need a C99 compiler.)
0 Kudos
stankoz
Beginner
4,493 Views
Ian, Thank you kindle for your response. Here is more detail of what I am trying to do and maybe this can solidify which suggestion above (maybe both?) makes more sense. I have three 1D arrays I am interested in (a,b and c). I would like them to be dynamically allocated in the C wrapper (I do have a C99 compiler) and the only way I know how to do that is with pointers and malloc. I declare and allocate as follows: [cpp] int a_size, b_size, c_size; double a[10], b[10], c[10]; // if I hard code in the lengths, option 1 double *a_ptr, *b_ptr, *c_ptr; // alternatively, option 2 fscanf(file,"%d%d%d", &a_size, &b_size, &c_size); a_ptr = (double *)malloc(a_size*sizeof(double)); // same for b and c[/cpp] For array a I want to send to fortran as a INTENT(IN) array that will be changed only in the C code prior to each call to fortran_sub, b as an INTENT(OUT) array whose values will be determined in the fortran_sub, and c as an INTENT(IN) array of parameters that are read in once and never change. I don't have any use for pointers other than for the allocation, I only care about the values contained. The fortran routine has the prototype: [cpp] extern void fortran_sub(int *, int *, int *, double [], double [], double []) [/cpp] and is called as follows [cpp] fortran_sub(&a_size, &b_size, &c_size, a, b, c); //option 1 fortran_sub(&a_size, &b_size, &c_size, a_ptr, b_ptr, c_ptr); //option 2 [/cpp] Here is the fortran declarations that worked (Thanks Steve!!) when I was dealing with option 1: [fortran] SUBROUTINE fortran_sub(LENA, LENB, LENC, A, B, C) BIND(C) USE ISO_C_BINDING INTEGER(C_INT), INTENT(IN) :: LENA,LENB,LENC REAL(C_DOUBLE), DIMENSION(LENA), INTENT(IN) :: A REAL(C_DOUBLE), DIMENSION(LENB), INTENT(OUT) :: B REAL(C_DOUBLE), DIMENSION(LENC), INTENT(IN) :: C REAL, DIMENSION(LENA) :: LOCAL1, LOCAL2 ! arrays determined/used by local routine FORALL (i = LENA) LOCAL1(i) = A(i)*C(i) FORALL (i = LENB) B(i) = LOCAL2(i) RETURN END SUBROUTINE [/fortran] Q1 - why would I need to add the VALUE attribute for LENA, LENB, LENC in this context, as the above example has for array_size? Q2 - how can I easily do this same thing now that the arrays in C are formally pointers. Does it even make a difference since regular C arrays are actually pointers? Q3 - Do I need to handle A and C any differently since I just need their values? Should they both have the VALUE attribute as well? Q4 - Will the target of b_ptr be modified by changing the (non-pointer) B array in fortran_sub or do I have to use a pointer B_PTR in Fortran too? So for option 2, I'm still not certain. Should I do the following even though I don't want to work with Fortran pointers? [fortran] SUBROUTINE fortran_sub(LENA, LENB, LENC, A_PTR, B, C) BIND(C) INTEGER(C_INT), INTENT(IN) :: LENA,LENB,LENC TYPE(C_PTR), INTENT(IN), VALUE :: A_PTR REAL(C_DOUBLE), DIMENSION(LENB), INTENT(OUT) :: B REAL(C_DOUBLE), DIMENSION(LENC), INTENT(IN) :: C REAL(C_DOUBLE), POINTER :: A(:) CALL C_F_POINTER(A_PTR, A, [LENA]) REAL, DIMENSION(LENA) :: LOCAL1, LOCAL2 ! arrays determined/used by local routine FORALL (i = LENA) LOCAL1(i) = A(i)*C(i) FORALL (i = LENB) B(i) = LOCAL2(i) RETURN END SUBROUTINE [/fortran] Sorry if this is too much, but my head is really spinning with all this pointer nonsense. What is the alternative way of allocating in C without pointers, I hate pointers, why do I have to use them (I guess that's Q5 :)?
0 Kudos
IanH
Honored Contributor III
4,493 Views
stankoz wrote:

Ian, Thank you kindle for your response.

..For array a I want to send to fortran as a INTENT(IN) array that will be changed only in the C code prior to each call to fortran_sub, b as an INTENT(OUT) array whose values will be determined in the fortran_sub, and c as an INTENT(IN) array of parameters that are read in once and never change. I don't have any use for pointers other than for the allocation, I only care about the values contained.
The fortran routine has the prototype:

     extern void fortran_sub(int *, int *, int *, double [], double [], double [])

     

and is called as follows

     fortran_sub(&a_size, &b_size, &c_size, a, b, c); //option 1

     fortran_sub(&a_size, &b_size, &c_size, a_ptr, b_ptr, c_ptr); //option 2

     

... Q1 - why would I need to add the VALUE attribute for LENA, LENB, LENC in this context, as the above example has for array_size?
You don't. In the code you provided you are passing those parameters/arguments by reference (you are passing a pointer to the storage for each size) - see the * before the parameter name in the declaration ("this parameter is a pointer") and the ampersand before the variable being passed in the call ("get the address of this variable"). In my example I was passing array_size by value (no leading star in the C declaration of the function, no leading ampersand in the call). (In the case of those LEN* arguments, because the thing being passed is small and the Fortran code doesn't need to modify the thing and get those modifications back to the C code, you could pass those LEN* things by value. This would require changes to the C declaration (remove *), the C call (remove ampersand) and the Fortran (add VALUE). Passing small variables by value is more "typical" from a C point of view, but passing things by reference is more typical from a Fortran point of view - up to you which way you go.)
Q2 - how can I easily do this same thing now that the arrays in C are formally pointers. Does it even make a difference since regular C arrays are actually pointers?
See my example. When you pass a C array from C the C compiler passes a pointer to the data for the array (you effectively always pass the array by reference). If working with the passed argument as an array in Fortran then simply declare the argument to be an array passed by reference. Passing by reference (passing a pointer to the data) is the "default" for BIND(C) procedures unless an argument has the VALUE attribute. In terms of the C to Fortran interface, there is no difference between your option 1 and option 2 as commented in your C code - the difference is only in how C sets aside memory for the array. Your option 1 Fortran code interoperates with both your option 1 and 2 in C, similarly, your option 2 Fortran code interoperates with option 1 and 2 in C.
Q3 - Do I need to handle A and C any differently since I just need their values? Should they both have the VALUE attribute as well?
No. Just declare them as INTENT(IN). Because they are arrays they are still going to be passed by reference - but a reference to some data that you promise not to modify.
Q4 - Will the target of b_ptr be modified by changing the (non-pointer) B array in fortran_sub or do I have to use a pointer B_PTR in Fortran too?
So for option 2, I'm still not certain. Should I do the following even though I don't want to work with Fortran pointers?
If you don't need to work with pointers on the Fortran side, then don't declare the Fortran arguments as C_PTR's, just declare them as arrays passed by reference. Forget I mentioned C_PTR. C_PTR? What's that? I've not seen any C_PTR's around here. Lets see how the forum software handles this response...
0 Kudos
stankoz
Beginner
4,493 Views
Ian, Thank you for the thorough explanation and beating me over the head with the 'pass by reference is pass by reference' stick. I needed that since I tend to complicate things unnecessarily. Keeping my option 1 Fortran declarations and changing to option 2 on the C-side achieved my goal and works fine. but I also changed [cpp] extern void fortran_sub(int *, int *, int *, double [], double [], double []) [/cpp] to [cpp] extern void fortran_sub(int *, int *, int *, double *, double *, double *) [/cpp]
0 Kudos
Reply