I am wondering what is the correct way of interfacing with C, when the C methods have optional arguments (i.e. you are allowed to pass NULL
) and you want the optional arguments to propage to the Fortran API.
- Is there a benefit in including the
optional
keyword in both the Fortran method's argument list and the interface block? see:err_c
argument innull_return_f90
>C_API
. - Is it legal to initialise a local variable with an optional input variable and pass that to the C interface? (this feels wrong, it doesn't feel like it should work, yet it does). see:
name_c
variable, its initialisation and input toC_API
innull_str_f90
subroutine.
I have provided a MWE below demonstrating what I am asking and as far as I can tell, the answers to my questions are 1. it doesn't matter 2. yes, but I am not entirely convinced they are correct, especially 2.
If there is a better way on how to write the Fortran API I would be open to that as well, but what I cannot do is edit the C API.
Related post: Calling Fortran subroutines with optional arguments from C
MWE
program main
use, intrinsic :: iso_c_binding
implicit none
integer(c_int) :: err
call null_str_f90("abc")
call null_str_f90()
print*, repeat("*", 10)
call null_return_f90()
call null_return_f90(err)
print*, "error code:", err
contains
function istring_(o) result(v)
character(len=*), intent(in) :: o
character(len=:, kind=c_char), allocatable :: v
v = trim(o)//c_null_char
end function istring_
subroutine null_return_f90(err)
interface
subroutine C_API(err_c) bind(C, name="null_return")
use, intrinsic :: iso_c_binding
integer(c_int), optional, intent(out) :: err_c ! 1. does the optional do anything?
end subroutine C_API
end interface
integer(c_int), optional, intent(out) :: err
call C_API(err_c=err) ! 1. Is this safe & portable?
end subroutine null_return_f90
subroutine null_str_f90(str)
interface
subroutine C_API(str_c) bind(C, name="null_str")
use, intrinsic :: iso_c_binding
character(len=1, kind=c_char), dimension(*), optional, intent(in) :: str_c
end subroutine C_API
end interface
character(len=*), intent(in), optional :: str
! Local variables
character(len=:, kind=c_char), allocatable :: name_c
if (present(str)) name_c = istring_(str)
call C_API(str_c=name_c) ! 2. Is this safe & portable?
end subroutine null_str_f90
end program main
#include <stdio.h>
void null_str(const char *str) {
if (str) {
printf("str is present: str is %s\n", str);
} else {
printf("str is not present\n");
}
}
void null_return(int *opt) {
if (opt) {
*opt = 1;
} else {
printf("opt is not present\n");
}
}
Compiling
Turn on aggressive non-IEEE compliant optimisations to ensure this will still work.
gcc -c -Ofast -Wall null_args.c -o null_args.o
gfortran -Ofast -Wall null_args.o null_args.f90 -o null_args
./null_args
Output
str is present: str is abc
str is not present
**********
opt is not present
error code: 1
**********
CodePudding user response:
For null_return_f90
and its C_API
, we must have err_c
in that latter as an optional argument. Without it being optional, we cannot associate an absent err
with it in that call. If it is optional, then it's fine to use the optional and not present actual argument err
.
Looking at the subroutine null_str_f90
we have the following:
character(len=:, kind=c_char), allocatable :: name_c
if (present(str)) name_c = istring_(str)
call C_API(str_c=name_c) ! 2. Is this safe & portable?
Your concern is that if the actual argument str
is not present, then name_c
is undefined1 when it comes to the call to C_API
. This is true, but is not a concern.
name_c
remains not allocated rather than simply undefined. An allocatable actual argument which is not allocated (or a pointer which is not associated) is allowed to be associated with an ordinary optional dummy argument. In this case it will be treated as a not present actual argument, just as if it weren't given at all.
1 You state "uninitialized" but definition and initialization are very different things, and initialization is not relevant to this case.
CodePudding user response:
Keeping in mind that function arguments:
- in
C
they are passed byvalue
, hence, they are unchanged on output; the only way to change a variable which is not the function's return value, is to pass apointer
to it (yourint* err_c
is a pointer to integer(s)) - in
Fortran
they are passed byreference
by default, unless otherwise specified (value
keyword).
whenever you write a Fortran interface to a C function that involves a pointer, you have these options:
- Copy the C approach, set it as a pointer
type(c_ptr), intent(in), value :: c_err
- You can always shape it to an appropriate Fortran derived type and shape by using
c_f_pointer
, and/or access the C pointer of a Fortrantarget
variable usingc_loc
- You lose information about the original type (
int
) in this case
- Use the Fortran approach, i.e. reference that pointer to a given type, shape, intent. For example in your case, you could use any of
integer(c_int), intent(out) :: c_err
integer(c_int), intent(out) :: c_err(*)
Note that the C routine, per se, doesn't tell you which one is right, but the Fortran compiler will check it.
Now you're trying to use the optional
keyword to hide the fact of a C pointer being NULL
as opposed to a Fortran variable being present
or not. The C standard does not allow for optional inputs; while the Fortran standard does not like null
stuff. It seems like even Fortran standard gurus do not fully agree on what's allowed by the standard or not (see here), I'd go with using the optional
keyword in the interface only if that's useful on the Fortran side.
See this example:
module test_c_opt
use iso_c_binding
implicit none
interface
! Pass-by-reference interface
subroutine C_reference(err_c,message) bind(C, name="null_return")
import c_int,c_char
integer(c_int), intent(out) :: err_c
character(len=1,kind=c_char), intent(in) :: message(*)
end subroutine C_reference
subroutine C_reference_arr(err_c,message) bind(C, name="null_return")
import c_int,c_char
integer(c_int), intent(out) :: err_c(*)
character(len=1,kind=c_char), intent(in) :: message(*)
end subroutine C_reference_arr
! Pass-by-value interface
subroutine C_value(err_c,message) bind(C, name="null_return")
import c_ptr,c_char
type(c_ptr), intent(in), value :: err_c
character(len=1,kind=c_char), intent(in) :: message(*)
end subroutine C_value
end interface
end module test_c_opt
program test
use test_c_opt
use iso_c_binding
implicit none
integer(c_int), target :: ierr,ierr10(10)
integer(c_int), allocatable, target :: ierra,ierra10(:)
character(len=255,kind=c_char) :: msg
msg = 'reference, static' //c_null_char; call C_reference(ierr,msg)
msg = 'value , static' //c_null_char; call C_value(c_loc(ierr),msg)
msg = 'reference, not alloc' //c_null_char; call C_reference(ierra,msg)
msg = 'value , not alloc' //c_null_char; call C_value(c_loc(ierra),msg)
allocate(ierra)
msg = 'reference, allocated' //c_null_char; call C_reference(ierra,msg)
msg = 'value , allocated' //c_null_char; call C_value(c_loc(ierra),msg)
msg = 'reference, not alloc(10)' //c_null_char; call C_reference_arr(ierra10,msg)
allocate(ierra10(10))
msg = 'reference, alloc(10)' //c_null_char; call C_reference_arr(ierra10,msg)
print *, 'from fortran, ierra(10)=',ierra10
!msg = 'reference, static(10)'//c_null_char; call C_reference(ierr10,msg) ! error: fortran checks this is not a scalar
msg = 'value , static(10)'//c_null_char; call C_value(c_loc(ierr10),msg)
end program test
#include <stdio.h>
void null_return(int *opt, char* message) {
if (opt) {
*opt = 1;
printf("when [%s], opt is present, set opt=%d \n", message, *opt);
} else {
printf("when [%s], opt is not present\n");
}
}
it works in all cases without optional
anyways, which prints (gfortran-12)
when [reference, static], opt is present, set opt=1
when [value , static], opt is present, set opt=1
when [reference, not alloc], opt is not present
when [value , not alloc], opt is not present
when [reference, allocated], opt is present, set opt=1
when [value , allocated], opt is present, set opt=1
when [reference, not alloc(10)], opt is not present
when [reference, alloc(10)], opt is present, set opt=1
from fortran, ierra(10)= 1 0 0 0 0 0 0 0 0 0
when [value , static(10)], opt is present, set opt=1